diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index 425a5e71798b1..2f496329dfd8e 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -70,6 +70,8 @@ def getWorkerFromParams(isXpack, job, ciGroup) { "run `node scripts/mocha`" ) }) + } else if (job == 'accessibility') { + return kibanaPipeline.functionalTestProcess('kibana-accessibility', './test/scripts/jenkins_accessibility.sh') } else if (job == 'firefoxSmoke') { return kibanaPipeline.functionalTestProcess('firefoxSmoke', './test/scripts/jenkins_firefox_smoke.sh') } else if(job == 'visualRegression') { @@ -79,7 +81,9 @@ def getWorkerFromParams(isXpack, job, ciGroup) { } } - if (job == 'firefoxSmoke') { + if (job == 'accessibility') { + return kibanaPipeline.functionalTestProcess('xpack-accessibility', './test/scripts/jenkins_xpack_accessibility.sh') + } else if (job == 'firefoxSmoke') { return kibanaPipeline.functionalTestProcess('xpack-firefoxSmoke', './test/scripts/jenkins_xpack_firefox_smoke.sh') } else if(job == 'visualRegression') { return kibanaPipeline.functionalTestProcess('xpack-visualRegression', './test/scripts/jenkins_xpack_visual_regression.sh') diff --git a/.ci/es-snapshots/Jenkinsfile_verify_es b/.ci/es-snapshots/Jenkinsfile_verify_es index c87ca01354315..a6fe980242afe 100644 --- a/.ci/es-snapshots/Jenkinsfile_verify_es +++ b/.ci/es-snapshots/Jenkinsfile_verify_es @@ -22,8 +22,8 @@ def SNAPSHOT_MANIFEST = "https://storage.googleapis.com/kibana-ci-es-snapshots-d kibanaPipeline(timeoutMinutes: 150) { catchErrors { slackNotifications.onFailure( - title: ":broken_heart: *<${env.BUILD_URL}|[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure>*", - message: ":broken_heart: [${SNAPSHOT_VERSION}] ES Snapshot Verification Failure", + title: "*<${env.BUILD_URL}|[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure>*", + message: "[${SNAPSHOT_VERSION}] ES Snapshot Verification Failure", ) { retryable.enable(2) withEnv(["ES_SNAPSHOT_MANIFEST=${SNAPSHOT_MANIFEST}"]) { diff --git a/.eslintignore b/.eslintignore index 362b3e42d48e5..c3d7930732fa2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,6 +3,7 @@ /.es /build /built_assets +/config/apm.dev.js /data /html_docs /optimize diff --git a/config/kibana.yml b/config/kibana.yml index 8725888159506..72e0764f849a0 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -90,7 +90,7 @@ # Specifies the path where Kibana creates the process ID file. #pid.file: /var/run/kibana.pid -# Enables you specify a file where Kibana stores log output. +# Enables you to specify a file where Kibana stores log output. #logging.dest: stdout # Set the value of this setting to true to suppress all logging output. diff --git a/docs/api/saved-objects/bulk_get.asciidoc b/docs/api/saved-objects/bulk_get.asciidoc index a6fdeb69ba925..eaf91a662849e 100644 --- a/docs/api/saved-objects/bulk_get.asciidoc +++ b/docs/api/saved-objects/bulk_get.asciidoc @@ -35,7 +35,7 @@ experimental[] Retrieve multiple {kib} saved objects by ID. ==== Response body `saved_objects`:: - (array) Top-level property the contains objects that represent the response for each of the requested objects. The order of the objects in the response is identical to the order of the objects in the request. + (array) Top-level property containing objects that represent the response for each of the requested objects. The order of the objects in the response is identical to the order of the objects in the request. Saved objects that are unable to persist are replaced with an error object. diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index 3e4b515e009de..7411f37d3c692 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -11,6 +11,12 @@ Some APM app features are provided via a REST API: * <> * <> +[float] +[[apm-api-example]] +=== Using the APIs + +Users interacting with APM APIs must have <>. +In addition, there are request headers to be aware of, like `kbn-xsrf: true`, and `Content-Type: applicaton/json`. Here's an example CURL request that adds an annotation to the APM app: [source,curl] @@ -32,16 +38,8 @@ curl -X POST \ }' ---- -For more information, the Kibana <> provides information on how to use Kibana APIs, -like required request headers and authentication options. - -// AGENT CONFIG API -// GET --> Feature (APM) Read -// CREATE/EDIT/DELETE --> Feature (APM) All - -// ANNOTATION API -// Feature (APM) All -// Index: `observability-annotations`. Privileges: `create_index`, `create_doc`, `manage`, and `read`. +The Kibana <> provides additional information on how to use Kibana APIs, +required request headers, and token-based authentication options. //// ******************************************************* @@ -61,6 +59,8 @@ The following Agent configuration APIs are available: * <> to list all Agent configurations. * <> to search for an Agent configuration. +See <> for information on the privileges required to use this API endpoint. + //// ******************************************************* //// @@ -327,6 +327,8 @@ The following APIs are available: By default, annotations are stored in a newly created `observability-annotations` index. The name of this index can be changed in your `config.yml` by editing `xpack.observability.annotations.index`. +See <> for information on the privileges required to use this API endpoint. + //// ******************************************************* //// diff --git a/docs/apm/apm-app-users.asciidoc b/docs/apm/apm-app-users.asciidoc new file mode 100644 index 0000000000000..442a07d279725 --- /dev/null +++ b/docs/apm/apm-app-users.asciidoc @@ -0,0 +1,256 @@ +[role="xpack"] +[[apm-app-users]] +== APM app users and privileges + +:beat_default_index_prefix: apm +:beat_kib_app: APM app +:annotation_index: `observability-annotations` + +++++ +Users and privileges +++++ + +You can use role-based access control to grant users access to secured +resources. The roles that you set up depend on your organization's security +requirements and the minimum privileges required to use specific features. + +{es-security-features} provides {ref}/built-in-roles.html[built-in roles] that grant a +subset of the privileges needed by APM users. +When possible, assign users the built-in roles to minimize the affect of future changes on your security strategy. +If no built-in role is available, you can assign users the privileges needed to accomplish a specific task. +In general, there are three types of privileges you'll work with: + +* **Elasticsearch cluster privileges**: Manage the actions a user can perform against your cluster. +* **Elasticsearch index privileges**: Control access to the data in specific indices your cluster. +* **Kibana space privileges**: Grant users write or read access to features and apps within Kibana. + +//// +*********************************** *********************************** +//// + +[role="xpack"] +[[apm-app-reader]] +=== APM reader user + +++++ +Create an APM reader user +++++ + +[[apm-app-reader-full]] +==== Full APM reader + +APM reader users typically need to view the APM app, dashboards, and visualizations that contain APM data. +These users might also need to create and edit dashboards, visualizations, and machine learning jobs. + +. Assign the following built-in roles: ++ +[options="header"] +|==== +|Role | Purpose + +|`kibana_admin` +|Grants access to all features in Kibana. + +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices + +|`machine_learning_admin` +|Grants the privileges required to create, update, and view machine learning jobs +|==== + +[[apm-app-reader-partial]] +==== Partial APM reader + +In some instances, you may wish to restrict certain Kibana apps that a user has access to. + +. Assign the following built in roles: ++ +[options="header"] +|==== +|Role | Purpose +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices +|==== + +. Assign space privileges to any Kibana space that the user needs access to. +Here are two examples: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +| `Read` or `All` on the {beat_kib_app} +| Allow the use of the the {beat_kib_app} + +| Spaces +| `Read` or `All` on Dashboards, Visualize, and Discover +| Allow the user to view, edit, and create dashboards, as well as browse data. +|==== + +. Finally, assign the following role if a user needs to enable and edit machine learning features: ++ +[options="header"] +|==== +|Role | Purpose + +|`machine_learning_admin` +|Grants the privileges required to create, update, and view machine learning jobs +|==== + +//// +*********************************** *********************************** +//// + +[role="xpack"] +[[apm-app-central-config-user]] +=== APM app central config user + +++++ +Create a central config user +++++ + +[[apm-app-central-config-manager]] +==== Central configuration manager + +Central configuration users need to be able to view, create, update, and delete Agent configurations. + +. Assign the following built-in roles: ++ +[options="header"] +|==== +|Role | Purpose + +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices +|==== + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`All` on {beat_kib_app} +|Allow full use of the {beat_kib_app} +|==== + +[[apm-app-central-config-reader]] +==== Central configuration reader + +In some instances, you may wish to create a user that can only read central configurations, +but not create, update, or delete them. + +. Assign the following built-in roles: ++ +[options="header"] +|==== +|Role | Purpose +|`apm_user` +|Grants the privileges required for APM users on +{beat_default_index_prefix}*+ indices +|==== + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`read` on the {beat_kib_app} +|Allow read access to the {beat_kib_app} +|==== + +[[apm-app-central-config-api]] +==== Central configuration API + +See <>. + +//// +*********************************** *********************************** +//// + +[role="xpack"] +[[apm-app-api-user]] +=== APM app API user + +++++ +Create an API user +++++ + +[[apm-app-api-config-manager]] +==== Central configuration API + +Users can list, search, create, update, and delete central configurations via the APM app API. + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`all` on the {beat_kib_app} +|Allow all access to the {beat_kib_app} +|==== + +[[apm-app-api-config-reader]] +==== Central configuration API reader + +Sometimes a user only needs to list and search central configurations via the APM app API. + +. Assign the following Kibana space privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`read` on the {beat_kib_app} +|Allow read access to the {beat_kib_app} +|==== + +[[apm-app-api-annotation-manager]] +==== Annotation API + +Users can use the annotation API to create annotations on their APM data. + +. Create a new role, named something like `annotation_role`, +and assign the following privileges: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +|Index +|`manage` on +{annotation_index}+ index +|Check if the +{annotation_index}+ index exists + +|Index +|`read` on +{annotation_index}+ index +|Read the +{annotation_index}+ index + +|Index +|`create_index` on +{annotation_index}+ index +|Create the +{annotation_index}+ index + +|Index +|`create_doc` on +{annotation_index}+ index +|Create new annotations in the +{annotation_index}+ index +|==== + +. Assign the `annotation_role` created previously, +and the following Kibana space privileges to any annotation API users: ++ +[options="header"] +|==== +|Type | Privilege | Purpose + +| Spaces +|`all` on the {beat_kib_app} +|Allow all access to the {beat_kib_app} +|==== + +//LEARN MORE +//Learn more about <>. diff --git a/docs/apm/index.asciidoc b/docs/apm/index.asciidoc index 79190efccdff2..53ffee5e061d6 100644 --- a/docs/apm/index.asciidoc +++ b/docs/apm/index.asciidoc @@ -31,6 +31,8 @@ include::getting-started.asciidoc[] include::how-to-guides.asciidoc[] +include::apm-app-users.asciidoc[] + include::settings.asciidoc[] include::api.asciidoc[] diff --git a/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md b/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md index a7e92f310a62e..02c000e88f31d 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md +++ b/docs/development/core/public/kibana-plugin-core-public.appcategory.label.md @@ -4,7 +4,7 @@ ## AppCategory.label property -Label used for cateogry name. Also used as aria-label if one isn't set. +Label used for category name. Also used as aria-label if one isn't set. Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.appcategory.md b/docs/development/core/public/kibana-plugin-core-public.appcategory.md index d91727a1bbf29..b0ec377e165b6 100644 --- a/docs/development/core/public/kibana-plugin-core-public.appcategory.md +++ b/docs/development/core/public/kibana-plugin-core-public.appcategory.md @@ -19,6 +19,6 @@ export interface AppCategory | [ariaLabel](./kibana-plugin-core-public.appcategory.arialabel.md) | string | If the visual label isn't appropriate for screen readers, can override it here | | [euiIconType](./kibana-plugin-core-public.appcategory.euiicontype.md) | string | Define an icon to be used for the category If the category is only 1 item, and no icon is defined, will default to the product icon Defaults to initials if no icon is defined | | [id](./kibana-plugin-core-public.appcategory.id.md) | string | Unique identifier for the categories | -| [label](./kibana-plugin-core-public.appcategory.label.md) | string | Label used for cateogry name. Also used as aria-label if one isn't set. | +| [label](./kibana-plugin-core-public.appcategory.label.md) | string | Label used for category name. Also used as aria-label if one isn't set. | | [order](./kibana-plugin-core-public.appcategory.order.md) | number | The order that categories will be sorted in Prefer large steps between categories to allow for further editing (Default categories are in steps of 1000) | diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md index 8437f86d2d48e..8958b49d98b0c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicesetup.registerroutehandlercontext.md @@ -21,7 +21,7 @@ registerRouteHandlerContext: (contextName 'myApp', (context, req) => { async function search (id: string) { - return await context.elasticsearch.adminClient.callAsInternalUser('endpoint', id); + return await context.elasticsearch.legacy.client.callAsInternalUser('endpoint', id); } return { search }; } diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md index 0d640e52c3a03..7b887d6d421e4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md @@ -13,8 +13,9 @@ core: { typeRegistry: ISavedObjectTypeRegistry; }; elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; + legacy: { + client: IScopedClusterClient; + }; }; uiSettings: { client: IUiSettingsClient; diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md index 0966b91a4ebf2..6b3fc4c03ec73 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
} | | +| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
legacy: {
client: IScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
} | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index a62cee7b654fe..1d3cfa9305c18 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -31,6 +31,7 @@ export declare class Field implements IFieldType | [indexPattern](./kibana-plugin-plugins-data-public.indexpatternfield.indexpattern.md) | | IndexPattern | | | [lang](./kibana-plugin-plugins-data-public.indexpatternfield.lang.md) | | string | | | [name](./kibana-plugin-plugins-data-public.indexpatternfield.name.md) | | string | | +| [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) | | boolean | | | [script](./kibana-plugin-plugins-data-public.indexpatternfield.script.md) | | string | | | [scripted](./kibana-plugin-plugins-data-public.indexpatternfield.scripted.md) | | boolean | | | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md new file mode 100644 index 0000000000000..4b012c26a8620 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [readFromDocValues](./kibana-plugin-plugins-data-public.indexpatternfield.readfromdocvalues.md) + +## IndexPatternField.readFromDocValues property + +Signature: + +```typescript +readFromDocValues?: boolean; +``` diff --git a/docs/images/management-create-rollup-bar-chart.png b/docs/images/management-create-rollup-bar-chart.png new file mode 100644 index 0000000000000..324cfcb9ee5fb Binary files /dev/null and b/docs/images/management-create-rollup-bar-chart.png differ diff --git a/docs/images/management-rollup-index-pattern.png b/docs/images/management-rollup-index-pattern.png new file mode 100644 index 0000000000000..57ac00be7977c Binary files /dev/null and b/docs/images/management-rollup-index-pattern.png differ diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index da2e190847fdb..e9e4054f3b9ba 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -60,7 +60,7 @@ You can read more at {ref}/rollup-job-config.html[rollup job configuration]. === Try it: Create and visualize rolled up data This example creates a rollup job to capture log data from sample web logs. -To follow along, add the <>. +To follow along, add the <>. In this example, you want data that is older than 7 days in the target index pattern `kibana_sample_data_logs` to roll up once a day into the index `rollup_logstash`. You’ll bucket the @@ -127,19 +127,36 @@ rollup index, or you can remove or archive it using < Index Patterns* so you can +select your rolled up data for visualizations. Click *Create index pattern*, and select *Rollup index pattern* from the dropdown. ++ +[role="screenshot"] +image::images/management-rollup-index-pattern.png[][Create rollup index pattern] + +. Enter *rollup_logstash,kibana_sample_logs* as your *Index Pattern* and `@timestamp` +as the *Time Filter field name*. ++ The notation for a combination index pattern with both raw and rolled up data -is `rollup_logstash,kibana_sample_data_logs`. +is `rollup_logstash,kibana_sample_data_logs`. In this index pattern, `rollup_logstash` +matches the rolled up index pattern and `kibana_sample_data_logs` matches the index +pattern for raw data. +. Go to *Visualize* and create a vertical bar chart. Choose `rollup_logstash,kibana_sample_data_logs` +as your source to see both the raw and rolled up data. ++ [role="screenshot"] -image::images/management_rollup_job_vis.png[][Visualization of rolled up data] +image::images/management-create-rollup-bar-chart.png[][Create visualization of rolled up data] -You can then create a dashboard that contains visualizations of the rolled up -data, raw data, or both. For more information, refer to <>. +. Look at the data in your visualization. ++ +[role="screenshot"] +image::images/management_rollup_job_vis.png[][Visualization of rolled up data] +. Optionally, create a dashboard that contains visualizations of the rolled up +data, raw data, or both. ++ [role="screenshot"] image::images/management_rollup_job_dashboard.png[][Dashboard with rolled up data] diff --git a/docs/maps/images/embed_in_dashboard.jpeg b/docs/maps/images/embed_in_dashboard.jpeg new file mode 100644 index 0000000000000..7be233e7a0364 Binary files /dev/null and b/docs/maps/images/embed_in_dashboard.jpeg differ diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index de90d7adb29c0..6480d64bdd174 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -11,17 +11,41 @@ With *Elastic Maps*, you can: * Create maps with multiple layers and indices. * Upload GeoJSON files into Elasticsearch. -* Embed your map in Dashboards. -* Plot individual documents or use aggregations to plot any data set, no matter how large. -* Create choropleth maps. -* Use data driven styling to symbolize features from property values. -* Focus the data you want to display with searches. +* Embed your map in dashboards. +* Symbolize features using data values. +* Focus in on just the data you want. -Start your tour of *Elastic Maps* with the <>. +*Ready to get started?* Start your tour of *Elastic Maps* with the <>. + +[float] +=== Create maps with multiple layers and indices +You can use multiple layers and indices to show all your data in a single map. This enables your map to show how data sits relative to physical features like weather patterns, human-made features like international borders, and business-specific features like sales regions. You can plot individual documents or use aggregations to plot any data set, no matter how large. [role="screenshot"] image::maps/images/sample_data_ecommerce.png[] +[float] +=== Upload GeoJSON files into Elasticsearch +Elastic Maps makes it easy to import geospatial data into the Elastic Stack. Using the GeoJSON Upload feature, you can drag and drop your point and shape data files directly into Elasticsearch, and then use them as layers in the map. + +[float] +=== Embed your map in dashboards +Viewing data from different angles provides better insights. Dimensions that are obscured in one visualization might be illuminated in another. Add your map to a dashboard and view your geospatial data alongside bar charts, pie charts, tag clouds, and more. + +This choropleth map shows the density of non-emergency service requests in San Diego by council district. The map is embedded in a dashboard, so users can better understand when services are requested and gain insight into the top requested services. + +[role="screenshot"] +image::maps/images/embed_in_dashboard.jpeg[] + +[float] +=== Symbolize features using data values +You can customize each layer to highlight meaningful dimensions in your data. For example, you can use dark colors to symbolize areas with more web log traffic, and lighter colors to symbolize areas with less traffic. + +[float] +=== Focus in on just the data you want +You can search across your Elasticsearch layers to focus in on just the data you want. Draw a polygon on the map or use the shape from features to create spatial filters to narrow search results to documents that either intersect with, are within, or do not intersect with the specified geometry. Filter individual layers to compares facets. + + -- include::maps-getting-started.asciidoc[] diff --git a/docs/user/alerting/index.asciidoc b/docs/user/alerting/index.asciidoc index df11f5f03a7de..6f691f2715bc8 100644 --- a/docs/user/alerting/index.asciidoc +++ b/docs/user/alerting/index.asciidoc @@ -160,7 +160,7 @@ If you are using an *on-premises* Elastic Stack deployment: If you are using an *on-premises* Elastic Stack deployment with <>: -* Transport Layer Security (TLS) must be configured for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. +* You must enable Transport Layer Security (TLS) for communication <>. {kib} alerting uses <> to secure background alert checks and actions, and API keys require {ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. A proxy will not suffice. [float] [[alerting-security]] diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index bcaede01b7a86..1704a80847652 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -1,123 +1,171 @@ [[management]] -= Management += Stack Management [partintro] -- -*Management* is home to UIs for managing all things Elastic Stack— +*Stack Management* is home to UIs for managing all things Elastic Stack— indices, clusters, licenses, UI settings, index patterns, spaces, and more. [float] -[[manage-Elasticsearch]] -== Manage {es} +[[manage-ingest]] +== Ingest [cols="50, 50"] |=== -a| <> - -Replicate indices on a remote cluster and copy them to a follower index on a local cluster. -This is important for -disaster recovery. It also keeps data local for faster queries. - -| <> - -Create a policy for defining the lifecycle of an index as it ages -through the hot, warm, cold, and delete phases. -Such policies help you control operation costs -because you can put data in different resource tiers. +| <> +| Create and manage {es} +pipelines that enable you to perform common transformations and +enrichments on your data. -a| <> +| {logstash-ref}/logstash-centralized-pipeline-management.html[Logstash Pipelines] +| Create, edit, and delete your Logstash pipeline configurations. -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. +| <> +| Manage your Beats configurations in a central location and +quickly deploy configuration changes to all Beats running across your enterprise. -a| <> -Create and manage {es} -pipelines that enable you to perform common transformations and -enrichments on your data. +|=== -| <> +[float] +[[manage-data]] +== Data -View the status of your license, start a trial, or install a new license. For -the full list of features that are included in your license, -see the https://www.elastic.co/subscriptions[subscription page]. +[cols="50, 50"] +|=== -| <> +a| <> +| 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. -Manage your remote clusters for use with cross-cluster search and cross-cluster replication. -You can add and remove remote clusters, and check their connectivity. +| <> +|Create a policy for defining the lifecycle of an index as it ages +through the hot, warm, cold, and delete phases. +Such policies help you control operation costs +because you can put data in different resource tiers. -| <> +| <> +|Define a policy that creates, schedules, and automatically deletes snapshots to ensure that you +have backups of your cluster in case something goes wrong. -Create a job that periodically aggregates data from one or more indices, and then +| <> +|Create a job that periodically aggregates data from one or more indices, and then rolls it into a new, compact index. Rollup indices are a good way to store months or years of historical data in combination with your raw data. -| <> +| {ref}/transforms.html[Transforms] +|Use transforms to pivot existing {es} indices into summarized or entity-centric indices. -Define a policy that creates, schedules, and automatically deletes snapshots to ensure that you -have backups of your cluster in case something goes wrong. +| <> +|Replicate indices on a remote cluster and copy them to a follower index on a local cluster. +This is important for +disaster recovery. It also keeps data local for faster queries. -| {ref}/transforms.html[*Transforms*] +| <> +|Manage your remote clusters for use with cross-cluster search and cross-cluster replication. +You can add and remove remote clusters, and check their connectivity. +|=== -Use transforms to pivot existing {es} indices into summarized or entity-centric indices. +[float] +[[manage-alerts-insights]] +== Alerts and Insights -| <> +[cols="50, 50"] +|=== -Identify the issues that you need to address before upgrading to the -next major version of {es}, and then reindex, if needed. +| <> +| Centrally manage your alerts across {kib}. Create and manage reusable +connectors for triggering actions. -| <> +| <> +| Monitor the generation of reports—PDF, PNG, and CSV—and download reports that you previously generated. +A report can contain a dashboard, visualization, saved search, or Canvas workpad. + +| {ml-docs}/ml-jobs.html[Machine Learning Jobs] +| View your {anomaly-jobs} and {dfanalytics-jobs}. Open the Single Metric +Viewer or Anomaly Explorer to see your {ml} results. -Detect changes in your data by creating, managing, and monitoring alerts. -For example, create an alert when the maximum total CPU usage on a machine goes +| <> +| Detect changes in your data by creating, managing, and monitoring alerts. +For example, you might create an alert when the maximum total CPU usage on a machine goes above a certain percentage. |=== [float] -[[manage-kibana]] -== Manage {kib} +[[manage-security]] +== Security [cols="50, 50"] |=== -a| <> - -Customize {kib} to suit your needs. Change the format for displaying dates, turn on dark mode, -set the timespan for notification messages, and much more. +a| <> +|View the users that have been defined on your cluster. +Add or delete users and assign roles that give users +specific privileges. -| <> +| <> +|View the roles that exist on your cluster. Customize +the actions that a user with the role can perform, on a cluster, index, and space level. -Centrally manage your alerts across {kib}. Create and manage reusable -connectors for triggering actions. +| <> +| Create secondary credentials so that you can send requests on behalf of the user. +Secondary credentials have the same or lower access rights. -| <> +| <> +| Assign roles to your users using a set of rules. Role mappings are required +when authenticating via an external identity provider, such as Active Directory, +Kerberos, PKI, OIDC, and SAML. -Create and manage the index patterns that help you retrieve your data from {es}. +|=== -| <> +[float] +[[manage-kibana]] +== {kib} -Monitor the generation of reports—PDF, PNG, and CSV—and download reports that you previously generated. -A report can contain a dashboard, visualization, saved search, or Canvas workpad. +[cols="50, 50"] +|=== -| <> +a| <> +|Create and manage the index patterns that retrieve your data from {es}. -Copy, edit, delete, import, and export your saved objects. +| <> +| Copy, edit, delete, import, and export your saved objects. These include dashboards, visualizations, maps, index patterns, Canvas workpads, and more. -| <> - -Create spaces to organize your dashboards and other saved objects into categories. +| <> +| Create spaces to organize your dashboards and other saved objects into categories. A space is isolated from all other spaces, so you can tailor it to your needs without impacting others. -|   +a| <> +| Customize {kib} to suit your needs. Change the format for displaying dates, turn on dark mode, +set the timespan for notification messages, and much more. + +|=== + +[float] +[[manage-stack]] +== Stack + +[cols="50, 50"] +|=== + +| <> +| View the status of your license, start a trial, or install a new license. For +the full list of features that are included in your license, +see the https://www.elastic.co/subscriptions[subscription page]. + +| <> +| Identify the issues that you need to address before upgrading to the +next major version of {es}, and then reindex, if needed. |=== + + -- include::{kib-repo-dir}/management/advanced-options.asciidoc[] diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index 853c735418cea..4b91812660c78 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -2,16 +2,17 @@ [[xpack-security-authorization]] === Granting access to {kib} -The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built-in role], which you can use to grant access to all Kibana features in all spaces. To grant users access to a subset of spaces or features, you can create a custom role that grants the desired Kibana privileges. +The Elastic Stack comes with the `kibana_admin` {ref}/built-in-roles.html[built-in role], which you can use to grant access to all Kibana features in all spaces. To grant users access to a subset of spaces or features, you can create a custom role that grants the desired Kibana privileges. When you assign a user multiple roles, the user receives a union of the roles’ privileges. Therefore, assigning the `kibana_admin` role in addition to a custom role that grants Kibana privileges is ineffective because `kibana_admin` has access to all the features in all spaces. NOTE: When running multiple tenants of Kibana by changing the `kibana.index` in your `kibana.yml`, you cannot use `kibana_admin` to grant access. You must create custom roles that authorize the user for that specific tenant. Although multi-tenant installations are supported, the recommended approach to securing access to Kibana segments is to grant users access to specific spaces. [role="xpack"] +[[xpack-kibana-role-management]] === {kib} role management -To create a role that grants {kib} privileges, go to **Management -> Security -> Roles** and click **Create role**. +To create a role that grants {kib} privileges, go to **Management -> Security -> Roles** and click **Create role**. [[adding_kibana_privileges]] ==== Adding {kib} privileges @@ -63,7 +64,7 @@ Features are available to users when their roles grant access to the features, * Using the same role, it’s possible to assign different privileges to different spaces. After you’ve added space privileges, click **Add space privilege**. If you’ve already added privileges for either *** Global (all spaces)** or an individual space, you will not be able to select these in the **Spaces** selection control. -Additionally, if you’ve already assigned privileges at *** Global (all spaces)**, you are only able to assign additional privileges to individual spaces. Similar to the behavior of multiple roles granting the union of all privileges, space privileges are also a union. If you’ve already granted the user the **All** privilege at *** Global (all spaces)**, you’re not able to restrict the role to only the **Read** privilege at an individual space. +Additionally, if you’ve already assigned privileges at *** Global (all spaces)**, you are only able to assign additional privileges to individual spaces. Similar to the behavior of multiple roles granting the union of all privileges, space privileges are also a union. If you’ve already granted the user the **All** privilege at *** Global (all spaces)**, you’re not able to restrict the role to only the **Read** privilege at an individual space. ==== Privilege summary @@ -111,4 +112,3 @@ image::user/security/images/privilege-example-2.png[Privilege example 2] [role="screenshot"] image::user/security/images/privilege-example-3.png[Privilege example 3] - diff --git a/packages/kbn-optimizer/src/common/worker_config.ts b/packages/kbn-optimizer/src/common/worker_config.ts index 3fb1880a73716..a1ab51ee97c23 100644 --- a/packages/kbn-optimizer/src/common/worker_config.ts +++ b/packages/kbn-optimizer/src/common/worker_config.ts @@ -31,7 +31,7 @@ export interface WorkerConfig { readonly optimizerCacheKey: unknown; } -export type CacheableWorkerConfig = Omit; +export type CacheableWorkerConfig = Omit; export function parseWorkerConfig(json: string): WorkerConfig { try { diff --git a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts index 14ff320173d60..39064c64062e8 100644 --- a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts @@ -208,8 +208,8 @@ it('emits "bundle not cached" event when optimizerCacheKey is outdated, includes "diff": "- Expected + Received - - old - + optimizerCacheKey", + - \\"old\\" + + \\"optimizerCacheKey\\"", "reason": "optimizer cache key mismatch", "type": "bundle not cached", }, @@ -291,8 +291,8 @@ it('emits "bundle not cached" event when cacheKey is outdated', async () => { "diff": "- Expected + Received - - old - + new", + - \\"old\\" + + \\"new\\"", "reason": "cache key mismatch", "type": "bundle not cached", }, diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts index d5b0b8491f727..9d7f1709506f9 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.test.ts @@ -100,7 +100,6 @@ describe('getOptimizerCacheKey()', () => { }, "workerConfig": Object { "browserslistEnv": "dev", - "cache": true, "dist": false, "optimizerCacheKey": "♻", "repoRoot": , @@ -134,13 +133,13 @@ describe('diffCacheKey()', () => { "- Expected + Received -  Array [ +  [  \\"1\\",  \\"2\\", -  Object { - - \\"a\\": \\"b\\", - + \\"b\\": \\"a\\", -  }, +  { + - \\"a\\": \\"b\\" + + \\"b\\": \\"a\\" +  }  ]" `); expect( @@ -158,11 +157,11 @@ describe('diffCacheKey()', () => { "- Expected + Received -  Object { +  { - \\"a\\": \\"1\\", - - \\"b\\": \\"1\\", + - \\"b\\": \\"1\\" + \\"a\\": \\"2\\", - + \\"b\\": \\"2\\", + + \\"b\\": \\"2\\"  }" `); }); diff --git a/packages/kbn-optimizer/src/optimizer/cache_keys.ts b/packages/kbn-optimizer/src/optimizer/cache_keys.ts index e024af125312d..2766f6d63702b 100644 --- a/packages/kbn-optimizer/src/optimizer/cache_keys.ts +++ b/packages/kbn-optimizer/src/optimizer/cache_keys.ts @@ -48,11 +48,18 @@ function omit(obj: T, keys: K[]): Omit { } export function diffCacheKey(expected?: unknown, actual?: unknown) { - if (jsonStable(expected) === jsonStable(actual)) { + const expectedJson = jsonStable(expected, { + space: ' ', + }); + const actualJson = jsonStable(actual, { + space: ' ', + }); + + if (expectedJson === actualJson) { return; } - return reformatJestDiff(jestDiff(expected, actual)); + return reformatJestDiff(jestDiff(expectedJson, actualJson)); } export function reformatJestDiff(diff: string | null) { @@ -178,7 +185,7 @@ export async function getOptimizerCacheKey(config: OptimizerConfig) { bootstrap, deletedPaths, modifiedTimes: {} as Record, - workerConfig: omit(config.getWorkerConfig('♻'), ['watch', 'profileWebpack']), + workerConfig: omit(config.getWorkerConfig('♻'), ['watch', 'profileWebpack', 'cache']), }; const mtimes = await getMtimes(modifiedPaths); diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index b298c08f162bf..3bcea44cf73b6 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -49,8 +49,7 @@ export function runFailedTestsReporterCli() { } const isPr = !!process.env.ghprbPullId; - const isMasterOrVersion = - branch.match(/^(origin\/){0,1}master$/) || branch.match(/^(origin\/){0,1}\d+\.(x|\d+)$/); + const isMasterOrVersion = branch === 'master' || branch.match(/^\d+\.(x|\d+)$/); if (!isMasterOrVersion || isPr) { log.info('Failure issues only created on master/version branch jobs'); updateGithub = false; diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index c7925f5b6d821..9583cca177619 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -373,8 +373,7 @@ describe('http service', () => { const router = createRouter('/new-platform'); router.get({ path: '/', validate: false }, async (context, req, res) => { // it forces client initialization since the core creates them lazily. - await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); - await context.core.elasticsearch.dataClient.callAsCurrentUser('ping'); + await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); return res.ok(); }); @@ -382,11 +381,9 @@ describe('http service', () => { await kbnTestServer.request.get(root, '/new-platform/').expect(200); - // admin client contains authHeaders for BWC with legacy platform. - const [adminClient, dataClient] = clusterClientMock.mock.calls; - const [, , adminClientHeaders] = adminClient; - expect(adminClientHeaders).toEqual(authHeaders); - const [, , dataClientHeaders] = dataClient; + // client contains authHeaders for BWC with legacy platform. + const [client] = clusterClientMock.mock.calls; + const [, , dataClientHeaders] = client; expect(dataClientHeaders).toEqual(authHeaders); }); @@ -398,8 +395,7 @@ describe('http service', () => { const router = createRouter('/new-platform'); router.get({ path: '/', validate: false }, async (context, req, res) => { // it forces client initialization since the core creates them lazily. - await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); - await context.core.elasticsearch.dataClient.callAsCurrentUser('ping'); + await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); return res.ok(); }); @@ -410,10 +406,8 @@ describe('http service', () => { .set('Authorization', authorizationHeader) .expect(200); - const [adminClient, dataClient] = clusterClientMock.mock.calls; - const [, , adminClientHeaders] = adminClient; - expect(adminClientHeaders).toEqual({ authorization: authorizationHeader }); - const [, , dataClientHeaders] = dataClient; + const [client] = clusterClientMock.mock.calls; + const [, , dataClientHeaders] = client; expect(dataClientHeaders).toEqual({ authorization: authorizationHeader }); }); }); diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 4be7e59acb7b9..77e0d6b61692d 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -234,7 +234,7 @@ export interface HttpServiceSetup { * 'myApp', * (context, req) => { * async function search (id: string) { - * return await context.elasticsearch.adminClient.callAsInternalUser('endpoint', id); + * return await context.elasticsearch.legacy.client.callAsInternalUser('endpoint', id); * } * return { search }; * } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index cf999875b18f8..96bb0c9a006b0 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -354,8 +354,9 @@ export interface RequestHandlerContext { typeRegistry: ISavedObjectTypeRegistry; }; elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; + legacy: { + client: IScopedClusterClient; + }; }; uiSettings: { client: IUiSettingsClient; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c707fa2b479e4..f0fd471abb9be 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -199,8 +199,9 @@ function createCoreRequestHandlerContextMock() { typeRegistry: savedObjectsTypeRegistryMock.create(), }, elasticsearch: { - adminClient: elasticsearchServiceMock.createScopedClusterClient(), - dataClient: elasticsearchServiceMock.createScopedClusterClient(), + legacy: { + client: elasticsearchServiceMock.createScopedClusterClient(), + }, }, uiSettings: { client: uiSettingsServiceMock.createClient(), diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index fcf9a9e2dedc2..858458bfe40de 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1576,8 +1576,9 @@ export interface RequestHandlerContext { typeRegistry: ISavedObjectTypeRegistry; }; elasticsearch: { - dataClient: IScopedClusterClient; - adminClient: IScopedClusterClient; + legacy: { + client: IScopedClusterClient; + }; }; uiSettings: { client: IUiSettingsClient; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index d4c0ebcfb7cf2..8226b4e3a57e0 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -256,8 +256,9 @@ export class Server { typeRegistry: this.coreStart!.savedObjects.getTypeRegistry(), }, elasticsearch: { - adminClient: coreSetup.elasticsearch.adminClient.asScoped(req), - dataClient: coreSetup.elasticsearch.dataClient.asScoped(req), + legacy: { + client: coreSetup.elasticsearch.dataClient.asScoped(req), + }, }, uiSettings: { client: uiSettingsClient, diff --git a/src/core/types/app_category.ts b/src/core/types/app_category.ts index 8b39889b43a82..396cb07b437a1 100644 --- a/src/core/types/app_category.ts +++ b/src/core/types/app_category.ts @@ -30,7 +30,7 @@ export interface AppCategory { id: string; /** - * Label used for cateogry name. + * Label used for category name. * Also used as aria-label if one isn't set. */ label: string; diff --git a/src/dev/ci_setup/checkout_sibling_es.sh b/src/dev/ci_setup/checkout_sibling_es.sh index 2eec99a690134..915759d4214f9 100755 --- a/src/dev/ci_setup/checkout_sibling_es.sh +++ b/src/dev/ci_setup/checkout_sibling_es.sh @@ -43,7 +43,7 @@ function checkout_sibling { fi cloneAuthor="elastic" - cloneBranch="${PR_SOURCE_BRANCH:-${GIT_BRANCH#*/}}" # GIT_BRANCH starts with the repo, i.e., origin/master + cloneBranch="$GIT_BRANCH" if clone_target_is_valid ; then return 0 fi diff --git a/src/dev/ci_setup/extract_bootstrap_cache.sh b/src/dev/ci_setup/extract_bootstrap_cache.sh index 4a5977a68169e..fdd8bb6b90406 100755 --- a/src/dev/ci_setup/extract_bootstrap_cache.sh +++ b/src/dev/ci_setup/extract_bootstrap_cache.sh @@ -2,7 +2,7 @@ set -e -targetBranch="${PR_TARGET_BRANCH:-${GIT_BRANCH#*/}}" +targetBranch="${PR_TARGET_BRANCH:-$GIT_BRANCH}" bootstrapCache="$HOME/.kibana/bootstrap_cache/$targetBranch.tar" ### diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js index dcd78250986df..03126d130e984 100644 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js +++ b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js @@ -17,17 +17,12 @@ * under the License. */ -import { spawn } from 'child_process'; import { resolve } from 'path'; -import { green, always } from '../utils'; +import execa from 'execa'; +import expect from '@kbn/expect'; const ROOT_DIR = resolve(__dirname, '../../../../..'); const MOCKS_DIR = resolve(__dirname, './mocks'); -const staticSiteUrlRegexes = { - staticHostIncluded: /https:\/\/kibana-coverage\.elastic\.dev/, - timeStampIncluded: /\d{4}-\d{2}-\d{2}T\d{2}.*\d{2}.*\d{2}Z/, - folderStructureIncluded: /(?:.*|.*-combined)\//, -}; const env = { BUILD_ID: 407, CI_RUN_URL: 'https://kibana-ci.elastic.co/job/elastic+kibana+code-coverage/407/', @@ -37,11 +32,6 @@ const env = { NODE_ENV: 'integration_test', COVERAGE_INGESTION_KIBANA_ROOT: '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', }; -const includesSiteUrlPredicate = (x) => x.includes('staticSiteUrl'); -const siteUrlLines = specificLinesOnly(includesSiteUrlPredicate); -const splitByNewLine = (x) => x.split('\n'); -const siteUrlsSplitByNewLine = siteUrlLines(splitByNewLine); -const siteUrlsSplitByNewLineWithoutBlanks = siteUrlsSplitByNewLine(notBlankLines); const verboseArgs = [ 'scripts/ingest_coverage.js', '--verbose', @@ -50,62 +40,33 @@ const verboseArgs = [ '--path', ]; -describe('Ingesting coverage', () => { - const bothIndexesPath = 'jest-combined/coverage-summary-manual-mix.json'; +// FLAKY: https://github.com/elastic/kibana/issues/67554 +// FLAKY: https://github.com/elastic/kibana/issues/67555 +// FLAKY: https://github.com/elastic/kibana/issues/67556 +describe.skip('Ingesting coverage', () => { + const summaryPath = 'jest-combined/coverage-summary-manual-mix.json'; + const resolved = resolve(MOCKS_DIR, summaryPath); + const siteUrlRegex = /"staticSiteUrl": (".+",)/; + let actualUrl = ''; - describe(`to the coverage index`, () => { - const mutableCoverageIndexChunks = []; + beforeAll(async () => { + const opts = [...verboseArgs, resolved]; + const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env }); + actualUrl = siteUrlRegex.exec(stdout)[1]; + }); - beforeAll((done) => { - const ingestAndMutateAsync = ingestAndMutate(done); - const ingestAndMutateAsyncWithPath = ingestAndMutateAsync(bothIndexesPath); - const verboseIngestAndMutateAsyncWithPath = ingestAndMutateAsyncWithPath(verboseArgs); - verboseIngestAndMutateAsyncWithPath(mutableCoverageIndexChunks); + describe(`staticSiteUrl`, () => { + it('should contain the static host', () => { + const staticHost = /https:\/\/kibana-coverage\.elastic\.dev/; + expect(staticHost.test(actualUrl)).ok(); + }); + it('should contain the timestamp', () => { + const timeStamp = /\d{4}-\d{2}-\d{2}T\d{2}.*\d{2}.*\d{2}Z/; + expect(timeStamp.test(actualUrl)).ok(); + }); + it('should contain the folder structure', () => { + const folderStructure = /(?:.*|.*-combined)\//; + expect(folderStructure.test(actualUrl)).ok(); }); - - it( - 'should result in every posted item having a site url that meets all regex assertions', - always( - siteUrlsSplitByNewLineWithoutBlanks(mutableCoverageIndexChunks).forEach( - expectAllRegexesToPass({ - ...staticSiteUrlRegexes, - endsInDotJsDotHtml: /.js.html$/, - }) - ) - ) - ); }); }); - -function ingestAndMutate(done) { - return (summaryPathSuffix) => (args) => (xs) => { - const coverageSummaryPath = resolve(MOCKS_DIR, summaryPathSuffix); - const opts = [...args, coverageSummaryPath]; - const ingest = spawn(process.execPath, opts, { cwd: ROOT_DIR, env }); - - ingest.stdout.on('data', (x) => xs.push(x + '')); - ingest.on('close', done); - }; -} - -function specificLinesOnly(predicate) { - return (splitByNewLine) => (notBlankLines) => (xs) => - xs.filter(predicate).map((x) => splitByNewLine(x).reduce(notBlankLines)); -} - -function notBlankLines(acc, item) { - if (item !== '') return item; - return acc; -} - -function expectAllRegexesToPass(staticSiteUrlRegexes) { - return (urlLine) => - Object.entries(staticSiteUrlRegexes).forEach((regexTuple) => { - if (!regexTuple[1].test(urlLine)) - throw new Error( - `\n### ${green('FAILED')}\nAsserting: [\n\t${green( - regexTuple[0] - )}\n]\nAgainst: [\n\t${urlLine}\n]` - ); - }); -} diff --git a/src/plugins/data/public/index_patterns/fields/__snapshots__/field.test.ts.snap b/src/plugins/data/public/index_patterns/fields/__snapshots__/field.test.ts.snap new file mode 100644 index 0000000000000..4593349a408a7 --- /dev/null +++ b/src/plugins/data/public/index_patterns/fields/__snapshots__/field.test.ts.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Field exports the property to JSON 1`] = ` +Object { + "aggregatable": true, + "conflictDescriptions": Object { + "a": Array [ + "b", + "c", + ], + "d": Array [ + "e", + ], + }, + "count": 1, + "esTypes": Array [ + "type", + ], + "lang": "lang", + "name": "name", + "readFromDocValues": false, + "script": "script", + "scripted": true, + "searchable": true, + "subType": Object { + "multi": Object { + "parent": "parent", + }, + "nested": Object { + "path": "path", + }, + }, + "type": "type", +} +`; diff --git a/src/plugins/data/public/index_patterns/fields/field.test.ts b/src/plugins/data/public/index_patterns/fields/field.test.ts new file mode 100644 index 0000000000000..18252b159d98d --- /dev/null +++ b/src/plugins/data/public/index_patterns/fields/field.test.ts @@ -0,0 +1,223 @@ +/* + * 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 { Field } from './field'; +import { IndexPattern } from '..'; +import { notificationServiceMock } from '../../../../../core/public/mocks'; +import { FieldFormatsStart } from '../../field_formats'; +import { KBN_FIELD_TYPES } from '../../../common'; + +describe('Field', function () { + function flatten(obj: Record) { + return JSON.parse(JSON.stringify(obj)); + } + + function getField(values = {}) { + return new Field( + fieldValues.indexPattern as IndexPattern, + { ...fieldValues, ...values }, + false, + { + fieldFormats: {} as FieldFormatsStart, + toastNotifications: notificationServiceMock.createStartContract().toasts, + } + ); + } + + const fieldValues = { + name: 'name', + type: 'type', + script: 'script', + lang: 'lang', + count: 1, + esTypes: ['type'], + aggregatable: true, + filterable: true, + searchable: true, + sortable: true, + readFromDocValues: false, + visualizable: true, + scripted: true, + subType: { multi: { parent: 'parent' }, nested: { path: 'path' } }, + displayName: 'displayName', + indexPattern: ({ + fieldFormatMap: { name: {}, _source: {}, _score: {}, _id: {} }, + } as unknown) as IndexPattern, + format: { name: 'formatName' }, + $$spec: {}, + conflictDescriptions: { a: ['b', 'c'], d: ['e'] }, + } as Field; + + it('the correct properties are writable', () => { + const field = getField(); + + expect(field.count).toEqual(1); + field.count = 2; + expect(field.count).toEqual(2); + + expect(field.script).toEqual(fieldValues.script); + field.script = '1'; + expect(field.script).toEqual('1'); + + expect(field.lang).toEqual(fieldValues.lang); + field.lang = 'painless'; + expect(field.lang).toEqual('painless'); + + expect(field.conflictDescriptions).toEqual(fieldValues.conflictDescriptions); + field.conflictDescriptions = {}; + expect(field.conflictDescriptions).toEqual({}); + }); + + it('the correct properties are not writable', () => { + const field = getField(); + + expect(field.name).toEqual(fieldValues.name); + field.name = 'newName'; + expect(field.name).toEqual(fieldValues.name); + + expect(field.type).toEqual(fieldValues.type); + field.type = 'newType'; + expect(field.type).toEqual(fieldValues.type); + + expect(field.esTypes).toEqual(fieldValues.esTypes); + field.esTypes = ['newType']; + expect(field.esTypes).toEqual(fieldValues.esTypes); + + expect(field.scripted).toEqual(fieldValues.scripted); + field.scripted = false; + expect(field.scripted).toEqual(fieldValues.scripted); + + expect(field.searchable).toEqual(fieldValues.searchable); + field.searchable = false; + expect(field.searchable).toEqual(fieldValues.searchable); + + expect(field.aggregatable).toEqual(fieldValues.aggregatable); + field.aggregatable = false; + expect(field.aggregatable).toEqual(fieldValues.aggregatable); + + expect(field.readFromDocValues).toEqual(fieldValues.readFromDocValues); + field.readFromDocValues = true; + expect(field.readFromDocValues).toEqual(fieldValues.readFromDocValues); + + expect(field.subType).toEqual(fieldValues.subType); + field.subType = {}; + expect(field.subType).toEqual(fieldValues.subType); + + // not writable, not serialized + expect(() => { + field.indexPattern = {} as IndexPattern; + }).toThrow(); + + // computed fields + expect(() => { + field.format = { name: 'newFormatName' }; + }).toThrow(); + + expect(() => { + field.sortable = false; + }).toThrow(); + + expect(() => { + field.filterable = false; + }).toThrow(); + + expect(() => { + field.visualizable = false; + }).toThrow(); + + expect(() => { + field.displayName = 'newDisplayName'; + }).toThrow(); + + expect(() => { + field.$$spec = { a: 'b' }; + }).toThrow(); + }); + + it('sets type field when _source field', () => { + const field = getField({ name: '_source' }); + expect(field.type).toEqual('_source'); + }); + + it('calculates searchable', () => { + const field = getField({ searchable: true, scripted: false }); + expect(field.searchable).toEqual(true); + + const fieldB = getField({ searchable: false, scripted: true }); + expect(fieldB.searchable).toEqual(true); + + const fieldC = getField({ searchable: false, scripted: false }); + expect(fieldC.searchable).toEqual(false); + }); + + it('calculates aggregatable', () => { + const field = getField({ aggregatable: true, scripted: false }); + expect(field.aggregatable).toEqual(true); + + const fieldB = getField({ aggregatable: false, scripted: true }); + expect(fieldB.aggregatable).toEqual(true); + + const fieldC = getField({ aggregatable: false, scripted: false }); + expect(fieldC.aggregatable).toEqual(false); + }); + + it('calculates readFromDocValues', () => { + const field = getField({ readFromDocValues: true, scripted: false }); + expect(field.readFromDocValues).toEqual(true); + + const fieldB = getField({ readFromDocValues: false, scripted: false }); + expect(fieldB.readFromDocValues).toEqual(false); + + const fieldC = getField({ readFromDocValues: true, scripted: true }); + expect(fieldC.readFromDocValues).toEqual(false); + }); + + it('calculates sortable', () => { + const field = getField({ name: '_score' }); + expect(field.sortable).toEqual(true); + + const fieldB = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); + expect(fieldB.sortable).toEqual(true); + + const fieldC = getField({ indexed: false }); + expect(fieldC.sortable).toEqual(false); + }); + + it('calculates filterable', () => { + const field = getField({ name: '_id' }); + expect(field.filterable).toEqual(true); + + const fieldB = getField({ scripted: true }); + expect(fieldB.filterable).toEqual(true); + + const fieldC = getField({ indexed: true, type: KBN_FIELD_TYPES.STRING }); + expect(fieldC.filterable).toEqual(true); + + const fieldD = getField({ scripted: false, indexed: false }); + expect(fieldD.filterable).toEqual(false); + }); + + it('exports the property to JSON', () => { + const field = new Field({ fieldFormatMap: { name: {} } } as IndexPattern, fieldValues, false, { + fieldFormats: {} as FieldFormatsStart, + toastNotifications: notificationServiceMock.createStartContract().toasts, + }); + expect(flatten(field)).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/data/public/index_patterns/fields/field.ts b/src/plugins/data/public/index_patterns/fields/field.ts index 12db09bbb846f..625df17d62e0d 100644 --- a/src/plugins/data/public/index_patterns/fields/field.ts +++ b/src/plugins/data/public/index_patterns/fields/field.ts @@ -56,6 +56,7 @@ export class Field implements IFieldType { subType?: IFieldSubType; displayName?: string; indexPattern?: IndexPattern; + readFromDocValues?: boolean; format: any; $$spec: FieldSpec; conflictDescriptions?: Record; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 417d8227b121c..43404c32cb3d4 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -174,7 +174,7 @@ export class IndexPattern implements IIndexPattern { private updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { if (!response.found) { - throw new SavedObjectNotFound(type, this.id, '#/management/kibana/indexPatterns'); + throw new SavedObjectNotFound(type, this.id, 'kibana#/management/kibana/indexPatterns'); } _.forOwn(this.mapping, (fieldMapping: FieldMappingSpec, name: string | undefined) => { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index fd40153e12c06..32abfd2694f16 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -991,6 +991,8 @@ export class IndexPatternField implements IFieldType { // (undocumented) name: string; // (undocumented) + readFromDocValues?: boolean; + // (undocumented) script?: string; // (undocumented) scripted?: boolean; diff --git a/src/plugins/data/public/search/aggs/buckets/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/filters.test.ts new file mode 100644 index 0000000000000..295e740a2a780 --- /dev/null +++ b/src/plugins/data/public/search/aggs/buckets/filters.test.ts @@ -0,0 +1,253 @@ +/* + * 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 { Query } from '../../../../common'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { AggConfigs } from '../agg_configs'; +import { mockAggTypesRegistry } from '../test_helpers'; +import { BUCKET_TYPES } from './bucket_agg_types'; +import { getFiltersBucketAgg, FiltersBucketAggDependencies } from './filters'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { InternalStartServices } from '../../../types'; + +describe('Filters Agg', () => { + let aggTypesDependencies: FiltersBucketAggDependencies; + + beforeEach(() => { + jest.resetAllMocks(); + const { uiSettings } = coreMock.createSetup(); + + aggTypesDependencies = { + uiSettings, + getInternalStartServices: () => + (({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices), + }; + }); + + describe('order agg editor UI', () => { + const getAggConfigs = (params: Record = {}) => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + find: () => field, + }, + } as any; + + const field = { + name: 'field', + indexPattern, + }; + + return new AggConfigs( + indexPattern, + [ + { + id: 'test', + params, + type: BUCKET_TYPES.FILTERS, + }, + ], + { + typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]), + fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, + } + ); + }; + + const generateFilter = (label: string, language: string, query: Query['query']) => ({ + label, + input: { + language, + query, + }, + }); + + describe('using Lucene', () => { + test('works with lucene filters', () => { + const aggConfigs = getAggConfigs({ + filters: [ + generateFilter('a', 'lucene', 'foo'), + generateFilter('b', 'lucene', 'status:200'), + generateFilter('c', 'lucene', 'status:[400 TO 499] AND (foo OR bar)'), + ], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.must)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "query_string": Object { + "query": "foo", + }, + }, + ], + Array [ + Object { + "query_string": Object { + "query": "status:200", + }, + }, + ], + Array [ + Object { + "query_string": Object { + "query": "status:[400 TO 499] AND (foo OR bar)", + }, + }, + ], + ] + `); + }); + }); + + describe('using KQL', () => { + test('works with KQL filters', () => { + const aggConfigs = getAggConfigs({ + filters: [ + generateFilter('a', 'kuery', 'status:200'), + generateFilter('b', 'kuery', 'status > 500 and name:hello'), + ], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.filter)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "field": 200, + }, + }, + ], + }, + }, + ], + Array [ + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "field": Object { + "gt": 500, + }, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "match": Object { + "field": "hello", + }, + }, + ], + }, + }, + ], + }, + }, + ], + ] + `); + }); + + test('works with KQL wildcards', () => { + const aggConfigs = getAggConfigs({ + filters: [generateFilter('a', 'kuery', '*'), generateFilter('b', 'kuery', 'foo*')], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.filter)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "query_string": Object { + "query": "*", + }, + }, + ], + Array [ + Object { + "query_string": Object { + "query": "foo*", + }, + }, + ], + ] + `); + }); + + test('throws with leading wildcards if not allowed', () => { + const aggConfigs = getAggConfigs({ + filters: [generateFilter('a', 'kuery', '*foo*')], + }); + + expect(() => { + aggConfigs.aggs[0].toDsl(); + }).toThrowErrorMatchingInlineSnapshot(` +"Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings. +*foo* +^" +`); + }); + + test('works with leading wildcards if allowed', () => { + aggTypesDependencies.uiSettings.get = (s: any) => + s === 'query:allowLeadingWildcards' ? true : s; + + const aggConfigs = getAggConfigs({ + filters: [generateFilter('a', 'kuery', '*foo*')], + }); + + const { [BUCKET_TYPES.FILTERS]: params } = aggConfigs.aggs[0].toDsl(); + expect(Object.values(params.filters).map((v: any) => v.bool.filter)).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "query_string": Object { + "query": "*foo*", + }, + }, + ], + ] + `); + }); + }); + }); +}); diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index 1b13ff4905c40..f68d7e1552ccb 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -55,7 +55,7 @@ export function registerValueSuggestionsRoute( const config = await config$.pipe(first()).toPromise(); const { field: fieldName, query, boolFilter } = request.body; const { index } = request.params; - const { dataClient } = context.core.elasticsearch; + const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); const autocompleteSearchOptions = { @@ -69,7 +69,7 @@ export function registerValueSuggestionsRoute( const body = await getBody(autocompleteSearchOptions, field || fieldName, query, boolFilter); try { - const result = await dataClient.callAsCurrentUser('search', { index, body }, { signal }); + const result = await client.callAsCurrentUser('search', { index, body }, { signal }); const buckets: any[] = get(result, 'aggregations.suggestions.buckets') || diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 8b9fa28c77165..428e7fef6deea 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -46,7 +46,7 @@ export function registerRoutes(http: HttpServiceSetup) { }, }, async (context, request, response) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, meta_fields: metaFields } = request.query; @@ -105,7 +105,7 @@ export function registerRoutes(http: HttpServiceSetup) { }, }, async (context: RequestHandlerContext, request: any, response: any) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, interval, look_back: lookBack, meta_fields: metaFields } = request.query; diff --git a/src/plugins/data/server/search/routes.test.ts b/src/plugins/data/server/search/routes.test.ts index 6ea0799f790fc..f5e6507d977cd 100644 --- a/src/plugins/data/server/search/routes.test.ts +++ b/src/plugins/data/server/search/routes.test.ts @@ -38,8 +38,9 @@ describe('Search service', () => { const mockContext = { core: { elasticsearch: { - dataClient: {} as ScopedClusterClient, - adminClient: {} as ScopedClusterClient, + legacy: { + client: {} as ScopedClusterClient, + }, }, }, search: { @@ -75,8 +76,9 @@ describe('Search service', () => { const mockContext = { core: { elasticsearch: { - dataClient: {} as ScopedClusterClient, - adminClient: {} as ScopedClusterClient, + legacy: { + client: {} as ScopedClusterClient, + }, }, }, search: { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 2a0164f113097..1c267c32ebc37 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -59,7 +59,7 @@ export class SearchService implements Plugin { core.http.registerRouteHandlerContext<'search'>('search', (context) => { return createApi({ - caller: context.core.elasticsearch.dataClient.callAsCurrentUser, + caller: context.core.elasticsearch.legacy.client.callAsCurrentUser, searchStrategies: this.searchStrategies, }); }); diff --git a/src/plugins/home/server/services/sample_data/routes/install.ts b/src/plugins/home/server/services/sample_data/routes/install.ts index 940162b10385b..2d1a53fbb09dc 100644 --- a/src/plugins/home/server/services/sample_data/routes/install.ts +++ b/src/plugins/home/server/services/sample_data/routes/install.ts @@ -61,7 +61,7 @@ const insertDataIntoIndex = ( bulk.push(insertCmd); bulk.push(updateTimestamps(doc)); }); - const resp = await context.core.elasticsearch.adminClient.callAsCurrentUser('bulk', { + const resp = await context.core.elasticsearch.legacy.client.callAsCurrentUser('bulk', { body: bulk, }); if (resp.errors) { @@ -110,7 +110,7 @@ export function createInstallRoute( // clean up any old installation of dataset try { - await context.core.elasticsearch.dataClient.callAsCurrentUser('indices.delete', { + await context.core.elasticsearch.legacy.client.callAsCurrentUser('indices.delete', { index, }); } catch (err) { @@ -125,7 +125,7 @@ export function createInstallRoute( mappings: { properties: dataIndexConfig.fields }, }, }; - await context.core.elasticsearch.dataClient.callAsCurrentUser( + await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.create', createIndexParams ); diff --git a/src/plugins/home/server/services/sample_data/routes/list.ts b/src/plugins/home/server/services/sample_data/routes/list.ts index 4fb67c6c78840..770b3116b74f1 100644 --- a/src/plugins/home/server/services/sample_data/routes/list.ts +++ b/src/plugins/home/server/services/sample_data/routes/list.ts @@ -47,7 +47,7 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc const dataIndexConfig = sampleDataset.dataIndices[i]; const index = createIndexName(sampleDataset.id, dataIndexConfig.id); try { - const indexExists = await context.core.elasticsearch.dataClient.callAsCurrentUser( + const indexExists = await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.exists', { index } ); @@ -56,9 +56,12 @@ export const createListRoute = (router: IRouter, sampleDatasets: SampleDatasetSc return; } - const { count } = await context.core.elasticsearch.dataClient.callAsCurrentUser('count', { - index, - }); + const { count } = await context.core.elasticsearch.legacy.client.callAsCurrentUser( + 'count', + { + index, + } + ); if (count === 0) { sampleDataset.status = NOT_INSTALLED; return; diff --git a/src/plugins/home/server/services/sample_data/routes/uninstall.ts b/src/plugins/home/server/services/sample_data/routes/uninstall.ts index 64fb2b8b3a547..9bb260460b38a 100644 --- a/src/plugins/home/server/services/sample_data/routes/uninstall.ts +++ b/src/plugins/home/server/services/sample_data/routes/uninstall.ts @@ -39,7 +39,9 @@ export function createUninstallRoute( { core: { elasticsearch: { - dataClient: { callAsCurrentUser }, + legacy: { + client: { callAsCurrentUser }, + }, }, savedObjects: { client: savedObjectsClient }, }, 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 22865f98c2349..87fdf0730c880 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 @@ -37,7 +37,7 @@ import { EuiCallOut, EuiBasicTableColumn, } from '@elastic/eui'; -import { ToastsStart } from 'kibana/public'; +import { HttpFetchError, ToastsStart } from 'kibana/public'; import { toMountPoint } from '../util'; interface Column { @@ -79,6 +79,7 @@ export interface TableListViewState { isDeletingItems: boolean; showDeleteModal: boolean; showLimitError: boolean; + fetchError?: HttpFetchError; filter: string; selectedIds: string[]; totalItems: number; @@ -128,22 +129,33 @@ class TableListView extends React.Component { - const response = await this.props.findItems(filter); - - if (!this._isMounted) { - return; - } - - // We need this check to handle the case where search results come back in a different - // order than they were sent out. Only load results for the most recent search. - // Also, in case filter is empty, items are being pre-sorted alphabetically. - if (filter === this.state.filter) { + try { + const response = await this.props.findItems(filter); + + if (!this._isMounted) { + return; + } + + // We need this check to handle the case where search results come back in a different + // order than they were sent out. Only load results for the most recent search. + // Also, in case filter is empty, items are being pre-sorted alphabetically. + if (filter === this.state.filter) { + this.setState({ + hasInitialFetchReturned: true, + isFetchingItems: false, + items: !filter ? sortBy(response.hits, 'title') : response.hits, + totalItems: response.total, + showLimitError: response.total > this.props.listingLimit, + }); + } + } catch (fetchError) { this.setState({ hasInitialFetchReturned: true, isFetchingItems: false, - items: !filter ? sortBy(response.hits, 'title') : response.hits, - totalItems: response.total, - showLimitError: response.total > this.props.listingLimit, + items: [], + totalItems: 0, + showLimitError: false, + fetchError, }); } }, 300); @@ -152,6 +164,7 @@ class TableListView extends React.Component + + } + color="danger" + iconType="alert" + > +

+ +

+
+ + + ); + } + } + renderNoItemsMessage() { if (this.props.noItemsFragment) { return this.props.noItemsFragment; @@ -431,7 +475,7 @@ class TableListView extends React.Component {this.renderListingLimitWarning()} + {this.renderFetchError()} {this.renderTable()} diff --git a/src/plugins/testbed/server/index.ts b/src/plugins/testbed/server/index.ts index 31f5d433c66ab..21f97259c97f4 100644 --- a/src/plugins/testbed/server/index.ts +++ b/src/plugins/testbed/server/index.ts @@ -64,7 +64,7 @@ class Plugin { router.get( { path: '/requestcontext/elasticsearch', validate: false }, async (context, req, res) => { - const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping'); + const response = await context.core.elasticsearch.legacy.client.callAsInternalUser('ping'); return res.ok({ body: `Elasticsearch: ${response}` }); } ); @@ -84,7 +84,10 @@ class Plugin { return `Some exposed data derived from config: ${configValue.secret}`; }) ), - pingElasticsearch: () => core.elasticsearch.adminClient.callAsInternalUser('ping'), + pingElasticsearch: async () => { + const [coreStart] = await core.getStartServices(); + return coreStart.elasticsearch.legacy.client.callAsInternalUser('ping'); + }, }; } diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index 1efc7c4101518..b8cefc8a07f28 100644 --- a/src/plugins/vis_type_timelion/server/routes/run.ts +++ b/src/plugins/vis_type_timelion/server/routes/run.ts @@ -87,7 +87,7 @@ export function runRoute( allowedGraphiteUrls: configManager.getGraphiteUrls(), esShardTimeout: configManager.getEsShardTimeout(), savedObjectsClient: context.core.savedObjects.client, - esDataClient: () => context.core.elasticsearch.dataClient, + esDataClient: () => context.core.elasticsearch.legacy.client, }); const chainRunner = chainRunnerFn(tlConfig); const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); diff --git a/src/plugins/vis_type_timelion/server/routes/validate_es.ts b/src/plugins/vis_type_timelion/server/routes/validate_es.ts index 511a5852e4641..d5ce80dc151a2 100644 --- a/src/plugins/vis_type_timelion/server/routes/validate_es.ts +++ b/src/plugins/vis_type_timelion/server/routes/validate_es.ts @@ -29,7 +29,7 @@ export function validateEsRoute(router: IRouter) { async function (context, request, response) { const uiSettings = await context.core.uiSettings.client.getAll(); - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const timefield = uiSettings['timelion:es.timefield']; diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index a120b8ed2b0d4..fd20ff8b024b3 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -43,7 +43,7 @@ export async function getFields( payload: {}, pre: { indexPatternsService: new IndexPatternsFetcher( - requestContext.core.elasticsearch.dataClient.callAsCurrentUser + requestContext.core.elasticsearch.legacy.client.callAsCurrentUser ), }, getUiSettingsService: () => requestContext.core.uiSettings.client, @@ -54,7 +54,7 @@ export async function getFields( getCluster: () => { return { callWithRequest: async (req: any, endpoint: string, params: any) => { - return await requestContext.core.elasticsearch.dataClient.callAsCurrentUser( + return await requestContext.core.elasticsearch.legacy.client.callAsCurrentUser( endpoint, params ); diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index 8ad87c9ad7f9b..f697e754a2e00 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -77,7 +77,7 @@ export function getVisData( getCluster: () => { return { callWithRequest: async (req: any, endpoint: string, params: any) => { - return await requestContext.core.elasticsearch.dataClient.callAsCurrentUser( + return await requestContext.core.elasticsearch.legacy.client.callAsCurrentUser( endpoint, params ); diff --git a/tasks/test_jest.js b/tasks/test_jest.js index 7fce3d061d69a..d8f51806e8ddc 100644 --- a/tasks/test_jest.js +++ b/tasks/test_jest.js @@ -33,7 +33,7 @@ module.exports = function (grunt) { function runJest(jestScript) { const serverCmd = { cmd: 'node', - args: [jestScript, '--ci', '--detectOpenHandles'], + args: [jestScript, '--ci'], opts: { stdio: 'inherit' }, }; diff --git a/test/functional/apps/dashboard/dashboard_error_handling.ts b/test/functional/apps/dashboard/dashboard_error_handling.ts new file mode 100644 index 0000000000000..6bd8327a110b9 --- /dev/null +++ b/test/functional/apps/dashboard/dashboard_error_handling.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 { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['dashboard', 'header', 'common']); + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + + /** + * Common test suite for testing exception scenarious within dashboard + */ + describe('dashboard error handling', () => { + before(async () => { + await esArchiver.loadIfNeeded('dashboard/current/kibana'); + await PageObjects.common.navigateToApp('dashboard'); + }); + + // wrapping into own describe to make sure new tab is cleaned up even if test failed + // see: https://github.com/elastic/kibana/pull/67280#discussion_r430528122 + describe('recreate index pattern link works', () => { + let tabsCount = 1; + it('recreate index pattern link works', async () => { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('dashboard with missing index pattern'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const errorEmbeddable = await testSubjects.find('embeddableStackError'); + await (await errorEmbeddable.findByTagName('a')).click(); + await browser.switchTab(1); + tabsCount++; + await testSubjects.existOrFail('createIndexPatternButton'); + }); + + after(async () => { + if (tabsCount > 1) { + await browser.closeCurrentWindow(); + await browser.switchTab(0); + } + }); + }); + }); +} diff --git a/test/functional/apps/dashboard/index.js b/test/functional/apps/dashboard/index.js index 9ae1a2afddcc6..a8d0e03c9421e 100644 --- a/test/functional/apps/dashboard/index.js +++ b/test/functional/apps/dashboard/index.js @@ -57,6 +57,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./data_shared_attributes')); loadTestFile(require.resolve('./embed_mode')); loadTestFile(require.resolve('./dashboard_back_button')); + loadTestFile(require.resolve('./dashboard_error_handling')); // Note: This one must be last because it unloads some data for one of its tests! // No, this isn't ideal, but loading/unloading takes so much time and these are all bunched diff --git a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz index 767d27fb325df..e83e34a2e07fa 100644 Binary files a/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz and b/test/functional/fixtures/es_archiver/dashboard/current/kibana/data.json.gz differ diff --git a/test/functional/services/common/browser.ts b/test/functional/services/common/browser.ts index 46d57f030937b..3f71c16bd3c44 100644 --- a/test/functional/services/common/browser.ts +++ b/test/functional/services/common/browser.ts @@ -409,6 +409,20 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { return await driver.getAllWindowHandles(); } + /** + * Switches driver to specific browser tab by index + * + * @param {string} tabIndex + * @return {Promise} + */ + public async switchTab(tabIndex: number) { + const tabs = await driver.getAllWindowHandles(); + if (tabs.length <= tabIndex) { + throw new Error(`Out of existing tabs bounds`); + } + await driver.switchTo().window(tabs[tabIndex]); + } + /** * Sets a value in local storage for the focused window/frame. * diff --git a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts index 1b9496be36526..a0a66c5a69518 100644 --- a/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_a/server/plugin.ts @@ -34,7 +34,7 @@ export class CorePluginAPlugin implements Plugin { core.http.registerRouteHandlerContext('pluginA', (context) => { return { ping: () => - context.core.elasticsearch.adminClient.callAsInternalUser('ping') as Promise, + context.core.elasticsearch.legacy.client.callAsInternalUser('ping') as Promise, }; }); } diff --git a/test/plugin_functional/plugins/core_plugin_legacy/index.ts b/test/plugin_functional/plugins/core_plugin_legacy/index.ts index fde67a2f9243f..9584927c3cc89 100644 --- a/test/plugin_functional/plugins/core_plugin_legacy/index.ts +++ b/test/plugin_functional/plugins/core_plugin_legacy/index.ts @@ -37,7 +37,7 @@ export default function (kibana: any) { const router = http.createRouter(); router.get({ path: '/api/np-http-in-legacy', validate: false }, async (context, req, res) => { - const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping'); + const response = await context.core.elasticsearch.legacy.client.callAsInternalUser('ping'); return res.ok({ body: `Pong in legacy via new platform: ${response}` }); }); diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 395ebccb8c187..ebc58c5e4e773 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -11,7 +11,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> Running jest tests" cd "$XPACK_DIR" - checks-reporter-with-killswitch "X-Pack Jest" node --max-old-space-size=6144 scripts/jest --ci --verbose --detectOpenHandles + checks-reporter-with-killswitch "X-Pack Jest" node --max-old-space-size=6144 scripts/jest --ci --verbose echo "" echo "" @@ -32,7 +32,7 @@ else # build runtime for canvas echo "NODE_ENV=$NODE_ENV" node ./plugins/canvas/scripts/shareable_runtime - node --max-old-space-size=6144 scripts/jest --ci --verbose --detectOpenHandles --coverage + node --max-old-space-size=6144 scripts/jest --ci --verbose --coverage # rename file in order to be unique one test -f ../target/kibana-coverage/jest/coverage-final.json \ && mv ../target/kibana-coverage/jest/coverage-final.json \ diff --git a/vars/getCheckoutInfo.groovy b/vars/getCheckoutInfo.groovy new file mode 100644 index 0000000000000..7a3a7a9d2eccc --- /dev/null +++ b/vars/getCheckoutInfo.groovy @@ -0,0 +1,41 @@ +def call(branchOverride) { + def repoInfo = [ + branch: branchOverride ?: env.ghprbSourceBranch, + targetBranch: env.ghprbTargetBranch, + ] + + if (repoInfo.branch == null) { + if (!(params.branch_specifier instanceof String)) { + throw new Exception( + "Unable to determine branch automatically, either pass a branch name to getCheckoutInfo() or use the branch_specifier param." + ) + } + + // strip prefix from the branch specifier to make it consistent with ghprbSourceBranch + repoInfo.branch = params.branch_specifier.replaceFirst(/^(refs\/heads\/|origin\/)/, "") + } + + repoInfo.commit = sh( + script: "git rev-parse HEAD", + label: "determining checked out sha", + returnStdout: true + ).trim() + + if (repoInfo.targetBranch) { + sh( + script: "git fetch origin ${repoInfo.targetBranch}", + label: "fetch latest from '${repoInfo.targetBranch}' at origin" + ) + repoInfo.mergeBase = sh( + script: "git merge-base HEAD FETCH_HEAD", + label: "determining merge point with '${repoInfo.targetBranch}' at origin", + returnStdout: true + ).trim() + } + + print "repoInfo: ${repoInfo}" + + return repoInfo +} + +return this diff --git a/vars/slackNotifications.groovy b/vars/slackNotifications.groovy index 0eea6640d0831..c50a99d3e335c 100644 --- a/vars/slackNotifications.groovy +++ b/vars/slackNotifications.groovy @@ -79,18 +79,29 @@ def getDefaultContext() { ].join(' · ')) } +def getStatusIcon() { + def status = buildUtils.getBuildStatus() + if (status == 'UNSTABLE') { + return ':yellow_heart:' + } + + return ':broken_heart:' +} + def sendFailedBuild(Map params = [:]) { def config = [ channel: '#kibana-operations-alerts', - title: ":broken_heart: *<${env.BUILD_URL}|${getDefaultDisplayName()}>*", - message: ":broken_heart: ${getDefaultDisplayName()}", + title: "*<${env.BUILD_URL}|${getDefaultDisplayName()}>*", + message: getDefaultDisplayName(), color: 'danger', icon: ':jenkins:', username: 'Kibana Operations', context: getDefaultContext(), ] + params - def blocks = [markdownBlock(config.title)] + def title = "${getStatusIcon()} ${config.title}" + + def blocks = [markdownBlock(title)] getFailedBuildBlocks().each { blocks << it } blocks << dividerBlock() blocks << config.context diff --git a/vars/workers.groovy b/vars/workers.groovy index b4e4a115f2011..d2cc19787bc5f 100644 --- a/vars/workers.groovy +++ b/vars/workers.groovy @@ -51,33 +51,24 @@ def base(Map params, Closure closure) { } } - def scmVars = [:] + def checkoutInfo = [:] if (config.scm) { // Try to clone from Github up to 8 times, waiting 15 secs between attempts retryWithDelay(8, 15) { - scmVars = checkout scm - - def mergeBase - if (env.ghprbTargetBranch) { - sh( - script: "cd kibana && git fetch origin ${env.ghprbTargetBranch}", - label: "update reference to target branch 'origin/${env.ghprbTargetBranch}'" - ) - mergeBase = sh( - script: "cd kibana && git merge-base HEAD FETCH_HEAD", - label: "determining merge point with target branch 'origin/${env.ghprbTargetBranch}'", - returnStdout: true - ).trim() - } + checkout scm + } - ciStats.reportGitInfo( - env.ghprbSourceBranch ?: scmVars.GIT_LOCAL_BRANCH ?: scmVars.GIT_BRANCH, - scmVars.GIT_COMMIT, - env.ghprbTargetBranch, - mergeBase - ) + dir("kibana") { + checkoutInfo = getCheckoutInfo() } + + ciStats.reportGitInfo( + checkoutInfo.branch, + checkoutInfo.commit, + checkoutInfo.targetBranch, + checkoutInfo.mergeBase + ) } withEnv([ @@ -87,7 +78,7 @@ def base(Map params, Closure closure) { "PR_TARGET_BRANCH=${env.ghprbTargetBranch ?: ''}", "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", "TEST_BROWSER_HEADLESS=1", - "GIT_BRANCH=${scmVars.GIT_BRANCH ?: ''}", + "GIT_BRANCH=${checkoutInfo.branch}", ]) { withCredentials([ string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), diff --git a/x-pack/mocks.ts b/x-pack/mocks.ts new file mode 100644 index 0000000000000..28c589bee4baa --- /dev/null +++ b/x-pack/mocks.ts @@ -0,0 +1,18 @@ +/* + * 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 { coreMock } from '../src/core/server/mocks'; +import { licensingMock } from './plugins/licensing/server/mocks'; + +function createCoreRequestHandlerContextMock() { + return { + core: coreMock.createRequestHandlerContext(), + licensing: { license: licensingMock.createLicense() }, + }; +} + +export const xpackMocks = { + createRequestHandlerContext: createCoreRequestHandlerContextMock, +}; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 8673d992ada98..9f485b1664c89 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -83,7 +83,9 @@ describe('Actions Plugin', () => { client: {}, }, elasticsearch: { - adminClient: jest.fn(), + legacy: { + client: jest.fn(), + }, }, }, } as unknown) as RequestHandlerContext, diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 256ec505cbff2..8c1b21e600ae1 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -334,7 +334,7 @@ export class ActionsPlugin implements Plugin, Plugi savedObjectsClient: savedObjects.getScopedClient(request, { includedHiddenTypes }), actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, - scopedClusterClient: context.core.elasticsearch.adminClient, + scopedClusterClient: context.core.elasticsearch.legacy.client, preconfiguredActions, }); }, diff --git a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts index 6fb3df8446f5c..9c98040ea09bb 100644 --- a/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts +++ b/x-pack/plugins/alerting/server/routes/_mock_handler_arguments.ts @@ -20,13 +20,11 @@ export function mockHandlerArguments( { alertsClient = alertsClientMock.create(), listTypes: listTypesRes = [], - elasticsearch = elasticsearchServiceMock.createSetup(), + esClient = elasticsearchServiceMock.createClusterClient(), }: { alertsClient?: AlertsClientMock; listTypes?: AlertType[]; - elasticsearch?: jest.Mocked<{ - adminClient: jest.Mocked; - }>; + esClient?: jest.Mocked; }, req: unknown, res?: Array> @@ -34,7 +32,7 @@ export function mockHandlerArguments( const listTypes = jest.fn(() => listTypesRes); return [ ({ - core: { elasticsearch }, + core: { elasticsearch: { legacy: { client: esClient } } }, alerting: { listTypes, getAlertsClient() { diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index 7c50fbf561e59..9b1c95c393f56 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -43,16 +43,16 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); await handler(context, req, res); expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - expect(elasticsearch.adminClient.callAsInternalUser.mock.calls[0]).toMatchInlineSnapshot(` + expect(esClient.callAsInternalUser.mock.calls[0]).toMatchInlineSnapshot(` Array [ "transport.request", Object { @@ -72,10 +72,10 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { @@ -96,10 +96,10 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { @@ -120,10 +120,10 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: {} })); + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: {} })); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { @@ -144,12 +144,10 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue( - Promise.resolve({ security: { enabled: true } }) - ); + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue(Promise.resolve({ security: { enabled: true } })); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { @@ -170,12 +168,12 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue( + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue( Promise.resolve({ security: { enabled: true, ssl: {} } }) ); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { @@ -196,12 +194,12 @@ describe('healthRoute', () => { healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; - const elasticsearch = elasticsearchServiceMock.createSetup(); - elasticsearch.adminClient.callAsInternalUser.mockReturnValue( + const esClient = elasticsearchServiceMock.createClusterClient(); + esClient.callAsInternalUser.mockReturnValue( Promise.resolve({ security: { enabled: true, ssl: { http: { enabled: true } } } }) ); - const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + const [context, req, res] = mockHandlerArguments({ esClient }, {}, ['ok']); expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index 98465055e88de..fb4c5e76a02c9 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -49,7 +49,7 @@ export function healthRoute( enabled: isSecurityEnabled = false, ssl: { http: { enabled: isTLSEnabled = false } = {} } = {}, } = {}, - }: XPackUsageSecurity = await context.core.elasticsearch.adminClient + }: XPackUsageSecurity = await context.core.elasticsearch.legacy.client // `transport.request` is potentially unsafe when combined with untrusted user input. // Do not augment with such input. .callAsInternalUser('transport.request', { diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts index 5cc41671f6167..1ef289d74a05e 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/fields.ts @@ -51,7 +51,7 @@ export function createFieldsRoute(service: Service, router: IRouter, baseRoute: } try { - rawFields = await getRawFields(ctx.core.elasticsearch.dataClient, req.body.indexPatterns); + rawFields = await getRawFields(ctx.core.elasticsearch.legacy.client, req.body.indexPatterns); } catch (err) { const indexPatterns = req.body.indexPatterns.join(','); service.logger.warn( diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts index 297009b5a69e8..dcf117aafa6fb 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/indices.ts @@ -53,7 +53,7 @@ export function createIndicesRoute(service: Service, router: IRouter, baseRoute: let aliases: string[] = []; try { - aliases = await getAliasesFromPattern(ctx.core.elasticsearch.dataClient, pattern); + aliases = await getAliasesFromPattern(ctx.core.elasticsearch.legacy.client, pattern); } catch (err) { service.logger.warn( `route ${path} error getting aliases from pattern "${pattern}": ${err.message}` @@ -62,7 +62,7 @@ export function createIndicesRoute(service: Service, router: IRouter, baseRoute: let indices: string[] = []; try { - indices = await getIndicesFromPattern(ctx.core.elasticsearch.dataClient, pattern); + indices = await getIndicesFromPattern(ctx.core.elasticsearch.legacy.client, pattern); } catch (err) { service.logger.warn( `route ${path} error getting indices from pattern "${pattern}": ${err.message}` diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts index 201c82060f386..9af01dc766a99 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/routes/time_series_query.ts @@ -37,7 +37,7 @@ export function createTimeSeriesQueryRoute(service: Service, router: IRouter, ba const result = await service.indexThreshold.timeSeriesQuery({ logger: service.logger, - callCluster: ctx.core.elasticsearch.dataClient.callAsCurrentUser, + callCluster: ctx.core.elasticsearch.legacy.client.callAsCurrentUser, query: req.body, }); diff --git a/x-pack/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/plugins/apm/server/lib/helpers/es_client.ts index 321b9ead2499b..c7a17197ca778 100644 --- a/x-pack/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/plugins/apm/server/lib/helpers/es_client.ts @@ -138,7 +138,7 @@ export function getESClient( const { callAsCurrentUser, callAsInternalUser, - } = context.core.elasticsearch.dataClient; + } = context.core.elasticsearch.legacy.client; async function callEs(operationName: string, params: Record) { const startTime = process.hrtime(); diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index 8602df653c029..e529f4d4ab1ed 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -41,9 +41,11 @@ function getMockRequest() { }, core: { elasticsearch: { - dataClient: { - callAsCurrentUser: jest.fn(), - callAsInternalUser: jest.fn(), + legacy: { + client: { + callAsCurrentUser: jest.fn(), + callAsInternalUser: jest.fn(), + }, }, }, uiSettings: { @@ -60,9 +62,11 @@ function getMockRequest() { } as unknown) as APMRequestHandlerContext & { core: { elasticsearch: { - dataClient: { - callAsCurrentUser: jest.Mock; - callAsInternalUser: jest.Mock; + legacy: { + client: { + callAsCurrentUser: jest.Mock; + callAsInternalUser: jest.Mock; + }; }; }; uiSettings: { @@ -91,7 +95,7 @@ describe('setupRequest', () => { const { client } = await setupRequest(mockContext, mockRequest); await client.search({ index: 'apm-*', body: { foo: 'bar' } } as any); expect( - mockContext.core.elasticsearch.dataClient.callAsCurrentUser + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser ).toHaveBeenCalledWith('search', { index: 'apm-*', body: { @@ -114,7 +118,7 @@ describe('setupRequest', () => { body: { foo: 'bar' }, } as any); expect( - mockContext.core.elasticsearch.dataClient.callAsInternalUser + mockContext.core.elasticsearch.legacy.client.callAsInternalUser ).toHaveBeenCalledWith('search', { index: 'apm-*', body: { @@ -139,7 +143,7 @@ describe('setupRequest', () => { body: { query: { bool: { filter: [{ term: 'someTerm' }] } } }, }); const params = - mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock .calls[0][1]; expect(params.body).toEqual({ query: { @@ -158,7 +162,7 @@ describe('setupRequest', () => { const { client } = await setupRequest(mockContext, mockRequest); await client.search({ index: 'apm-*' }); const params = - mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock .calls[0][1]; expect(params.body).toEqual({ query: { @@ -182,7 +186,7 @@ describe('setupRequest', () => { } ); const params = - mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock .calls[0][1]; expect(params.body).toEqual({ query: { bool: { filter: [{ term: 'someTerm' }] } }, @@ -200,7 +204,7 @@ describe('setupRequest', () => { }, }); const params = - mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock .calls[0][1]; expect(params.body).toEqual({ query: { @@ -224,7 +228,7 @@ describe('setupRequest', () => { await client.search({}); const params = - mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock .calls[0][1]; expect(params.ignore_throttled).toBe(true); }); @@ -240,7 +244,7 @@ describe('setupRequest', () => { await client.search({}); const params = - mockContext.core.elasticsearch.dataClient.callAsCurrentUser.mock + mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock .calls[0][1]; expect(params.ignore_throttled).toBe(false); }); diff --git a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index f2ad9c966e517..6251bd7bd6601 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -38,7 +38,7 @@ export const getDynamicIndexPattern = async ({ const indexPatternsFetcher = new IndexPatternsFetcher( (...rest: Parameters) => - context.core.elasticsearch.adminClient.callAsCurrentUser(...rest) + context.core.elasticsearch.legacy.client.callAsCurrentUser(...rest) ); // Since `getDynamicIndexPattern` is called in setup_request (and thus by every endpoint) diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 501aa538c224b..996bfbd9184d1 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -104,7 +104,7 @@ export const serviceAnnotationsRoute = createRoute(() => ({ serviceName, environment, annotationsClient, - apiCaller: context.core.elasticsearch.dataClient.callAsCurrentUser, + apiCaller: context.core.elasticsearch.legacy.client.callAsCurrentUser, }); }, })); diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx index 426b34cf73b9a..8751d8102ad37 100644 --- a/x-pack/plugins/canvas/public/application.tsx +++ b/x-pack/plugins/canvas/public/application.tsx @@ -18,7 +18,6 @@ import { CanvasStartDeps, CanvasSetupDeps } from './plugin'; // @ts-ignore Untyped local import { App } from './components/app'; import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; -import { initInterpreter } from './lib/run_interpreter'; import { registerLanguage } from './lib/monaco_language_def'; import { SetupRegistries } from './plugin_api'; import { initRegistries, populateRegistries, destroyRegistries } from './registries'; @@ -37,6 +36,7 @@ import { startServices, services } from './services'; import { destroyHistory } from './lib/history_provider'; // @ts-ignore Untyped local import { stopRouter } from './lib/router_provider'; +import { initFunctions } from './functions'; // @ts-ignore Untyped local import { appUnload } from './state/actions/app'; @@ -82,15 +82,25 @@ export const initializeCanvas = async ( registries: SetupRegistries, appUpdater: BehaviorSubject ) => { - startServices(coreSetup, coreStart, setupPlugins, startPlugins, appUpdater); + await startServices(coreSetup, coreStart, setupPlugins, startPlugins, appUpdater); + + // Adding these functions here instead of in plugin.ts. + // Some of these functions have deep dependencies into Canvas, which was bulking up the size + // of our bundle entry point. Moving them here pushes that load to when canvas is actually loaded. + const canvasFunctions = initFunctions({ + timefilter: setupPlugins.data.query.timefilter.timefilter, + prependBasePath: coreSetup.http.basePath.prepend, + typesRegistry: setupPlugins.expressions.__LEGACY.types, + }); + + for (const fn of canvasFunctions) { + services.expressions.getService().registerFunction(fn); + } // Create Store const canvasStore = await createStore(coreSetup, setupPlugins); - // Init Interpreter - initInterpreter(startPlugins.expressions, setupPlugins.expressions).then(() => { - registerLanguage(Object.values(startPlugins.expressions.getFunctions())); - }); + registerLanguage(Object.values(services.expressions.getService().getFunctions())); // Init Registries initRegistries(); diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.ts b/x-pack/plugins/canvas/public/lib/run_interpreter.ts index bd00cad4fcbe7..07c0ca4b1ce15 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.ts +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.ts @@ -6,32 +6,7 @@ import { fromExpression, getType } from '@kbn/interpreter/common'; import { ExpressionValue, ExpressionAstExpression } from 'src/plugins/expressions/public'; -import { notifyService } from '../services'; - -import { CanvasStartDeps, CanvasSetupDeps } from '../plugin'; - -let expressionsStarting: Promise | undefined; - -export const initInterpreter = function ( - expressionsStart: CanvasStartDeps['expressions'], - expressionsSetup: CanvasSetupDeps['expressions'] -) { - expressionsStarting = startExpressions(expressionsStart, expressionsSetup); - - return expressionsStarting; -}; - -async function startExpressions( - expressionsStart: CanvasStartDeps['expressions'], - expressionsSetup: CanvasSetupDeps['expressions'] -) { - await expressionsSetup.__LEGACY.loadLegacyServerFunctionWrappers(); - return expressionsStart; -} - -export const resetInterpreter = function () { - expressionsStarting = undefined; -}; +import { notifyService, expressionsService } from '../services'; interface Options { castToRender?: boolean; @@ -41,12 +16,7 @@ interface Options { * Meant to be a replacement for plugins/interpreter/interpretAST */ export async function interpretAst(ast: ExpressionAstExpression): Promise { - if (!expressionsStarting) { - throw new Error('Interpreter has not been initialized'); - } - - const expressions = await expressionsStarting; - return await expressions.execute(ast).getData(); + return await expressionsService.getService().execute(ast).getData(); } /** @@ -63,14 +33,8 @@ export async function runInterpreter( input: ExpressionValue, options: Options = {} ): Promise { - if (!expressionsStarting) { - throw new Error('Interpreter has not been initialized'); - } - - const expressions = await expressionsStarting; - try { - const renderable = await expressions.execute(ast, input).getData(); + const renderable = await expressionsService.getService().execute(ast, input).getData(); if (getType(renderable) === 'render') { return renderable; diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 1265bfbb69b70..9d2a6b3fdf4f4 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -28,7 +28,6 @@ import { Start as InspectorStart } from '../../../../src/plugins/inspector/publi import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; import { getPluginApi, CanvasApi } from './plugin_api'; -import { initFunctions } from './functions'; import { CanvasSrcPlugin } from '../canvas_plugin_src/plugin'; export { CoreStart, CoreSetup }; @@ -117,14 +116,6 @@ export class CanvasPlugin plugins.home.featureCatalogue.register(featureCatalogueEntry); - // Register core canvas stuff - canvasApi.addFunctions( - initFunctions({ - timefilter: plugins.data.query.timefilter.timefilter, - prependBasePath: core.http.basePath.prepend, - typesRegistry: plugins.expressions.__LEGACY.types, - }) - ); canvasApi.addArgumentUIs(argTypeSpecs); canvasApi.addTransitions(transitions); diff --git a/x-pack/plugins/canvas/public/services/expressions.ts b/x-pack/plugins/canvas/public/services/expressions.ts new file mode 100644 index 0000000000000..16f939a9c97fc --- /dev/null +++ b/x-pack/plugins/canvas/public/services/expressions.ts @@ -0,0 +1,19 @@ +/* + * 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 { CanvasServiceFactory } from '.'; +import { ExpressionsService } from '../../../../../src/plugins/expressions/common'; + +export const expressionsServiceFactory: CanvasServiceFactory = async ( + coreSetup, + coreStart, + setupPlugins, + startPlugins +) => { + await setupPlugins.expressions.__LEGACY.loadLegacyServerFunctionWrappers(); + + return setupPlugins.expressions.fork(); +}; diff --git a/x-pack/plugins/canvas/public/services/index.ts b/x-pack/plugins/canvas/public/services/index.ts index 42176f953c331..a929b4639d3e4 100644 --- a/x-pack/plugins/canvas/public/services/index.ts +++ b/x-pack/plugins/canvas/public/services/index.ts @@ -10,6 +10,7 @@ import { CanvasSetupDeps, CanvasStartDeps } from '../plugin'; import { notifyServiceFactory } from './notify'; import { platformServiceFactory } from './platform'; import { navLinkServiceFactory } from './nav_link'; +import { expressionsServiceFactory } from './expressions'; export type CanvasServiceFactory = ( coreSetup: CoreSetup, @@ -17,7 +18,7 @@ export type CanvasServiceFactory = ( canvasSetupPlugins: CanvasSetupDeps, canvasStartPlugins: CanvasStartDeps, appUpdater: BehaviorSubject -) => Service; +) => Service | Promise; class CanvasServiceProvider { private factory: CanvasServiceFactory; @@ -27,14 +28,14 @@ class CanvasServiceProvider { this.factory = factory; } - start( + async start( coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, canvasStartPlugins: CanvasStartDeps, appUpdater: BehaviorSubject ) { - this.service = this.factory( + this.service = await this.factory( coreSetup, coreStart, canvasSetupPlugins, @@ -59,27 +60,31 @@ class CanvasServiceProvider { export type ServiceFromProvider

= P extends CanvasServiceProvider ? T : never; export const services = { + expressions: new CanvasServiceProvider(expressionsServiceFactory), notify: new CanvasServiceProvider(notifyServiceFactory), platform: new CanvasServiceProvider(platformServiceFactory), navLink: new CanvasServiceProvider(navLinkServiceFactory), }; export interface CanvasServices { + expressions: ServiceFromProvider; notify: ServiceFromProvider; platform: ServiceFromProvider; navLink: ServiceFromProvider; } -export const startServices = ( +export const startServices = async ( coreSetup: CoreSetup, coreStart: CoreStart, canvasSetupPlugins: CanvasSetupDeps, canvasStartPlugins: CanvasStartDeps, appUpdater: BehaviorSubject ) => { - Object.entries(services).forEach(([key, provider]) => + const startPromises = Object.values(services).map((provider) => provider.start(coreSetup, coreStart, canvasSetupPlugins, canvasStartPlugins, appUpdater) ); + + await Promise.all(startPromises); }; export const stopServices = () => { @@ -90,4 +95,5 @@ export const { notify: notifyService, platform: platformService, navLink: navLinkService, + expressions: expressionsService, } = services; diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts index c96856d09256b..c3588957ff68e 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -20,7 +20,7 @@ import { const mockRouteContext = ({ core: { - elasticsearch: { dataClient: elasticsearchServiceMock.createScopedClusterClient() }, + elasticsearch: { legacy: { client: elasticsearchServiceMock.createScopedClusterClient() } }, }, } as unknown) as RequestHandlerContext; @@ -76,7 +76,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockResolvedValueOnce(mockResults); @@ -104,7 +104,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockResolvedValueOnce(mockResults); @@ -132,7 +132,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockResolvedValueOnce(mockResults); @@ -152,7 +152,7 @@ describe('Retrieve ES Fields', () => { }, }); - const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock; callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found')); diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts index e13b19610213e..8f3ced13895f6 100644 --- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts @@ -28,7 +28,7 @@ export function initializeESFieldsRoute(deps: RouteInitializerDeps) { }, }, catchErrorHandler(async (context, request, response) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const { index, fields } = request.query; const config = { diff --git a/x-pack/plugins/file_upload/server/routes/file_upload.js b/x-pack/plugins/file_upload/server/routes/file_upload.js index c7f3e2a621161..3935d4ca5fe8e 100644 --- a/x-pack/plugins/file_upload/server/routes/file_upload.js +++ b/x-pack/plugins/file_upload/server/routes/file_upload.js @@ -85,7 +85,7 @@ const finishValidationAndProcessReq = () => { let resp; try { const validIdReqData = idConditionalValidation(body, boolHasId); - const callWithRequest = con.core.elasticsearch.dataClient.callAsCurrentUser; + const callWithRequest = con.core.elasticsearch.legacy.client.callAsCurrentUser; const { importData: importDataFunc } = importDataProvider(callWithRequest); const { index, settings, mappings, ingestPipeline, data } = validIdReqData; diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index cb4a6a3577915..648010eeeafeb 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -32,7 +32,9 @@ export function registerExploreRoute({ { core: { elasticsearch: { - dataClient: { callAsCurrentUser: callCluster }, + legacy: { + client: { callAsCurrentUser: callCluster }, + }, }, }, }, diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index 6e9fe508af3d3..ffca273d66c71 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -31,7 +31,9 @@ export function registerSearchRoute({ core: { uiSettings: { client: uiSettings }, elasticsearch: { - dataClient: { callAsCurrentUser: callCluster }, + legacy: { + client: { callAsCurrentUser: callCluster }, + }, }, }, }, diff --git a/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts b/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts index 749f5e9ebf8f0..015a2e250bb0e 100644 --- a/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts +++ b/x-pack/plugins/grokdebugger/server/lib/kibana_framework.ts @@ -100,6 +100,6 @@ export class KibanaFramework { options?: any ) { const { elasticsearch } = requestContext.core; - return elasticsearch.dataClient.callAsCurrentUser(endpoint, options); + return elasticsearch.legacy.client.callAsCurrentUser(endpoint, options); } } diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts index 9627f6399eaaf..b8870a83ac5ac 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_add_policy_route.ts @@ -45,7 +45,7 @@ export function registerAddPolicyRoute({ router, license, lib }: RouteDependenci try { await addLifecyclePolicy( - context.core.elasticsearch.dataClient.callAsCurrentUser, + context.core.elasticsearch.legacy.client.callAsCurrentUser, indexName, policyName, alias diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts index 8ec94a8591785..b1bc2264fc529 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_remove_route.ts @@ -37,7 +37,10 @@ export function registerRemoveRoute({ router, license, lib }: RouteDependencies) const { indexNames } = body; try { - await removeLifecycle(context.core.elasticsearch.dataClient.callAsCurrentUser, indexNames); + await removeLifecycle( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + indexNames + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts index 1e2d621cab173..0a5ca1730d170 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/index/register_retry_route.ts @@ -37,7 +37,10 @@ export function registerRetryRoute({ router, license, lib }: RouteDependencies) const { indexNames } = body; try { - await retryLifecycle(context.core.elasticsearch.dataClient.callAsCurrentUser, indexNames); + await retryLifecycle( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + indexNames + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts index 6ff1f147e7ea7..db0352ec83ee0 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_details_route.ts @@ -46,7 +46,9 @@ export function registerDetailsRoute({ router, license, lib }: RouteDependencies const { nodeAttrs } = params; try { - const stats = await fetchNodeStats(context.core.elasticsearch.dataClient.callAsCurrentUser); + const stats = await fetchNodeStats( + context.core.elasticsearch.legacy.client.callAsCurrentUser + ); const okResponse = { body: findMatchingNodes(stats, nodeAttrs) }; return response.ok(okResponse); } catch (e) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts index 73d85c78d3b11..91b542284a041 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/nodes/register_list_route.ts @@ -51,7 +51,9 @@ export function registerListRoute({ router, config, license, lib }: RouteDepende { path: addBasePath('/nodes/list'), validate: false }, license.guardApiRoute(async (context, request, response) => { try { - const stats = await fetchNodeStats(context.core.elasticsearch.dataClient.callAsCurrentUser); + const stats = await fetchNodeStats( + context.core.elasticsearch.legacy.client.callAsCurrentUser + ); const okResponse = { body: convertStatsIntoList(stats, disallowedNodeAttributes) }; return response.ok(okResponse); } catch (e) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts index a9c6bab58fdd9..2eb635e19be48 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts @@ -128,7 +128,11 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies) const { name, phases } = body; try { - await createPolicy(context.core.elasticsearch.dataClient.callAsCurrentUser, name, phases); + await createPolicy( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + name, + phases + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts index e08297f4d7bc4..241668780ea25 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_delete_route.ts @@ -33,7 +33,10 @@ export function registerDeleteRoute({ router, license, lib }: RouteDependencies) const { policyNames } = params; try { - await deletePolicies(context.core.elasticsearch.dataClient.callAsCurrentUser, policyNames); + await deletePolicies( + context.core.elasticsearch.legacy.client.callAsCurrentUser, + policyNames + ); return response.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts index 294b7c4c65cba..d581cc15f3053 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_fetch_route.ts @@ -66,7 +66,7 @@ export function registerFetchRoute({ router, license, lib }: RouteDependencies) license.guardApiRoute(async (context, request, response) => { const query = request.query as typeof querySchema.type; const { withIndices } = query; - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; try { const policiesMap = await fetchPolicies(callAsCurrentUser); diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts index 0da8535f8d4ec..ed05cc270e4c1 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_add_policy_route.ts @@ -60,7 +60,7 @@ export function registerAddPolicyRoute({ router, license, lib }: RouteDependenci try { await updateIndexTemplate( - context.core.elasticsearch.dataClient.callAsCurrentUser, + context.core.elasticsearch.legacy.client.callAsCurrentUser, templateName, policyName, aliasName diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts index 865d4f488e926..546d9674b0956 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/templates/register_fetch_route.ts @@ -66,7 +66,7 @@ export function registerFetchRoute({ router, license, lib }: RouteDependencies) license.guardApiRoute(async (context, request, response) => { try { const templates = await fetchTemplates( - context.core.elasticsearch.dataClient.callAsCurrentUser + context.core.elasticsearch.legacy.client.callAsCurrentUser ); const okResponse = { body: filterAndFormatTemplates(templates) }; return response.ok(okResponse); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts index ec42b2aee45a9..24cb9f07a0150 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_clear_cache_route.ts @@ -26,7 +26,7 @@ export function registerClearCacheRoute({ router, license, lib }: RouteDependenc }; try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.clearCache', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.clearCache', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts index bd243ab3e5de5..1222ebb62bd3e 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_close_route.ts @@ -26,7 +26,7 @@ export function registerCloseRoute({ router, license, lib }: RouteDependencies) }; try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.close', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.close', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts index ffe30b315363a..1f996737f6800 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_delete_route.ts @@ -27,7 +27,7 @@ export function registerDeleteRoute({ router, license, lib }: RouteDependencies) }; try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.delete', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.delete', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts index fee3a0f5278da..479261ea78f73 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_flush_route.ts @@ -27,7 +27,7 @@ export function registerFlushRoute({ router, license, lib }: RouteDependencies) }; try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.flush', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.flush', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts index c39547a3cbd40..f6b7350613aa9 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_forcemerge_route.ts @@ -34,7 +34,7 @@ export function registerForcemergeRoute({ router, license, lib }: RouteDependenc } try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.forcemerge', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.forcemerge', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts index 68bb4b13ef475..9e5870cac1a37 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_freeze_route.ts @@ -26,7 +26,7 @@ export function registerFreezeRoute({ router, license, lib }: RouteDependencies) }; try { - await await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + await await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( 'transport.request', params ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts index 1f5d8ddf529eb..bf9b926887171 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_list_route.ts @@ -14,7 +14,7 @@ export function registerListRoute({ router, license, indexDataEnricher, lib }: R license.guardApiRoute(async (ctx, req, res) => { try { const indices = await fetchIndices( - ctx.core.elasticsearch.dataClient.callAsCurrentUser, + ctx.core.elasticsearch.legacy.client.callAsCurrentUser, indexDataEnricher ); return res.ok({ body: indices }); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts index 28dbae0d8864b..714009bf41168 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_open_route.ts @@ -26,7 +26,7 @@ export function registerOpenRoute({ router, license, lib }: RouteDependencies) { }; try { - await await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.open', params); + await await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.open', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts index 34fee477662e8..34470cfab9c64 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_refresh_route.ts @@ -27,7 +27,7 @@ export function registerRefreshRoute({ router, license, lib }: RouteDependencies }; try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.refresh', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.refresh', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts index 22a9d79439ab0..8d002a56dfd75 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_reload_route.ts @@ -28,7 +28,7 @@ export function registerReloadRoute({ try { const indices = await fetchIndices( - ctx.core.elasticsearch.dataClient.callAsCurrentUser, + ctx.core.elasticsearch.legacy.client.callAsCurrentUser, indexDataEnricher, indexNames ); diff --git a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts index 67c4a3516d1e6..a10014ed0f3e9 100644 --- a/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/indices/register_unfreeze_route.ts @@ -24,7 +24,7 @@ export function registerUnfreezeRoute({ router, license, lib }: RouteDependencie }; try { - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('transport.request', params); + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('transport.request', params); return res.ok(); } catch (e) { if (lib.isEsError(e)) { diff --git a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts index 35121af223207..ee59c8ba7ec9d 100644 --- a/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/mapping/register_mapping_route.ts @@ -30,7 +30,7 @@ export function registerMappingRoute({ router, license, lib }: RouteDependencies }; try { - const hit = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + const hit = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.getMapping', params ); diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts index c31813b4a9f49..6da70e62ae3d7 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_load_route.ts @@ -33,7 +33,7 @@ export function registerLoadRoute({ router, license, lib }: RouteDependencies) { }; try { - const hit = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + const hit = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.getSettings', params ); diff --git a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts index 9ce5ae7f99393..d5faff38dd479 100644 --- a/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/settings/register_update_route.ts @@ -31,7 +31,7 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) }; try { - const response = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + const response = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.putSettings', params ); diff --git a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts index f408fd6584bd5..4d3c750dc031f 100644 --- a/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/stats/register_stats_route.ts @@ -32,7 +32,7 @@ export function registerStatsRoute({ router, license, lib }: RouteDependencies) }; try { - const hit = await ctx.core.elasticsearch.dataClient.callAsCurrentUser( + const hit = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.stats', params ); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts index 22715e457a832..1409fa8af2ceb 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_create_route.ts @@ -18,7 +18,7 @@ export function registerCreateRoute({ router, license, lib }: RouteDependencies) router.put( { path: addBasePath('/templates'), validate: { body: bodySchema } }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const template = req.body as TemplateDeserialized; const { _kbnMeta: { formatVersion }, diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts index 4c8fdd0c2f1c7..3dc31482b4947 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_delete_route.ts @@ -41,7 +41,7 @@ export function registerDeleteRoute({ router, license }: RouteDependencies) { return res.badRequest({ body: 'Only index template version 1 can be deleted.' }); } - await ctx.core.elasticsearch.dataClient.callAsCurrentUser('indices.deleteTemplate', { + await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('indices.deleteTemplate', { name, }); diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts index c0915c72a4b91..b18a8d88d3a4a 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_get_routes.ts @@ -14,7 +14,7 @@ export function registerGetAllRoute({ router, license }: RouteDependencies) { router.get( { path: addBasePath('/templates'), validate: false }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const managedTemplatePrefix = await getManagedTemplatePrefix(callAsCurrentUser); const indexTemplatesByName = await callAsCurrentUser('indices.getTemplate'); @@ -42,7 +42,7 @@ export function registerGetOneRoute({ router, license, lib }: RouteDependencies) }, license.guardApiRoute(async (ctx, req, res) => { const { name } = req.params as typeof paramsSchema.type; - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const { v: version } = req.query as TypeOf; diff --git a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts index 2df72bec9d252..81d7aa1b4978c 100644 --- a/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts +++ b/x-pack/plugins/index_management/server/routes/api/templates/register_update_route.ts @@ -23,7 +23,7 @@ export function registerUpdateRoute({ router, license, lib }: RouteDependencies) validate: { body: bodySchema, params: paramsSchema }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const { name } = req.params as typeof paramsSchema.type; const template = req.body as TemplateDeserialized; const { diff --git a/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts b/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts index f6d61a7177b49..811cf85db8883 100644 --- a/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts +++ b/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts @@ -51,11 +51,18 @@ export type LogEntriesHighlightsRequest = rt.TypeOf) => { rest[1] = rest[1] || {}; rest[1].allowNoIndices = true; - return requestContext.core.elasticsearch.adminClient.callAsCurrentUser(...rest); + return requestContext.core.elasticsearch.legacy.client.callAsCurrentUser(...rest); }); } diff --git a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts index c95032f56987d..be6be88e4b5ec 100644 --- a/x-pack/plugins/infra/server/routes/log_entries/highlights.ts +++ b/x-pack/plugins/infra/server/routes/log_entries/highlights.ts @@ -79,11 +79,21 @@ export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBa return response.ok({ body: logEntriesHighlightsResponseRT.encode({ - data: entriesPerHighlightTerm.map((entries) => ({ - entries, - topCursor: entries[0].cursor, - bottomCursor: entries[entries.length - 1].cursor, - })), + data: entriesPerHighlightTerm.map((entries) => { + if (entries.length > 0) { + return { + entries, + topCursor: entries[0].cursor, + bottomCursor: entries[entries.length - 1].cursor, + }; + } else { + return { + entries, + topCursor: null, + bottomCursor: null, + }; + } + }), }), }); } catch (error) { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx index cbd0b056eaaf1..47d723dd9a1ac 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/components/settings_flyout.tsx @@ -27,37 +27,71 @@ import { EuiText } from '@elastic/eui'; import { useInput, useComboInput, useCore, useGetSettings, sendPutSettings } from '../hooks'; import { useGetOutputs, sendPutOutput } from '../hooks/use_request/outputs'; +const URL_REGEX = /^(https?):\/\/[^\s$.?#].[^\s]*$/gm; + interface Props { onClose: () => void; } -function useSettingsForm(outputId: string | undefined) { +function useSettingsForm(outputId: string | undefined, onSuccess: () => void) { + const [isLoading, setIsloading] = React.useState(false); const { notifications } = useCore(); - const kibanaUrlInput = useInput(); - const elasticsearchUrlInput = useComboInput([]); + const kibanaUrlInput = useInput('', (value) => { + if (!value.match(URL_REGEX)) { + return [ + i18n.translate('xpack.ingestManager.settings.kibanaUrlError', { + defaultMessage: 'Invalid URL', + }), + ]; + } + }); + const elasticsearchUrlInput = useComboInput([], (value) => { + if (value.some((v) => !v.match(URL_REGEX))) { + return [ + i18n.translate('xpack.ingestManager.settings.elasticHostError', { + defaultMessage: 'Invalid URL', + }), + ]; + } + }); return { + isLoading, onSubmit: async () => { + if (!kibanaUrlInput.validate() || !elasticsearchUrlInput.validate()) { + return; + } + try { + setIsloading(true); if (!outputId) { throw new Error('Unable to load outputs'); } - await sendPutOutput(outputId, { + const outputResponse = await sendPutOutput(outputId, { hosts: elasticsearchUrlInput.value, }); - await sendPutSettings({ + if (outputResponse.error) { + throw outputResponse.error; + } + const settingsResponse = await sendPutSettings({ kibana_url: kibanaUrlInput.value, }); + if (settingsResponse.error) { + throw settingsResponse.error; + } + notifications.toasts.addSuccess( + i18n.translate('xpack.ingestManager.settings.success.message', { + defaultMessage: 'Settings saved', + }) + ); + setIsloading(false); + onSuccess(); } catch (error) { + setIsloading(false); notifications.toasts.addError(error, { title: 'Error', }); } - notifications.toasts.addSuccess( - i18n.translate('xpack.ingestManager.settings.success.message', { - defaultMessage: 'Settings saved', - }) - ); }, inputs: { kibanaUrl: kibanaUrlInput, @@ -72,7 +106,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { const settings = settingsRequest?.data?.item; const outputsRequest = useGetOutputs(); const output = outputsRequest.data?.items?.[0]; - const { inputs, onSubmit } = useSettingsForm(output?.id); + const { inputs, onSubmit, isLoading } = useSettingsForm(output?.id, onClose); useEffect(() => { if (output) { @@ -185,6 +219,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { label={i18n.translate('xpack.ingestManager.settings.kibanaUrlLabel', { defaultMessage: 'Kibana URL', })} + {...inputs.kibanaUrl.formRowProps} > @@ -195,6 +230,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { label={i18n.translate('xpack.ingestManager.settings.elasticsearchUrlLabel', { defaultMessage: 'Elasticsearch URL', })} + {...inputs.elasticsearchUrl.formRowProps} > @@ -226,7 +262,7 @@ export const SettingFlyout: React.FunctionComponent = ({ onClose }) => { - + string[] | undefined) { const [value, setValue] = React.useState(defaultValue); + const [errors, setErrors] = React.useState(); + + const onChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + setValue(newValue); + if (errors && validate && validate(newValue) === undefined) { + setErrors(undefined); + } + }; + + const isInvalid = errors !== undefined; return { value, + errors, props: { - onChange: (e: React.ChangeEvent) => { - setValue(e.target.value); - }, + onChange, value, + isInvalid, + }, + formRowProps: { + error: errors, + isInvalid, }, clear: () => { setValue(''); }, + validate: () => { + if (validate) { + const newErrors = validate(value); + setErrors(newErrors); + return newErrors === undefined; + } + + return true; + }, setValue, }; } -export function useComboInput(defaultValue = []) { +export function useComboInput( + defaultValue = [], + validate?: (value: string[]) => string[] | undefined +) { const [value, setValue] = React.useState(defaultValue); + const [errors, setErrors] = React.useState(); + + const isInvalid = errors !== undefined; return { props: { @@ -33,14 +63,33 @@ export function useComboInput(defaultValue = []) { onCreateOption: (newVal: any) => { setValue([...value, newVal]); }, - onChange: (newVals: any[]) => { - setValue(newVals.map((val) => val.label)); + onChange: (newSelectedOptions: any[]) => { + const newValues = newSelectedOptions.map((option) => option.label); + setValue(newValues); + if (errors && validate && validate(newValues) === undefined) { + setErrors(undefined); + } }, + isInvalid, + }, + formRowProps: { + error: errors, + isInvalid, }, value, clear: () => { setValue([]); }, setValue, + validate: () => { + if (validate) { + const newErrors = validate(value); + setErrors(newErrors); + + return newErrors === undefined; + } + + return true; + }, }; } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx index 349ebe1151c80..f746fadc4b0a3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/components/create_config.tsx @@ -32,7 +32,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ onClos const [agentConfig, setAgentConfig] = useState({ name: '', description: '', - namespace: '', + namespace: 'default', is_default: undefined, monitoring_enabled: ['logs', 'metrics'], }); @@ -102,6 +102,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ onClos setIsLoading(true); try { const { data, error } = await createAgentConfig(); + setIsLoading(false); if (data?.success) { notifications.toasts.addSuccess( i18n.translate( @@ -112,6 +113,7 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ onClos } ) ); + onClose(); } else { notifications.toasts.addDanger( error @@ -125,14 +127,13 @@ export const CreateAgentConfigFlyout: React.FunctionComponent = ({ onClos ); } } catch (e) { + setIsLoading(false); notifications.toasts.addDanger( i18n.translate('xpack.ingestManager.createAgentConfig.errorNotificationTitle', { defaultMessage: 'Unable to create agent config', }) ); } - setIsLoading(false); - onClose(); }} > { - const callCluster = context.core.elasticsearch.dataClient.callAsCurrentUser; + const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; try { // Get stats (size on disk) of all potentially matching indices diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts index 8f07e3ed1de02..09daec3370400 100644 --- a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts @@ -74,7 +74,7 @@ export const createDatasourceHandler: RequestHandler< TypeOf > = async (context, request, response) => { const soClient = context.core.savedObjects.client; - const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; + const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; const newData = { ...request.body }; try { diff --git a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts index fd3a9c520b90a..eaf0e1a104b3e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/epm/handlers.ts @@ -124,7 +124,7 @@ export const installPackageHandler: RequestHandler = async (context, request, response) => { try { const soClient = context.core.savedObjects.client; - const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; + const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; await setupIngestManager(soClient, callCluster); await setupFleet(soClient, callCluster, { forceRecreate: request.body?.forceRecreate ?? false, @@ -74,7 +74,7 @@ export const createFleetSetupHandler: RequestHandler< export const ingestManagerSetupHandler: RequestHandler = async (context, request, response) => { const soClient = context.core.savedObjects.client; - const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; + const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; const logger = appContextService.getLogger(); try { await setupIngestManager(soClient, callCluster); diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts index ae7124d2b6cbc..2b0bce99c5bda 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts @@ -9,7 +9,7 @@ import { AgentConfigStatus } from '../../../common'; const AgentConfigBaseSchema = { name: schema.string(), - namespace: schema.maybe(schema.string()), + namespace: schema.string({ minLength: 1 }), description: schema.maybe(schema.string()), monitoring_enabled: schema.maybe( schema.arrayOf(schema.oneOf([schema.literal('logs'), schema.literal('metrics')])) diff --git a/x-pack/plugins/ingest_manager/server/types/models/datasource.ts b/x-pack/plugins/ingest_manager/server/types/models/datasource.ts index e71016560f60c..3bca6d20d96a2 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/datasource.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/datasource.ts @@ -17,7 +17,7 @@ const ConfigRecordSchema = schema.recordOf( const DatasourceBaseSchema = { name: schema.string(), description: schema.maybe(schema.string()), - namespace: schema.maybe(schema.string()), + namespace: schema.string({ minLength: 1 }), config_id: schema.string(), enabled: schema.boolean(), package: schema.maybe( diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts index 79a7c444dacdb..315923fd9f401 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/output.ts @@ -18,7 +18,7 @@ export const PutOutputRequestSchema = { outputId: schema.string(), }), body: schema.object({ - hosts: schema.maybe(schema.arrayOf(schema.string())), + hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), ca_sha256: schema.maybe(schema.string()), }), }; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts index 8b7500e4a9bd9..f6e5fcbba7976 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/settings.ts @@ -11,7 +11,7 @@ export const PutSettingsRequestSchema = { body: schema.object({ agent_auto_upgrade: schema.maybe(schema.boolean()), package_auto_upgrade: schema.maybe(schema.boolean()), - kibana_url: schema.maybe(schema.string()), + kibana_url: schema.maybe(schema.uri({ scheme: ['http', 'https'] })), kibana_ca_sha256: schema.maybe(schema.string()), }), }; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index d2be8c0e3e0cf..c1ab3852ee784 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -29,7 +29,7 @@ export const registerCreateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const pipeline = req.body as Pipeline; const { name, description, processors, version, on_failure } = pipeline; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts index 8ef9387010eb6..a356019e379e9 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts @@ -21,7 +21,7 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const { names } = req.params; const pipelineNames = names.split(','); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts index ec92262014272..076841d6b0b97 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts @@ -22,7 +22,7 @@ export const registerGetRoutes = ({ router.get( { path: API_BASE_PATH, validate: false }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; try { const pipelines = await callAsCurrentUser('ingest.getPipeline'); @@ -56,7 +56,7 @@ export const registerGetRoutes = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const { name } = req.params; try { diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 684ad927b95d4..69cba215beafd 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -36,12 +36,14 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend const { core: { - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client }, + }, }, } = ctx; try { - const { has_all_requested: hasAllPrivileges, cluster } = await dataClient.callAsCurrentUser( + const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( 'transport.request', { path: '/_security/user/_has_privileges', diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts index 0b7e5d886a936..5f509cbe5e412 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts @@ -28,7 +28,7 @@ export const registerSimulateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const { pipeline, documents, verbose } = req.body; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts index 3bf2b0b15a50a..214b293a43c6c 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts @@ -29,7 +29,7 @@ export const registerUpdateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.dataClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; const { name } = req.params; const { description, processors, version, on_failure } = req.body; diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 4edd0d1fa68e8..461406591d432 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -117,7 +117,7 @@ async function fetchFieldExistence({ fromDate, toDate, dslQuery, - client: context.core.elasticsearch.dataClient, + client: context.core.elasticsearch.legacy.client, index: indexPatternTitle, timeFieldName: timeFieldName || indexPattern.attributes.timeFieldName, fields, @@ -131,7 +131,7 @@ async function fetchFieldExistence({ async function fetchIndexPatternDefinition(indexPatternId: string, context: RequestHandlerContext) { const savedObjectsClient = context.core.savedObjects.client; - const requestClient = context.core.elasticsearch.dataClient; + const requestClient = context.core.elasticsearch.legacy.client; const indexPattern = await savedObjectsClient.get( 'index-pattern', indexPatternId diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index d7fcffa034a2a..20d3e2b4164ca 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -42,7 +42,7 @@ export async function initFieldsRoute(setup: CoreSetup) { }, }, async (context, req, res) => { - const requestClient = context.core.elasticsearch.dataClient; + const requestClient = context.core.elasticsearch.legacy.client; const { fromDate, toDate, timeFieldName, field, dslQuery } = req.body; try { diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts index 0f426764f68ee..d531059dd8ae6 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_license_route.ts @@ -21,7 +21,7 @@ export function registerLicenseRoute({ router, plugins: { licensing } }: RouteDe }, }, async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.adminClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; try { return res.ok({ body: await putLicense({ diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts index 7aa3c4733acfd..32c977b31a3c4 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_permissions_route.ts @@ -13,7 +13,7 @@ export function registerPermissionsRoute({ config: { isSecurityEnabled }, }: RouteDependencies) { router.post({ path: addBasePath('/permissions'), validate: false }, async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.adminClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; try { return res.ok({ diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts b/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts index ebfa283872e60..725af0ab8f6db 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_start_basic_route.ts @@ -16,7 +16,7 @@ export function registerStartBasicRoute({ router, plugins: { licensing } }: Rout validate: { query: schema.object({ acknowledge: schema.string() }) }, }, async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.adminClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; try { return res.ok({ body: await startBasic({ diff --git a/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts b/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts index e418c390aaab6..a1e21a255c8cb 100644 --- a/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts +++ b/x-pack/plugins/license_management/server/routes/api/license/register_start_trial_routes.ts @@ -10,7 +10,7 @@ import { addBasePath } from '../../helpers'; export function registerStartTrialRoutes({ router, plugins: { licensing } }: RouteDependencies) { router.get({ path: addBasePath('/start_trial'), validate: false }, async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.adminClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; try { return res.ok({ body: await canStartTrial(callAsCurrentUser) }); } catch (e) { @@ -19,7 +19,7 @@ export function registerStartTrialRoutes({ router, plugins: { licensing } }: Rou }); router.post({ path: addBasePath('/start_trial'), validate: false }, async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.adminClient; + const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; try { return res.ok({ body: await startTrial({ callAsCurrentUser, licensing }), diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index dbda144239927..9d9864886fd4e 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -87,7 +87,9 @@ export class ListPlugin core: { savedObjects: { client: savedObjectsClient }, elasticsearch: { - dataClient: { callAsCurrentUser: callCluster }, + legacy: { + client: { callAsCurrentUser: callCluster }, + }, }, }, } = context; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts index 5e1ea68533f18..e420087628bc8 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.d.ts @@ -30,6 +30,7 @@ export interface IVectorLayer extends ILayer { getJoins(): IJoin[]; getValidJoins(): IJoin[]; getSource(): IVectorSource; + getStyle(): IVectorStyle; } export class VectorLayer extends AbstractLayer implements IVectorLayer { @@ -73,4 +74,5 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { _setMbPointsProperties(mbMap: unknown, mvtSourceLayer?: string): void; _setMbLinePolygonProperties(mbMap: unknown, mvtSourceLayer?: string): void; getSource(): IVectorSource; + getStyle(): IVectorStyle; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx index 4e75ae8823385..84bdee2a64bd8 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/clusters_layer_wizard.tsx @@ -27,7 +27,6 @@ import { VECTOR_STYLES, STYLE_TYPE, } from '../../../../common/constants'; -// @ts-ignore import { COLOR_GRADIENTS } from '../../styles/color_utils'; export const clustersLayerWizardConfig: LayerWizard = { diff --git a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx index bda1a6650c48a..8d7bf0d2af661 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_pew_pew_source/point_2_point_layer_wizard.tsx @@ -17,7 +17,6 @@ import { VECTOR_STYLES, STYLE_TYPE, } from '../../../../common/constants'; -// @ts-ignore import { COLOR_GRADIENTS } from '../../styles/color_utils'; // @ts-ignore import { CreateSourceEditor } from './create_source_editor'; diff --git a/x-pack/plugins/maps/public/classes/styles/_index.scss b/x-pack/plugins/maps/public/classes/styles/_index.scss index a1c4c297a3ac1..3ee713ffc1a02 100644 --- a/x-pack/plugins/maps/public/classes/styles/_index.scss +++ b/x-pack/plugins/maps/public/classes/styles/_index.scss @@ -2,3 +2,5 @@ @import 'vector/components/style_prop_editor'; @import 'vector/components/color/color_stops'; @import 'vector/components/symbol/icon_select'; +@import 'vector/components/legend/category'; +@import 'vector/components/legend/vector_legend'; diff --git a/x-pack/plugins/maps/public/classes/styles/color_utils.test.js b/x-pack/plugins/maps/public/classes/styles/color_utils.test.ts similarity index 91% rename from x-pack/plugins/maps/public/classes/styles/color_utils.test.js rename to x-pack/plugins/maps/public/classes/styles/color_utils.test.ts index 9a5ece01d5206..ed7cafd53a6fc 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_utils.test.js +++ b/x-pack/plugins/maps/public/classes/styles/color_utils.test.ts @@ -3,11 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { COLOR_GRADIENTS, getColorRampCenterColor, - getOrdinalColorRampStops, + getOrdinalMbColorRampStops, getHexColorRangeStrings, getLinearGradient, getRGBColorRangeStrings, @@ -25,7 +24,7 @@ describe('COLOR_GRADIENTS', () => { describe('getRGBColorRangeStrings', () => { it('Should create RGB color ramp', () => { - expect(getRGBColorRangeStrings('Blues')).toEqual([ + expect(getRGBColorRangeStrings('Blues', 8)).toEqual([ 'rgb(247,250,255)', 'rgb(221,234,247)', 'rgb(197,218,238)', @@ -61,7 +60,7 @@ describe('getColorRampCenterColor', () => { describe('getColorRampStops', () => { it('Should create color stops for custom range', () => { - expect(getOrdinalColorRampStops('Blues', 0, 1000)).toEqual([ + expect(getOrdinalMbColorRampStops('Blues', 0, 1000, 8)).toEqual([ 0, '#f7faff', 125, @@ -82,7 +81,7 @@ describe('getColorRampStops', () => { }); it('Should snap to end of color stops for identical range', () => { - expect(getOrdinalColorRampStops('Blues', 23, 23)).toEqual([23, '#072f6b']); + expect(getOrdinalMbColorRampStops('Blues', 23, 23, 8)).toEqual([23, '#072f6b']); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/color_utils.js b/x-pack/plugins/maps/public/classes/styles/color_utils.tsx similarity index 50% rename from x-pack/plugins/maps/public/classes/styles/color_utils.js rename to x-pack/plugins/maps/public/classes/styles/color_utils.tsx index 2df743375a538..116e03096b0f5 100644 --- a/x-pack/plugins/maps/public/classes/styles/color_utils.js +++ b/x-pack/plugins/maps/public/classes/styles/color_utils.tsx @@ -7,73 +7,85 @@ import React from 'react'; import tinycolor from 'tinycolor2'; import chroma from 'chroma-js'; +// @ts-ignore import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { ColorGradient } from './components/color_gradient'; -import { vislibColorMaps } from '../../../../../../src/plugins/charts/public'; +import { RawColorSchema, vislibColorMaps } from '../../../../../../src/plugins/charts/public'; -const GRADIENT_INTERVALS = 8; +export const GRADIENT_INTERVALS = 8; -export const DEFAULT_FILL_COLORS = euiPaletteColorBlind(); -export const DEFAULT_LINE_COLORS = [ - ...DEFAULT_FILL_COLORS.map((color) => tinycolor(color).darken().toHexString()), +export const DEFAULT_FILL_COLORS: string[] = euiPaletteColorBlind(); +export const DEFAULT_LINE_COLORS: string[] = [ + ...DEFAULT_FILL_COLORS.map((color: string) => tinycolor(color).darken().toHexString()), // Explicitly add black & white as border color options '#000', '#FFF', ]; -function getLegendColors(colorRamp, numLegendColors = 4) { +function getRGBColors(colorRamp: Array<[number, number[]]>, numLegendColors: number = 4): string[] { const colors = []; - colors[0] = getColor(colorRamp, 0); + colors[0] = getRGBColor(colorRamp, 0); for (let i = 1; i < numLegendColors - 1; i++) { - colors[i] = getColor(colorRamp, Math.floor((colorRamp.length * i) / numLegendColors)); + colors[i] = getRGBColor(colorRamp, Math.floor((colorRamp.length * i) / numLegendColors)); } - colors[numLegendColors - 1] = getColor(colorRamp, colorRamp.length - 1); + colors[numLegendColors - 1] = getRGBColor(colorRamp, colorRamp.length - 1); return colors; } -function getColor(colorRamp, i) { - const color = colorRamp[i][1]; - const red = Math.floor(color[0] * 255); - const green = Math.floor(color[1] * 255); - const blue = Math.floor(color[2] * 255); +function getRGBColor(colorRamp: Array<[number, number[]]>, i: number): string { + const rgbArray = colorRamp[i][1]; + const red = Math.floor(rgbArray[0] * 255); + const green = Math.floor(rgbArray[1] * 255); + const blue = Math.floor(rgbArray[2] * 255); return `rgb(${red},${green},${blue})`; } -function getColorRamp(colorRampName) { - const colorRamp = vislibColorMaps[colorRampName]; - if (!colorRamp) { +function getColorSchema(colorRampName: string): RawColorSchema { + const colorSchema = vislibColorMaps[colorRampName]; + if (!colorSchema) { throw new Error( `${colorRampName} not found. Expected one of following values: ${Object.keys( vislibColorMaps )}` ); } - return colorRamp; + return colorSchema; } -export function getRGBColorRangeStrings(colorRampName, numberColors = GRADIENT_INTERVALS) { - const colorRamp = getColorRamp(colorRampName); - return getLegendColors(colorRamp.value, numberColors); +export function getRGBColorRangeStrings( + colorRampName: string, + numberColors: number = GRADIENT_INTERVALS +): string[] { + const colorSchema = getColorSchema(colorRampName); + return getRGBColors(colorSchema.value, numberColors); } -export function getHexColorRangeStrings(colorRampName, numberColors = GRADIENT_INTERVALS) { +export function getHexColorRangeStrings( + colorRampName: string, + numberColors: number = GRADIENT_INTERVALS +): string[] { return getRGBColorRangeStrings(colorRampName, numberColors).map((rgbColor) => chroma(rgbColor).hex() ); } -export function getColorRampCenterColor(colorRampName) { +export function getColorRampCenterColor(colorRampName: string): string | null { if (!colorRampName) { return null; } - const colorRamp = getColorRamp(colorRampName); - const centerIndex = Math.floor(colorRamp.value.length / 2); - return getColor(colorRamp.value, centerIndex); + const colorSchema = getColorSchema(colorRampName); + const centerIndex = Math.floor(colorSchema.value.length / 2); + return getRGBColor(colorSchema.value, centerIndex); } // Returns an array of color stops // [ stop_input_1: number, stop_output_1: color, stop_input_n: number, stop_output_n: color ] -export function getOrdinalColorRampStops(colorRampName, min, max) { +export function getOrdinalMbColorRampStops( + colorRampName: string, + min: number, + max: number, + numberColors: number +): Array | null { if (!colorRampName) { return null; } @@ -82,17 +94,20 @@ export function getOrdinalColorRampStops(colorRampName, min, max) { return null; } - const hexColors = getHexColorRangeStrings(colorRampName, GRADIENT_INTERVALS); + const hexColors = getHexColorRangeStrings(colorRampName, numberColors); if (max === min) { - //just return single stop value + // just return single stop value return [max, hexColors[hexColors.length - 1]]; } const delta = max - min; - return hexColors.reduce((accu, stopColor, idx, srcArr) => { - const stopNumber = min + (delta * idx) / srcArr.length; - return [...accu, stopNumber, stopColor]; - }, []); + return hexColors.reduce( + (accu: Array, stopColor: string, idx: number, srcArr: string[]) => { + const stopNumber = min + (delta * idx) / srcArr.length; + return [...accu, stopNumber, stopColor]; + }, + [] + ); } export const COLOR_GRADIENTS = Object.keys(vislibColorMaps).map((colorRampName) => ({ @@ -102,7 +117,7 @@ export const COLOR_GRADIENTS = Object.keys(vislibColorMaps).map((colorRampName) export const COLOR_RAMP_NAMES = Object.keys(vislibColorMaps); -export function getLinearGradient(colorStrings) { +export function getLinearGradient(colorStrings: string[]): string { const intervals = colorStrings.length; let linearGradient = `linear-gradient(to right, ${colorStrings[0]} 0%,`; for (let i = 1; i < intervals - 1; i++) { @@ -112,7 +127,12 @@ export function getLinearGradient(colorStrings) { return `${linearGradient} ${colorStrings[colorStrings.length - 1]} 100%)`; } -const COLOR_PALETTES_CONFIGS = [ +export interface ColorPalette { + id: string; + colors: string[]; +} + +const COLOR_PALETTES_CONFIGS: ColorPalette[] = [ { id: 'palette_0', colors: euiPaletteColorBlind(), @@ -127,14 +147,14 @@ const COLOR_PALETTES_CONFIGS = [ }, ]; -export function getColorPalette(paletteId) { - const palette = COLOR_PALETTES_CONFIGS.find((palette) => palette.id === paletteId); +export function getColorPalette(paletteId: string): string[] | null { + const palette = COLOR_PALETTES_CONFIGS.find(({ id }: ColorPalette) => id === paletteId); return palette ? palette.colors : null; } export const COLOR_PALETTES = COLOR_PALETTES_CONFIGS.map((palette) => { const paletteDisplay = palette.colors.map((color) => { - const style = { + const style: React.CSSProperties = { backgroundColor: color, width: `${100 / palette.colors.length}%`, position: 'relative', diff --git a/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js b/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js deleted file mode 100644 index 8772f33b76fd7..0000000000000 --- a/x-pack/plugins/maps/public/classes/styles/components/color_gradient.js +++ /dev/null @@ -1,20 +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 { COLOR_RAMP_NAMES, getRGBColorRangeStrings, getLinearGradient } from '../color_utils'; -import classNames from 'classnames'; - -export const ColorGradient = ({ colorRamp, colorRampName, className }) => { - if (!colorRamp && (!colorRampName || !COLOR_RAMP_NAMES.includes(colorRampName))) { - return null; - } - - const classes = classNames('mapColorGradient', className); - const rgbColorStrings = colorRampName ? getRGBColorRangeStrings(colorRampName) : colorRamp; - const background = getLinearGradient(rgbColorStrings); - return

; -}; diff --git a/x-pack/plugins/maps/public/classes/styles/components/color_gradient.tsx b/x-pack/plugins/maps/public/classes/styles/components/color_gradient.tsx new file mode 100644 index 0000000000000..b29146062e46d --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/components/color_gradient.tsx @@ -0,0 +1,30 @@ +/* + * 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 { + COLOR_RAMP_NAMES, + GRADIENT_INTERVALS, + getRGBColorRangeStrings, + getLinearGradient, +} from '../color_utils'; + +interface Props { + colorRamp?: string[]; + colorRampName?: string; +} + +export const ColorGradient = ({ colorRamp, colorRampName }: Props) => { + if (!colorRamp && (!colorRampName || !COLOR_RAMP_NAMES.includes(colorRampName))) { + return null; + } + + const rgbColorStrings = colorRampName + ? getRGBColorRangeStrings(colorRampName, GRADIENT_INTERVALS) + : colorRamp!; + const background = getLinearGradient(rgbColorStrings); + return
; +}; diff --git a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js index 3eb34ec1406d2..4a43cc24e2c07 100644 --- a/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js +++ b/x-pack/plugins/maps/public/classes/styles/components/ranged_style_legend_row.js @@ -7,19 +7,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiToolTip } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel, fieldLabel }) { return (
- - {header} - - - {minLabel} - - @@ -29,6 +22,14 @@ export function RangedStyleLegendRow({ header, minLabel, maxLabel, propertyLabel + + {header} + + + + {minLabel} + + {maxLabel} diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap b/x-pack/plugins/maps/public/classes/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/classes/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap rename to x-pack/plugins/maps/public/classes/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.tsx.snap diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_constants.js b/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_constants.ts similarity index 100% rename from x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_constants.js rename to x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_constants.ts diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.js b/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.tsx similarity index 100% rename from x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.js rename to x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.test.tsx diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.js b/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.tsx similarity index 85% rename from x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.js rename to x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.tsx index 6d38a7985269e..d15fdbd79de75 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.js +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/components/heatmap_style_editor.tsx @@ -15,8 +15,13 @@ import { HEATMAP_COLOR_RAMP_LABEL, } from './heatmap_constants'; -export function HeatmapStyleEditor({ colorRampName, onHeatmapColorChange }) { - const onColorRampChange = (selectedColorRampName) => { +interface Props { + colorRampName: string; + onHeatmapColorChange: ({ colorRampName }: { colorRampName: string }) => void; +} + +export function HeatmapStyleEditor({ colorRampName, onHeatmapColorChange }: Props) { + const onColorRampChange = (selectedColorRampName: string) => { onHeatmapColorChange({ colorRampName: selectedColorRampName, }); diff --git a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js index 1fa24943c5e51..5f920d0ba52d3 100644 --- a/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js +++ b/x-pack/plugins/maps/public/classes/styles/heatmap/heatmap_style.js @@ -10,7 +10,7 @@ import { HeatmapStyleEditor } from './components/heatmap_style_editor'; import { HeatmapLegend } from './components/legend/heatmap_legend'; import { DEFAULT_HEATMAP_COLOR_RAMP_NAME } from './components/heatmap_constants'; import { LAYER_STYLE_TYPE, GRID_RESOLUTION } from '../../../../common/constants'; -import { getOrdinalColorRampStops } from '../color_utils'; +import { getOrdinalMbColorRampStops, GRADIENT_INTERVALS } from '../color_utils'; import { i18n } from '@kbn/i18n'; import { EuiIcon } from '@elastic/eui'; @@ -85,7 +85,13 @@ export class HeatmapStyle extends AbstractStyle { const { colorRampName } = this._descriptor; if (colorRampName && colorRampName !== DEFAULT_HEATMAP_COLOR_RAMP_NAME) { - const colorStops = getOrdinalColorRampStops(colorRampName, MIN_RANGE, MAX_RANGE); + const colorStops = getOrdinalMbColorRampStops( + colorRampName, + MIN_RANGE, + MAX_RANGE, + GRADIENT_INTERVALS + ); + // TODO handle null mbMap.setPaintProperty(layerId, 'heatmap-color', [ 'interpolate', ['linear'], diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss new file mode 100644 index 0000000000000..aff843c3ed120 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_category.scss @@ -0,0 +1,3 @@ +.mapLegendIconPreview { + width: $euiSizeL; +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss new file mode 100644 index 0000000000000..d260f6effb2cb --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/_vector_legend.scss @@ -0,0 +1,5 @@ +.vectorStyleLegendSpacer { + &:not(:last-child) { + margin-bottom: $euiSizeS; + } +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js new file mode 100644 index 0000000000000..7e8e6896ef9ce --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/breaked_legend.js @@ -0,0 +1,82 @@ +/* + * 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 _ from 'lodash'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui'; +import { Category } from './category'; +const EMPTY_VALUE = ''; + +export class BreakedLegend extends React.Component { + state = { + label: EMPTY_VALUE, + }; + + componentDidMount() { + this._isMounted = true; + this._loadParams(); + } + + componentDidUpdate() { + this._loadParams(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async _loadParams() { + const label = await this.props.style.getField().getLabel(); + const newState = { label }; + if (this._isMounted && !_.isEqual(this.state, newState)) { + this.setState(newState); + } + } + + render() { + if (this.state.label === EMPTY_VALUE) { + return null; + } + + const categories = this.props.breaks.map((brk, index) => { + return ( + + + + ); + }); + + return ( +
+ + + + + + {this.state.label} + + + + + + + {categories} + +
+ ); + } +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js index b0f397b6375ad..cfdbd728c2217 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/category.js @@ -31,13 +31,13 @@ export function Category({ styleName, label, color, isLinesOnly, isPointsOnly, s } return ( - - - - {label} - - {renderIcon()} - - + + + {renderIcon()} + + + {label} + + ); } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/extract_color_from_style_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/extract_color_from_style_property.ts index 71f77bc313191..dadb3f201fa33 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/components/legend/extract_color_from_style_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/extract_color_from_style_property.ts @@ -43,7 +43,7 @@ export function extractColorFromStyleProperty( } const palette = getColorPalette(dynamicOptions.colorCategory); - return palette[0]; + return palette ? palette[0] : defaultColor; } else { // return middle of gradient for dynamic style property if (dynamicOptions.useCustomColorRamp) { @@ -58,6 +58,7 @@ export function extractColorFromStyleProperty( if (!dynamicOptions.color) { return defaultColor; } - return getColorRampCenterColor(dynamicOptions.color); + const centerColor = getColorRampCenterColor(dynamicOptions.color); + return centerColor ? centerColor : defaultColor; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/ordinal_legend.js b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.js similarity index 50% rename from x-pack/plugins/maps/public/classes/styles/vector/properties/components/ordinal_legend.js rename to x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.js index 1ebd042118480..478d96962e47b 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/ordinal_legend.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/components/legend/ordinal_legend.js @@ -4,9 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; import _ from 'lodash'; import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; +import { VECTOR_STYLES } from '../../../../../../common/constants'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { CircleIcon } from './circle_icon'; + +function getLineWidthIcons() { + const defaultStyle = { + stroke: 'grey', + fill: 'none', + width: '12px', + }; + return [ + , + , + , + ]; +} + +function getSymbolSizeIcons() { + const defaultStyle = { + stroke: 'grey', + fill: 'grey', + }; + return [ + , + , + , + ]; +} const EMPTY_VALUE = ''; export class OrdinalLegend extends React.Component { @@ -45,7 +73,46 @@ export class OrdinalLegend extends React.Component { this._isMounted = true; this._loadParams(); } + + _renderRangeLegendHeader() { + let icons; + if (this.props.style.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { + icons = getLineWidthIcons(); + } else if (this.props.style.getStyleName() === VECTOR_STYLES.ICON_SIZE) { + icons = getSymbolSizeIcons(); + } else { + return null; + } + + return ( + + {icons.map((icon, index) => { + const isLast = index === icons.length - 1; + let spacer; + if (!isLast) { + spacer = ( + + + + ); + } + return ( + + {icon} + {spacer} + + ); + })} + + ); + } + render() { + const header = this._renderRangeLegendHeader(); + if (!header) { + return null; + } + const fieldMeta = this.props.style.getRangeFieldMeta(); let minLabel = EMPTY_VALUE; @@ -67,7 +134,7 @@ export class OrdinalLegend extends React.Component { return ( { - return ( - - {style.renderLegendDetailRow({ - isLinesOnly, - isPointsOnly, - symbolId, - })} - + const legendRows = []; + + for (let i = 0; i < styles.length; i++) { + const row = styles[i].renderLegendDetailRow({ + isLinesOnly, + isPointsOnly, + symbolId, + }); + + legendRows.push( +
+ {row} +
); - }); + } + + return legendRows; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap index ab47e88bb3143..29eb52897a50e 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap @@ -1,50 +1,92 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Should render categorical legend with breaks from custom 1`] = `""`; +exports[`categorical Should render categorical legend with breaks from custom 1`] = `""`; -exports[`Should render categorical legend with breaks from default 1`] = ` +exports[`categorical Should render categorical legend with breaks from default 1`] = `
- + + + + + + + foobar_label + + + + + + - - - - Other - - } - styleName="lineColor" - /> + + + + + + + + + Other + + } + styleName="lineColor" + /> + +
+`; + +exports[`ordinal Should render custom ordinal legend with breaks 1`] = ` +
+ + + + +
`; -exports[`Should render ordinal legend 1`] = ` - - } - maxLabel="100_format" - minLabel="0_format" - propertyLabel="Border color" -/> -`; - -exports[`Should render ordinal legend with breaks 1`] = ` +exports[`ordinal Should render only single band of last color when delta is 0 1`] = `
- + + + + + + + foobar_label + + + + + + - - + > + + +
+`; + +exports[`ordinal Should render ordinal legend as bands 1`] = ` +
+ + + + + + + + + + + + + + + + + + + + + + +
`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap index 057907353d68a..b4843324a0def 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_icon_property.test.tsx.snap @@ -2,50 +2,9 @@ exports[`Should render categorical legend with breaks 1`] = `
- - - - - - Other - - } - styleName="icon" - symbolId="square" - /> - + + + + + + + +
`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap new file mode 100644 index 0000000000000..11138bf337043 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renderLegendDetailRow Should render as range 1`] = ` + + + + + + + + + + + + + + + + + + + + + + + + } + maxLabel="100_format" + minLabel="0_format" + propertyLabel="Symbol size" +/> +`; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts index 1c478bb85ccc7..a8fba834d65ab 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/__tests__/test_util.ts @@ -14,6 +14,7 @@ import { StyleMetaDescriptor, } from '../../../../../../common/descriptor_types'; import { AbstractField, IField } from '../../../../fields/field'; +import { IStyle, AbstractStyle } from '../../../style'; class MockField extends AbstractField { async getLabel(): Promise { @@ -29,14 +30,27 @@ export const mockField: IField = new MockField({ origin: FIELD_ORIGIN.SOURCE, }); -class MockStyle { +export class MockStyle extends AbstractStyle implements IStyle { + private readonly _min: number; + private readonly _max: number; + + constructor({ min = 0, max = 100 } = {}) { + super(null); + this._min = min; + this._max = max; + } + getStyleMeta(): StyleMeta { const geomTypes: GeometryTypes = { isPointsOnly: false, isLinesOnly: false, isPolygonsOnly: false, }; - const rangeFieldMeta: RangeFieldMeta = { min: 0, max: 100, delta: 100 }; + const rangeFieldMeta: RangeFieldMeta = { + min: this._min, + max: this._max, + delta: this._max - this._min, + }; const catFieldMeta: CategoryFieldMeta = { categories: [ { @@ -65,8 +79,12 @@ class MockStyle { } export class MockLayer { + private readonly _style: IStyle; + constructor(style = new MockStyle()) { + this._style = style; + } getStyle() { - return new MockStyle(); + return this._style; } getDataRequest() { diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js deleted file mode 100644 index a46492b6034a7..0000000000000 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/components/categorical_legend.js +++ /dev/null @@ -1,48 +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 _ from 'lodash'; -const EMPTY_VALUE = ''; - -export class CategoricalLegend extends React.Component { - state = { - label: EMPTY_VALUE, - }; - - componentDidMount() { - this._isMounted = true; - this._loadParams(); - } - - componentDidUpdate() { - this._loadParams(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - async _loadParams() { - const label = await this.props.style.getField().getLabel(); - const newState = { label }; - if (this._isMounted && !_.isEqual(this.state, newState)) { - this.setState(newState); - } - } - - render() { - if (this.state.label === EMPTY_VALUE) { - return null; - } - return this.props.style.renderBreakedLegend({ - fieldLabel: this.state.label, - isLinesOnly: this.props.isLinesOnly, - isPointsOnly: this.props.isPointsOnly, - symbolId: this.props.symbolId, - }); - } -} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js index 0afc784c482c5..4c02dee762e9d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.js @@ -5,23 +5,24 @@ */ import { DynamicStyleProperty } from './dynamic_style_property'; -import { getOtherCategoryLabel, makeMbClampedNumberExpression } from '../style_util'; -import { getOrdinalColorRampStops, getColorPalette } from '../../color_utils'; -import { ColorGradient } from '../../components/color_gradient'; +import { makeMbClampedNumberExpression, dynamicRound } from '../style_util'; +import { + getOrdinalMbColorRampStops, + getColorPalette, + getHexColorRangeStrings, + GRADIENT_INTERVALS, +} from '../../color_utils'; import React from 'react'; +import { COLOR_MAP_TYPE } from '../../../../../common/constants'; import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiToolTip, - EuiTextColor, -} from '@elastic/eui'; -import { Category } from '../components/legend/category'; -import { COLOR_MAP_TYPE, RGBA_0000 } from '../../../../../common/constants'; -import { isCategoricalStopsInvalid } from '../components/color/color_stops_utils'; + isCategoricalStopsInvalid, + getOtherCategoryLabel, +} from '../components/color/color_stops_utils'; +import { BreakedLegend } from '../components/legend/breaked_legend'; +import { EuiTextColor } from '@elastic/eui'; const EMPTY_STOPS = { stops: [], defaultColor: null }; +const RGBA_0000 = 'rgba(0,0,0,0)'; export class DynamicColorProperty extends DynamicStyleProperty { syncCircleColorWithMb(mbLayerId, mbMap, alpha) { @@ -99,14 +100,6 @@ export class DynamicColorProperty extends DynamicStyleProperty { return true; } - isOrdinalRanged() { - return this.isOrdinal() && !this._options.useCustomColorRamp; - } - - hasOrdinalBreaks() { - return (this.isOrdinal() && this._options.useCustomColorRamp) || this.isCategorical(); - } - _getMbColor() { if (!this._field || !this._field.getName()) { return null; @@ -142,10 +135,11 @@ export class DynamicColorProperty extends DynamicStyleProperty { return null; } - const colorStops = getOrdinalColorRampStops( + const colorStops = getOrdinalMbColorRampStops( this._options.color, rangeFieldMeta.min, - rangeFieldMeta.max + rangeFieldMeta.max, + GRADIENT_INTERVALS ); if (!colorStops) { return null; @@ -237,28 +231,47 @@ export class DynamicColorProperty extends DynamicStyleProperty { for (let i = 0; i < stops.length; i++) { const stop = stops[i]; const branch = `${stop.stop}`; - if (typeof branch === 'string') { - mbStops.push(branch); - mbStops.push(stop.color); - } + mbStops.push(branch); + mbStops.push(stop.color); } mbStops.push(defaultColor); //last color is default color return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops]; } - renderRangeLegendHeader() { - if (this._options.color) { - return ; - } else { - return null; + _getColorRampStops() { + if (this._options.useCustomColorRamp && this._options.customColorRamp) { + return this._options.customColorRamp; } - } - _getColorRampStops() { - return this._options.useCustomColorRamp && this._options.customColorRamp - ? this._options.customColorRamp - : []; + if (!this._options.color) { + return []; + } + + const rangeFieldMeta = this.getRangeFieldMeta(); + if (!rangeFieldMeta) { + return []; + } + + const colors = getHexColorRangeStrings(this._options.color, GRADIENT_INTERVALS); + + if (rangeFieldMeta.delta === 0) { + //map to last color. + return [ + { + color: colors[colors.length - 1], + stop: dynamicRound(rangeFieldMeta.max), + }, + ]; + } + + return colors.map((color, index) => { + const rawStopValue = rangeFieldMeta.min + rangeFieldMeta.delta * (index / GRADIENT_INTERVALS); + return { + color, + stop: dynamicRound(rawStopValue), + }; + }); } _getColorStops() { @@ -274,55 +287,33 @@ export class DynamicColorProperty extends DynamicStyleProperty { } } - renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { - const categories = []; + renderLegendDetailRow({ isPointsOnly, isLinesOnly, symbolId }) { const { stops, defaultColor } = this._getColorStops(); - stops.map(({ stop, color }) => { - categories.push( - - ); + const breaks = []; + stops.forEach(({ stop, color }) => { + if (stop) { + breaks.push({ + color, + symbolId, + label: this.formatField(stop), + }); + } }); - if (defaultColor) { - categories.push( - {getOtherCategoryLabel()}} - color={defaultColor} - isLinesOnly={isLinesOnly} - isPointsOnly={isPointsOnly} - symbolId={symbolId} - /> - ); + breaks.push({ + color: defaultColor, + label: {getOtherCategoryLabel()}, + symbolId, + }); } return ( -
- - - {categories} - - - - - - - {fieldLabel} - - - - - -
+ ); } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js index afcdf1e3cfc5b..1879b260da2e2 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_color_property.test.js @@ -16,12 +16,18 @@ import { shallow } from 'enzyme'; import { DynamicColorProperty } from './dynamic_color_property'; import { COLOR_MAP_TYPE, VECTOR_STYLES } from '../../../../../common/constants'; -import { mockField, MockLayer } from './__tests__/test_util'; - -const makeProperty = (options, field = mockField) => { - return new DynamicColorProperty(options, VECTOR_STYLES.LINE_COLOR, field, new MockLayer(), () => { - return (x) => x + '_format'; - }); +import { mockField, MockLayer, MockStyle } from './__tests__/test_util'; + +const makeProperty = (options, mockStyle, field = mockField) => { + return new DynamicColorProperty( + options, + VECTOR_STYLES.LINE_COLOR, + field, + new MockLayer(mockStyle), + () => { + return (x) => x + '_format'; + } + ); }; const defaultLegendParams = { @@ -29,91 +35,121 @@ const defaultLegendParams = { isLinesOnly: false, }; -test('Should render ordinal legend', async () => { - const colorStyle = makeProperty({ - color: 'Blues', - type: undefined, - }); +describe('ordinal', () => { + test('Should render ordinal legend as bands', async () => { + const colorStyle = makeProperty({ + color: 'Blues', + type: undefined, + }); - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - const component = shallow(legendRow); + const component = shallow(legendRow); - expect(component).toMatchSnapshot(); -}); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); -test('Should render ordinal legend with breaks', async () => { - const colorStyle = makeProperty({ - type: COLOR_MAP_TYPE.ORDINAL, - useCustomColorRamp: true, - customColorRamp: [ - { - stop: 0, - color: '#FF0000', - }, + expect(component).toMatchSnapshot(); + }); + + test('Should render only single band of last color when delta is 0', async () => { + const colorStyle = makeProperty( { - stop: 10, - color: '#00FF00', + color: 'Blues', + type: undefined, }, - ], + new MockStyle({ min: 100, max: 100 }) + ); + + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + + const component = shallow(legendRow); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + expect(component).toMatchSnapshot(); }); - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + test('Should render custom ordinal legend with breaks', async () => { + const colorStyle = makeProperty({ + type: COLOR_MAP_TYPE.ORDINAL, + useCustomColorRamp: true, + customColorRamp: [ + { + stop: 0, + color: '#FF0000', + }, + { + stop: 10, + color: '#00FF00', + }, + ], + }); - const component = shallow(legendRow); + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + const component = shallow(legendRow); - expect(component).toMatchSnapshot(); -}); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); -test('Should render categorical legend with breaks from default', async () => { - const colorStyle = makeProperty({ - type: COLOR_MAP_TYPE.CATEGORICAL, - useCustomColorPalette: false, - colorCategory: 'palette_0', + expect(component).toMatchSnapshot(); }); +}); - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); +describe('categorical', () => { + test('Should render categorical legend with breaks from default', async () => { + const colorStyle = makeProperty({ + type: COLOR_MAP_TYPE.CATEGORICAL, + useCustomColorPalette: false, + colorCategory: 'palette_0', + }); - const component = shallow(legendRow); + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); + const component = shallow(legendRow); - expect(component).toMatchSnapshot(); -}); + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); -test('Should render categorical legend with breaks from custom', async () => { - const colorStyle = makeProperty({ - type: COLOR_MAP_TYPE.CATEGORICAL, - useCustomColorPalette: true, - customColorPalette: [ - { - stop: null, //should include the default stop - color: '#FFFF00', - }, - { - stop: 'US_STOP', - color: '#FF0000', - }, - { - stop: 'CN_STOP', - color: '#00FF00', - }, - ], + expect(component).toMatchSnapshot(); }); - const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); + test('Should render categorical legend with breaks from custom', async () => { + const colorStyle = makeProperty({ + type: COLOR_MAP_TYPE.CATEGORICAL, + useCustomColorPalette: true, + customColorPalette: [ + { + stop: null, //should include the default stop + color: '#FFFF00', + }, + { + stop: 'US_STOP', + color: '#FF0000', + }, + { + stop: 'CN_STOP', + color: '#00FF00', + }, + ], + }); + + const legendRow = colorStyle.renderLegendDetailRow(defaultLegendParams); - const component = shallow(legendRow); + const component = shallow(legendRow); - expect(component).toMatchSnapshot(); + expect(component).toMatchSnapshot(); + }); }); function makeFeatures(foobarPropValues) { @@ -201,7 +237,7 @@ describe('supportsFieldMeta', () => { const dynamicStyleOptions = { type: COLOR_MAP_TYPE.ORDINAL, }; - const styleProp = makeProperty(dynamicStyleOptions, field); + const styleProp = makeProperty(dynamicStyleOptions, undefined, field); expect(styleProp.supportsFieldMeta()).toEqual(false); }); @@ -210,7 +246,7 @@ describe('supportsFieldMeta', () => { const dynamicStyleOptions = { type: COLOR_MAP_TYPE.ORDINAL, }; - const styleProp = makeProperty(dynamicStyleOptions, null); + const styleProp = makeProperty(dynamicStyleOptions, undefined, null); expect(styleProp.supportsFieldMeta()).toEqual(false); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js index 27c4fca7d701d..c7620512710dc 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_icon_property.js @@ -6,19 +6,11 @@ import _ from 'lodash'; import React from 'react'; -import { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; import { DynamicStyleProperty } from './dynamic_style_property'; import { getIconPalette, getMakiIconId, getMakiSymbolAnchor } from '../symbol_utils'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, - EuiToolTip, - EuiTextColor, -} from '@elastic/eui'; -import { Category } from '../components/legend/category'; +import { BreakedLegend } from '../components/legend/breaked_legend'; +import { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; +import { EuiTextColor } from '@elastic/eui'; export class DynamicIconProperty extends DynamicStyleProperty { isOrdinal() { @@ -60,7 +52,7 @@ export class DynamicIconProperty extends DynamicStyleProperty { } return { - fallback: + fallbackSymbolId: this._options.customIconStops.length > 0 ? this._options.customIconStops[0].icon : null, stops, }; @@ -73,9 +65,9 @@ export class DynamicIconProperty extends DynamicStyleProperty { } _getMbIconImageExpression(iconPixelSize) { - const { stops, fallback } = this._getPaletteStops(); + const { stops, fallbackSymbolId } = this._getPaletteStops(); - if (stops.length < 1 || !fallback) { + if (stops.length < 1 || !fallbackSymbolId) { //occurs when no data return null; } @@ -85,14 +77,17 @@ export class DynamicIconProperty extends DynamicStyleProperty { mbStops.push(`${stop}`); mbStops.push(getMakiIconId(style, iconPixelSize)); }); - mbStops.push(getMakiIconId(fallback, iconPixelSize)); //last item is fallback style for anything that does not match provided stops + + if (fallbackSymbolId) { + mbStops.push(getMakiIconId(fallbackSymbolId, iconPixelSize)); //last item is fallback style for anything that does not match provided stops + } return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops]; } _getMbIconAnchorExpression() { - const { stops, fallback } = this._getPaletteStops(); + const { stops, fallbackSymbolId } = this._getPaletteStops(); - if (stops.length < 1 || !fallback) { + if (stops.length < 1 || !fallbackSymbolId) { //occurs when no data return null; } @@ -102,7 +97,10 @@ export class DynamicIconProperty extends DynamicStyleProperty { mbStops.push(`${stop}`); mbStops.push(getMakiSymbolAnchor(style)); }); - mbStops.push(getMakiSymbolAnchor(fallback)); //last item is fallback style for anything that does not match provided stops + + if (fallbackSymbolId) { + mbStops.push(getMakiSymbolAnchor(fallbackSymbolId)); //last item is fallback style for anything that does not match provided stops + } return ['match', ['to-string', ['get', this._field.getName()]], ...mbStops]; } @@ -110,55 +108,34 @@ export class DynamicIconProperty extends DynamicStyleProperty { return this._field && this._field.isValid(); } - renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly }) { - const categories = []; - const { stops, fallback } = this._getPaletteStops(); - stops.map(({ stop, style }) => { - categories.push( - - ); + renderLegendDetailRow({ isPointsOnly, isLinesOnly }) { + const { stops, fallbackSymbolId } = this._getPaletteStops(); + const breaks = []; + stops.forEach(({ stop, style }) => { + if (stop) { + breaks.push({ + color: 'grey', + label: this.formatField(stop), + symbolId: style, + }); + } }); - if (fallback) { - categories.push( - {getOtherCategoryLabel()}} - color="grey" - isLinesOnly={isLinesOnly} - isPointsOnly={isPointsOnly} - symbolId={fallback} - /> - ); + if (fallbackSymbolId) { + breaks.push({ + color: 'grey', + label: {getOtherCategoryLabel()}, + symbolId: fallbackSymbolId, + }); } return ( -
- - - {categories} - - - - - - - {fieldLabel} - - - - - -
+ ); } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js index 71ac25f0c6e61..898da439c44aa 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.js @@ -5,6 +5,7 @@ */ import { DynamicStyleProperty } from './dynamic_style_property'; +import { OrdinalLegend } from '../components/legend/ordinal_legend'; import { makeMbClampedNumberExpression } from '../style_util'; import { HALF_LARGE_MAKI_ICON_SIZE, @@ -13,34 +14,7 @@ import { } from '../symbol_utils'; import { VECTOR_STYLES } from '../../../../../common/constants'; import _ from 'lodash'; -import { CircleIcon } from '../components/legend/circle_icon'; -import React, { Fragment } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; - -function getLineWidthIcons() { - const defaultStyle = { - stroke: 'grey', - fill: 'none', - width: '12px', - }; - return [ - , - , - , - ]; -} - -function getSymbolSizeIcons() { - const defaultStyle = { - stroke: 'grey', - fill: 'grey', - }; - return [ - , - , - , - ]; -} +import React from 'react'; export class DynamicSizeProperty extends DynamicStyleProperty { constructor(options, styleName, field, vectorLayer, getFieldFormatter, isSymbolizedAsIcon) { @@ -99,13 +73,9 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } } - syncCircleStrokeWidthWithMb(mbLayerId, mbMap, hasNoRadius) { - if (hasNoRadius) { - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', 0); - } else { - const lineWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); - } + syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { + const lineWidth = this.getMbSizeExpression(); + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); } syncCircleRadiusWithMb(mbLayerId, mbMap) { @@ -166,36 +136,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { ); } - renderRangeLegendHeader() { - let icons; - if (this.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { - icons = getLineWidthIcons(); - } else if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE) { - icons = getSymbolSizeIcons(); - } else { - return null; - } - - return ( - - {icons.map((icon, index) => { - const isLast = index === icons.length - 1; - let spacer; - if (!isLast) { - spacer = ( - - - - ); - } - return ( - - {icon} - {spacer} - - ); - })} - - ); + renderLegendDetailRow() { + return ; } } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx new file mode 100644 index 0000000000000..34f3e796f409f --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 { IVectorStyle } from '../vector_style'; + +jest.mock('ui/new_platform'); +jest.mock('../components/vector_style_editor', () => ({ + VectorStyleEditor: () => { + return
mockVectorStyleEditor
; + }, +})); + +import React from 'react'; +import { shallow } from 'enzyme'; + +// @ts-ignore +import { DynamicSizeProperty } from './dynamic_size_property'; +import { StyleMeta } from '../style_meta'; +import { FIELD_ORIGIN, VECTOR_STYLES } from '../../../../../common/constants'; +import { DataRequest } from '../../../util/data_request'; +import { IVectorLayer } from '../../../layers/vector_layer/vector_layer'; +import { IField } from '../../../fields/field'; + +// @ts-ignore +const mockField: IField = { + async getLabel() { + return 'foobar_label'; + }, + getName() { + return 'foobar'; + }, + getRootName() { + return 'foobar'; + }, + getOrigin() { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMeta() { + return true; + }, + canValueBeFormatted() { + return true; + }, + async getDataType() { + return 'number'; + }, +}; + +// @ts-ignore +const mockLayer: IVectorLayer = { + getDataRequest(): DataRequest | undefined { + return undefined; + }, + getStyle(): IVectorStyle { + // @ts-ignore + return { + getStyleMeta(): StyleMeta { + return new StyleMeta({ + geometryTypes: { + isPointsOnly: true, + isLinesOnly: false, + isPolygonsOnly: false, + }, + fieldMeta: { + foobar: { + range: { min: 0, max: 100, delta: 100 }, + categories: { categories: [] }, + }, + }, + }); + }, + }; + }, +}; + +const makeProperty: DynamicSizeProperty = (options: object) => { + return new DynamicSizeProperty(options, VECTOR_STYLES.ICON_SIZE, mockField, mockLayer, () => { + return (x: string) => x + '_format'; + }); +}; + +const defaultLegendParams = { + isPointsOnly: true, + isLinesOnly: false, +}; + +describe('renderLegendDetailRow', () => { + test('Should render as range', async () => { + const sizeProp = makeProperty(); + const legendRow = sizeProp.renderLegendDetailRow(defaultLegendParams); + const component = shallow(legendRow); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js index 82645b3a29319..98d5d3feb60ea 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.js @@ -9,8 +9,6 @@ import { AbstractStyleProperty } from './style_property'; import { DEFAULT_SIGMA } from '../vector_style_defaults'; import { STYLE_TYPE, SOURCE_META_ID_ORIGIN, FIELD_ORIGIN } from '../../../../../common/constants'; import React from 'react'; -import { OrdinalLegend } from './components/ordinal_legend'; -import { CategoricalLegend } from './components/categorical_legend'; import { OrdinalFieldMetaPopover } from '../components/field_meta/ordinal_field_meta_popover'; import { CategoricalFieldMetaPopover } from '../components/field_meta/categorical_field_meta_popover'; @@ -119,14 +117,6 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return 0; } - hasOrdinalBreaks() { - return false; - } - - isOrdinalRanged() { - return true; - } - isComplete() { return !!this._field; } @@ -280,49 +270,14 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } getNumericalMbFeatureStateValue(value) { - if (typeof value === 'number') { - return value; - } - const valueAsFloat = parseFloat(value); return isNaN(valueAsFloat) ? null : valueAsFloat; } - renderBreakedLegend() { + renderLegendDetailRow() { return null; } - _renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }) { - return ( - - ); - } - - _renderRangeLegend() { - return ; - } - - renderLegendDetailRow({ isPointsOnly, isLinesOnly, symbolId }) { - if (this.isOrdinal()) { - if (this.isOrdinalRanged()) { - return this._renderRangeLegend(); - } else if (this.hasOrdinalBreaks()) { - return this._renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }); - } else { - return null; - } - } else if (this.isCategorical()) { - return this._renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }); - } else { - return null; - } - } - renderFieldMetaPopover(onFieldMetaOptionsChange) { if (!this.supportsFieldMeta()) { return null; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts index af04a95e3c3bd..b704e4bd56970 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/style_property.ts @@ -23,7 +23,6 @@ export interface IStyleProperty { formatField(value: string | undefined): string; getStyleName(): VECTOR_STYLES; getOptions(): StylePropertyOptions; - renderRangeLegendHeader(): ReactElement | null; renderLegendDetailRow(legendProps: LegendProps): ReactElement | null; renderFieldMetaPopover( onFieldMetaOptionsChange: (fieldMetaOptions: FieldMetaOptions) => void @@ -67,10 +66,6 @@ export class AbstractStyleProperty implements IStyleProperty { return this._options || {}; } - renderRangeLegendHeader() { - return null; - } - renderLegendDetailRow() { return null; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.js b/x-pack/plugins/maps/public/classes/styles/vector/style_util.js index 0820568468439..3b62dcb27dced 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.js @@ -34,6 +34,21 @@ export function isOnlySingleFeatureType(featureType, supportedFeatures, hasFeatu }, true); } +export function dynamicRound(value) { + if (typeof value !== 'number') { + return value; + } + + let precision = 0; + let threshold = 10; + while (value < threshold && precision < 8) { + precision++; + threshold = threshold / 10; + } + + return precision === 0 ? Math.round(value) : parseFloat(value.toFixed(precision + 1)); +} + export function assignCategoriesToPalette({ categories, paletteValues }) { const stops = []; let fallback = null; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js b/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js index 76bbfc84e3892..eb4c6708fb2dd 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isOnlySingleFeatureType, assignCategoriesToPalette } from './style_util'; +import { isOnlySingleFeatureType, assignCategoriesToPalette, dynamicRound } from './style_util'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; describe('isOnlySingleFeatureType', () => { @@ -100,3 +100,15 @@ describe('assignCategoriesToPalette', () => { }); }); }); + +describe('dynamicRound', () => { + test('Should truncate based on magnitude of number', () => { + expect(dynamicRound(1000.1234)).toBe(1000); + expect(dynamicRound(1.1234)).toBe(1.12); + expect(dynamicRound(0.0012345678)).toBe(0.00123); + }); + + test('Should return argument when not a number', () => { + expect(dynamicRound('foobar')).toBe('foobar'); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts index beea943943994..ea0736c4837d8 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style.d.ts @@ -12,11 +12,13 @@ import { VectorStyleDescriptor, VectorStylePropertiesDescriptor, } from '../../../../common/descriptor_types'; +import { StyleMeta } from './style_meta'; export interface IVectorStyle extends IStyle { getAllStyleProperties(): IStyleProperty[]; getDynamicPropertiesArray(): IDynamicStyleProperty[]; getSourceFieldNames(): string[]; + getStyleMeta(): StyleMeta; } export class VectorStyle extends AbstractStyle implements IVectorStyle { @@ -26,4 +28,5 @@ export class VectorStyle extends AbstractStyle implements IVectorStyle { getSourceFieldNames(): string[]; getAllStyleProperties(): IStyleProperty[]; getDynamicPropertiesArray(): IDynamicStyleProperty[]; + getStyleMeta(): StyleMeta; } diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.test.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.test.ts new file mode 100644 index 0000000000000..bc032639dd07d --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.test.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../../../kibana_services', () => { + const mockUiSettings = { + get: () => { + return undefined; + }, + }; + return { + getUiSettings: () => { + return mockUiSettings; + }, + }; +}); + +import { VECTOR_STYLES } from '../../../../common/constants'; +import { getDefaultStaticProperties } from './vector_style_defaults'; + +describe('getDefaultStaticProperties', () => { + test('Should use first color in DEFAULT_*_COLORS when no colors are used on the map', () => { + const styleProperties = getDefaultStaticProperties([]); + expect(styleProperties[VECTOR_STYLES.FILL_COLOR]!.options.color).toBe('#54B399'); + expect(styleProperties[VECTOR_STYLES.LINE_COLOR]!.options.color).toBe('#41937c'); + }); + + test('Should next color in DEFAULT_*_COLORS when colors are used on the map', () => { + const styleProperties = getDefaultStaticProperties(['#54B399']); + expect(styleProperties[VECTOR_STYLES.FILL_COLOR]!.options.color).toBe('#6092C0'); + expect(styleProperties[VECTOR_STYLES.LINE_COLOR]!.options.color).toBe('#4379aa'); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts index 86602381cf615..a6878a0d760c7 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/vector_style_defaults.ts @@ -16,7 +16,6 @@ import { COLOR_PALETTES, DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS, - // @ts-ignore } from '../color_utils'; import { VectorStylePropertiesDescriptor } from '../../../../common/descriptor_types'; // @ts-ignore @@ -58,9 +57,13 @@ export function getDefaultProperties(mapColors: string[] = []): VectorStylePrope export function getDefaultStaticProperties( mapColors: string[] = [] ): VectorStylePropertiesDescriptor { - // Colors must be state-aware to reduce unnecessary incrementation - const lastColor = mapColors.pop(); - const nextColorIndex = (DEFAULT_FILL_COLORS.indexOf(lastColor) + 1) % DEFAULT_FILL_COLORS.length; + let nextColorIndex = 0; + if (mapColors.length) { + const lastColor = mapColors[mapColors.length - 1]; + if (DEFAULT_FILL_COLORS.includes(lastColor)) { + nextColorIndex = (DEFAULT_FILL_COLORS.indexOf(lastColor) + 1) % DEFAULT_FILL_COLORS.length; + } + } const nextFillColor = DEFAULT_FILL_COLORS[nextColorIndex]; const nextLineColor = DEFAULT_LINE_COLORS[nextColorIndex]; diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index 766ba72b2dcef..63895ea8b9822 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -502,7 +502,7 @@ export function initRoutes(router, licenseUid, mapConfig, kbnVersion, logger) { } try { - const resp = await con.core.elasticsearch.dataClient.callAsCurrentUser( + const resp = await con.core.elasticsearch.legacy.client.callAsCurrentUser( 'indices.getSettings', { index: query.indexPatternTitle, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 0a3728c7351b5..15c54fc5b3a46 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -19,7 +19,7 @@ import { mlCalendarService } from '../../../services/calendar_service'; export function loadFullJob(jobId) { return new Promise((resolve, reject) => { ml.jobs - .jobs(jobId) + .jobs([jobId]) .then((jobs) => { if (jobs.length) { resolve(jobs[0]); diff --git a/x-pack/plugins/ml/server/routes/job_validation.ts b/x-pack/plugins/ml/server/routes/job_validation.ts index 632166d6d5fb8..0af8141a2a641 100644 --- a/x-pack/plugins/ml/server/routes/job_validation.ts +++ b/x-pack/plugins/ml/server/routes/job_validation.ts @@ -66,7 +66,7 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, let errorResp; const resp = await estimateBucketSpanFactory( context.ml!.mlClient.callAsCurrentUser, - context.core.elasticsearch.adminClient.callAsInternalUser, + context.ml!.mlClient.callAsInternalUser, mlLicense.isSecurityEnabled() === false )(request.body) // this catch gets triggered when the estimation code runs without error @@ -187,7 +187,7 @@ export function jobValidationRoutes({ router, mlLicense }: RouteInitialization, context.ml!.mlClient.callAsCurrentUser, request.body, version, - context.core.elasticsearch.adminClient.callAsInternalUser, + context.ml!.mlClient.callAsInternalUser, mlLicense.isSecurityEnabled() === false ); diff --git a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts index 1ca1e5287e9d0..be107db9508fd 100644 --- a/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/job_service_schema.ts @@ -40,10 +40,8 @@ export const forceStartDatafeedSchema = schema.object({ }); export const jobIdsSchema = schema.object({ - /** Optional list of job ID(s). */ - jobIds: schema.maybe( - schema.oneOf([schema.string(), schema.arrayOf(schema.maybe(schema.string()))]) - ), + /** Optional list of job IDs. */ + jobIds: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), }); export const jobsWithTimerangeSchema = { diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index 698ac8e6261e5..33a4d854dd3e9 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -23,7 +23,7 @@ export interface MlSystemProvider { ): { mlCapabilities(): Promise; mlInfo(): Promise; - mlSearch(searchParams: SearchParams): Promise>; + mlAnomalySearch(searchParams: SearchParams): Promise>; }; } @@ -68,7 +68,7 @@ export function getMlSystemProvider( cloudId, }; }, - async mlSearch(searchParams: SearchParams): Promise> { + async mlAnomalySearch(searchParams: SearchParams): Promise> { isFullLicense(); return callAsCurrentUser('search', { ...searchParams, diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index 37e5ad42c3e7c..f57e1a774a8e2 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -39,7 +39,7 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { return createAnnotationsClient({ index, - apiCaller: requestContext.core.elasticsearch.dataClient.callAsCurrentUser, + apiCaller: requestContext.core.elasticsearch.legacy.client.callAsCurrentUser, logger, license: requestContext.licensing?.license, }); diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 5bb6be39a2049..5d0fdc65117bf 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -50,7 +50,7 @@ export function registerAnnotationAPIs({ }); } - const apiCaller = context.core.elasticsearch.dataClient.callAsCurrentUser; + const apiCaller = context.core.elasticsearch.legacy.client.callAsCurrentUser; const client = createAnnotationsClient({ index, diff --git a/x-pack/plugins/painless_lab/server/routes/api/execute.ts b/x-pack/plugins/painless_lab/server/routes/api/execute.ts index 55adb5e0410cc..7d415761f04cb 100644 --- a/x-pack/plugins/painless_lab/server/routes/api/execute.ts +++ b/x-pack/plugins/painless_lab/server/routes/api/execute.ts @@ -23,7 +23,7 @@ export function registerExecuteRoute({ router, license }: RouteDependencies) { const body = req.body; try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const response = await callAsCurrentUser('scriptsPainlessExecute', { body, }); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts index 2b8b3bf1a5e59..cbfbbfd2d61f3 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.test.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './add_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -59,13 +60,8 @@ describe('ADD remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts index 5e0fce82376e0..38f11db18a088 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/add_route.ts @@ -36,7 +36,7 @@ export const register = (deps: RouteDependencies): void => { response ) => { try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const { name } = request.body; diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts index 69f84b7ef5e16..b9328cb61e967 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.test.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './delete_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -61,14 +62,8 @@ describe('DELETE remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; - + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts index 07b0fa3fd9cd7..69760b7477e1b 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/delete_route.ts @@ -29,7 +29,7 @@ export const register = (deps: RouteDependencies): void => { response ) => { try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const { nameOrNames } = request.params; const names = nameOrNames.split(','); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts index deaf251c80c17..f0444162e80b9 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.test.ts @@ -5,11 +5,12 @@ */ import Boom from 'boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './get_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -59,13 +60,8 @@ describe('GET remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts index 078e1073d1568..4e3fa34ac94c3 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/get_route.ts @@ -16,7 +16,7 @@ import { RouteDependencies } from '../../types'; export const register = (deps: RouteDependencies): void => { const allHandler: RequestHandler = async (ctx, request, response) => { try { - const callAsCurrentUser = await ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = await ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const clusterSettings = await callAsCurrentUser('cluster.getSettings'); const transientClusterNames = Object.keys( diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts index b2a443c41fbf9..7f8acb2b018d9 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.test.ts @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { register } from './update_route'; import { API_BASE_PATH } from '../../../common/constants'; import { LicenseStatus } from '../../types'; +import { xpackMocks } from '../../../../../mocks'; import { elasticsearchServiceMock, httpServerMock, @@ -69,14 +70,8 @@ describe('UPDATE remote clusters', () => { headers: { authorization: 'foo' }, }); - const mockContext = ({ - core: { - elasticsearch: { - dataClient: mockScopedClusterClient, - }, - }, - } as unknown) as RequestHandlerContext; - + const mockContext = xpackMocks.createRequestHandlerContext(); + mockContext.core.elasticsearch.legacy.client = mockScopedClusterClient; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); diff --git a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts index 02a63783154df..0666a295de456 100644 --- a/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts +++ b/x-pack/plugins/remote_clusters/server/routes/api/update_route.ts @@ -42,7 +42,7 @@ export const register = (deps: RouteDependencies): void => { response ) => { try { - const callAsCurrentUser = ctx.core.elasticsearch.dataClient.callAsCurrentUser; + const callAsCurrentUser = ctx.core.elasticsearch.legacy.client.callAsCurrentUser; const { name } = request.params; diff --git a/x-pack/plugins/reporting/constants.ts b/x-pack/plugins/reporting/constants.ts index 8079c5b1d9887..9a1d0cec2cf96 100644 --- a/x-pack/plugins/reporting/constants.ts +++ b/x-pack/plugins/reporting/constants.ts @@ -12,7 +12,7 @@ export const API_BASE_URL = '/api/reporting'; export const API_LIST_URL = `${API_BASE_URL}/jobs`; export const API_BASE_GENERATE = `${API_BASE_URL}/generate`; export const API_GENERATE_IMMEDIATE = `${API_BASE_URL}/v1/generate/immediate/csv/saved-object`; -export const REPORTING_MANAGEMENT_HOME = '/app/kibana#/management/kibana/reporting'; +export const REPORTING_MANAGEMENT_HOME = '/app/kibana#/management/insightsAndAlerting/reporting'; // Statuses export const JOB_STATUS_FAILED = 'failed'; diff --git a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts index 23afb85c5437a..546d9d628277f 100644 --- a/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts +++ b/x-pack/plugins/rollup/server/routes/api/index_patterns/register_fields_for_wildcard_route.ts @@ -25,7 +25,7 @@ const getFieldsForWildcardRequest = async ( response: any, IndexPatternsFetcher: any ) => { - const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser } = context.core.elasticsearch.legacy.client; const indexPatterns = new IndexPatternsFetcher(callAsCurrentUser); const { pattern, meta_fields: metaFields } = request.query; diff --git a/x-pack/plugins/searchprofiler/server/routes/profile.ts b/x-pack/plugins/searchprofiler/server/routes/profile.ts index 4af3f0519cbc0..914c688a080f8 100644 --- a/x-pack/plugins/searchprofiler/server/routes/profile.ts +++ b/x-pack/plugins/searchprofiler/server/routes/profile.ts @@ -46,7 +46,7 @@ export const register = ({ router, getLicenseStatus, log }: RouteDependencies) = body: JSON.stringify(parsed, null, 2), }; try { - const resp = await elasticsearch.dataClient.callAsCurrentUser('search', body); + const resp = await elasticsearch.legacy.client.callAsCurrentUser('search', body); return response.ok({ body: { ok: true, diff --git a/x-pack/plugins/siem/common/endpoint/types.ts b/x-pack/plugins/siem/common/endpoint/types.ts index 3a86014c57148..6d04f1dfac38f 100644 --- a/x-pack/plugins/siem/common/endpoint/types.ts +++ b/x-pack/plugins/siem/common/endpoint/types.ts @@ -6,11 +6,19 @@ import { Datasource, NewDatasource } from '../../../ingest_manager/common'; +/** + * Object that allows you to maintain stateful information in the location object across navigation events + * + */ + export interface AppLocation { pathname: string; search: string; hash: string; key?: string; + state?: { + isTabChange?: boolean; + }; } /** diff --git a/x-pack/plugins/siem/common/endpoint_alerts/types.ts b/x-pack/plugins/siem/common/endpoint_alerts/types.ts index 2ba8353b09c88..2df92b43ab52a 100644 --- a/x-pack/plugins/siem/common/endpoint_alerts/types.ts +++ b/x-pack/plugins/siem/common/endpoint_alerts/types.ts @@ -175,6 +175,10 @@ export interface AlertingIndexUIQueryParams { * If any value is present, show the alert detail view for the selected alert. Should be an ID for an alert event. */ selected_alert?: string; + /** + * Retain the selected tab through any refreshes. Should be an ID for an alert event. + */ + active_details_tab?: string; query?: string; date_range?: string; filters?: string; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list_pagination.test.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list_pagination.test.ts index 6c6a15cfc0be1..1fe27b842c528 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list_pagination.test.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/alert_list_pagination.test.ts @@ -23,12 +23,13 @@ import { alertMiddlewareFactory } from './middleware'; import { alertListReducer } from './reducer'; import { uiQueryParams } from './selectors'; import { urlFromQueryParams } from '../view/url_from_query_params'; +import { AppLocation } from './../../../common/endpoint/types'; describe('alert list pagination', () => { let store: Store; let coreStart: ReturnType; let depsStart: DepsStartMock; - let history: History; + let history: History; let queryParams: () => AlertingIndexUIQueryParams; /** * Update the history with a new `AlertingIndexUIQueryParams` diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts index e6d3444c7a067..b8e8d36801d48 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/middleware.ts @@ -13,7 +13,13 @@ import { import { AlertConstants } from '../../../common/endpoint_alerts/alert_constants'; import { ImmutableMiddlewareFactory } from '../../common/store'; import { cloneHttpFetchQuery } from '../../common/utils/clone_http_fetch_query'; -import { isOnAlertPage, apiQueryParams, hasSelectedAlert, uiQueryParams } from './selectors'; +import { + isOnAlertPage, + apiQueryParams, + hasSelectedAlert, + uiQueryParams, + isAlertPageTabChange, +} from './selectors'; import { Immutable } from '../../../common/endpoint/types'; export const alertMiddlewareFactory: ImmutableMiddlewareFactory> = ( @@ -39,22 +45,22 @@ export const alertMiddlewareFactory: ImmutableMiddlewareFactory (next) => async (action) => { next(action); const state = api.getState(); - if (action.type === 'userChangedUrl' && isOnAlertPage(state)) { + if (action.type === 'userChangedUrl' && isOnAlertPage(state) && !isAlertPageTabChange(state)) { const patterns = await fetchIndexPatterns(); api.dispatch({ type: 'serverReturnedSearchBarIndexPatterns', payload: patterns }); - const response: AlertResultList = await coreStart.http.get(`/api/endpoint/alerts`, { + const listResponse: AlertResultList = await coreStart.http.get(`/api/endpoint/alerts`, { query: cloneHttpFetchQuery(apiQueryParams(state)), }); - api.dispatch({ type: 'serverReturnedAlertsData', payload: response }); - } + api.dispatch({ type: 'serverReturnedAlertsData', payload: listResponse }); - if (action.type === 'userChangedUrl' && isOnAlertPage(state) && hasSelectedAlert(state)) { - const uiParams = uiQueryParams(state); - const response: AlertDetails = await coreStart.http.get( - `/api/endpoint/alerts/${uiParams.selected_alert}` - ); - api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: response }); + if (hasSelectedAlert(state)) { + const uiParams = uiQueryParams(state); + const detailsResponse: AlertDetails = await coreStart.http.get( + `/api/endpoint/alerts/${uiParams.selected_alert}` + ); + api.dispatch({ type: 'serverReturnedAlertDetailsData', payload: detailsResponse }); + } } }; }; diff --git a/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts b/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts index d5ed733fdad8d..bec524f948d76 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts +++ b/x-pack/plugins/siem/public/endpoint_alerts/store/selectors.ts @@ -47,6 +47,13 @@ export const isOnAlertPage = (state: Immutable): boolean => { return state.location ? state.location.pathname === '/endpoint-alerts' : false; }; +/** + * Returns a boolean based on whether or not the user navigated within the alerts page + */ +export const isAlertPageTabChange = (state: Immutable): boolean => { + return isOnAlertPage(state) && state.location?.state?.isTabChange === true; +}; + /** * Returns the query object received from parsing the browsers URL query params. * Used to calculate urls for links and such. @@ -68,6 +75,7 @@ export const uiQueryParams: ( const keys: Array = [ 'page_size', 'page_index', + 'active_details_tab', 'selected_alert', 'query', 'date_range', @@ -173,3 +181,10 @@ export const hasSelectedAlert: (state: Immutable) => boolean = c uiQueryParams, ({ selected_alert: selectedAlert }) => selectedAlert !== undefined ); + +export const selectedAlertDetailsTabId: ( + state: Immutable +) => string | undefined = createSelector( + uiQueryParams, + ({ active_details_tab: activeDetailsTab }) => activeDetailsTab +); diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx index cb44176d6b4c3..a4fe4811fa602 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/alert_details.test.tsx @@ -59,6 +59,20 @@ describe('when the alert details flyout is open', () => { await renderResult.findByTestId('alertDetailTakeActionWhitelistButton'); }); }); + describe('when the user navigates to the resolver tab', () => { + beforeEach(() => { + reactTestingLibrary.act(() => { + history.push({ + ...history.location, + search: '?selected_alert=1&active_details_tab=overviewResolver', + }); + }); + }); + it('should show the resolver view', async () => { + const resolver = await render().findByTestId('alertResolver'); + expect(resolver).toBeInTheDocument(); + }); + }); describe('when the user navigates to the overview tab', () => { let renderResult: reactTestingLibrary.RenderResult; beforeEach(async () => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/details/overview/index.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/details/overview/index.tsx index 4a51767150fdd..0af554b6343ce 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/details/overview/index.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/details/overview/index.tsx @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useMemo } from 'react'; +import React, { memo, useMemo, useCallback } from 'react'; import styled from 'styled-components'; +import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -22,9 +23,13 @@ import { FormattedDate } from '../../formatted_date'; import { AlertDetailResolver } from '../../resolver'; import { ResolverEvent } from '../../../../../common/endpoint/types'; import { TakeActionDropdown } from './take_action_dropdown'; +import { urlFromQueryParams } from '../../url_from_query_params'; const AlertDetailsOverviewComponent = memo(() => { + const history = useHistory(); const alertDetailsData = useAlertListSelector(selectors.selectedAlertDetailsData); + const alertDetailsTabId = useAlertListSelector(selectors.selectedAlertDetailsTabId); + const queryParams = useAlertListSelector(selectors.uiQueryParams); if (alertDetailsData === undefined) { return null; } @@ -66,6 +71,25 @@ const AlertDetailsOverviewComponent = memo(() => { ]; }, [alertDetailsData]); + const activeTab = useMemo( + () => (alertDetailsTabId ? tabs.find(({ id }) => id === alertDetailsTabId) : tabs[0]), + [alertDetailsTabId, tabs] + ); + + const handleTabClick = useCallback( + (clickedTab: EuiTabbedContentTab): void => { + if (clickedTab.id !== alertDetailsTabId) { + const locationObject = urlFromQueryParams({ + ...queryParams, + active_details_tab: clickedTab.id, + }); + locationObject.state = { isTabChange: true }; + history.push(locationObject); + } + }, + [alertDetailsTabId] + ); + return ( <>
@@ -110,7 +134,7 @@ const AlertDetailsOverviewComponent = memo(() => {
- + ); }); diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx index 6d7350f20d21f..4967d7661a085 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/index.test.tsx @@ -99,6 +99,12 @@ describe('when on the alerting page', () => { it('should no longer show the flyout', () => { expect(render().queryByTestId('alertDetailFlyout')).toBeNull(); }); + it('should no longer track flyout state in url', () => { + const unexpectedTabString = 'active_details_tab'; + const unexpectedAlertString = 'selected_alert'; + expect(history.location.search).toEqual(expect.not.stringContaining(unexpectedTabString)); + expect(history.location.search).toEqual(expect.not.stringContaining(unexpectedAlertString)); + }); }); }); describe('when the url has page_size=1 and a page_index=1', () => { diff --git a/x-pack/plugins/siem/public/endpoint_alerts/view/index.tsx b/x-pack/plugins/siem/public/endpoint_alerts/view/index.tsx index b80d113eaee15..ff65dd2bda6a2 100644 --- a/x-pack/plugins/siem/public/endpoint_alerts/view/index.tsx +++ b/x-pack/plugins/siem/public/endpoint_alerts/view/index.tsx @@ -120,8 +120,8 @@ export const AlertIndex = memo(() => { const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }) => id)); const handleFlyoutClose = useCallback(() => { - const { selected_alert, ...paramsWithoutSelectedAlert } = queryParams; - history.push(urlFromQueryParams(paramsWithoutSelectedAlert)); + const { active_details_tab, selected_alert, ...paramsWithoutFlyoutDetails } = queryParams; + history.push(urlFromQueryParams(paramsWithoutFlyoutDetails)); }, [history, queryParams]); const timestampForRows: Map = useMemo(() => { diff --git a/x-pack/plugins/siem/public/endpoint_hosts/store/host_pagination.test.ts b/x-pack/plugins/siem/public/endpoint_hosts/store/host_pagination.test.ts index 957a5600ef4c2..17feacb0a767a 100644 --- a/x-pack/plugins/siem/public/endpoint_hosts/store/host_pagination.test.ts +++ b/x-pack/plugins/siem/public/endpoint_hosts/store/host_pagination.test.ts @@ -10,7 +10,7 @@ import { applyMiddleware, Store, createStore } from 'redux'; import { coreMock } from '../../../../../../src/core/public/mocks'; -import { HostResultList } from '../../../common/endpoint/types'; +import { HostResultList, AppLocation } from '../../../common/endpoint/types'; import { DepsStartMock, depsStartMock } from '../../common/mock/endpoint'; import { hostMiddlewareFactory } from './middleware'; @@ -27,7 +27,7 @@ describe('host list pagination: ', () => { let fakeCoreStart: jest.Mocked; let depsStart: DepsStartMock; let fakeHttpServices: jest.Mocked; - let history: History; + let history: History; let store: Store; let queryParams: () => HostIndexUIQueryParams; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; diff --git a/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/index.ts b/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/index.ts index 18eb3ac417a3c..a4b07df6b8f8b 100644 --- a/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/index.ts +++ b/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/index.ts @@ -22,7 +22,7 @@ export const alertDetailsHandlerWrapper = function ( ) => { try { const alertId = AlertId.fromEncoded(req.params.id); - const response = (await ctx.core.elasticsearch.dataClient.callAsCurrentUser('get', { + const response = (await ctx.core.elasticsearch.legacy.client.callAsCurrentUser('get', { index: alertId.index, id: alertId.id, })) as GetResponse; diff --git a/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/lib/pagination.ts b/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/lib/pagination.ts index e12bd9e4c8de9..8326f16478f64 100644 --- a/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/lib/pagination.ts +++ b/x-pack/plugins/siem/server/endpoint/alerts/handlers/details/lib/pagination.ts @@ -56,7 +56,7 @@ export class AlertDetailsPagination extends Pagination< } const response = await searchESForAlerts( - this.requestContext.core.elasticsearch.dataClient, + this.requestContext.core.elasticsearch.legacy.client, reqData, this.indexPattern ); diff --git a/x-pack/plugins/siem/server/endpoint/alerts/handlers/list/index.ts b/x-pack/plugins/siem/server/endpoint/alerts/handlers/list/index.ts index ccb234fe9adc3..6eda863408b28 100644 --- a/x-pack/plugins/siem/server/endpoint/alerts/handlers/list/index.ts +++ b/x-pack/plugins/siem/server/endpoint/alerts/handlers/list/index.ts @@ -23,7 +23,7 @@ export const alertListHandlerWrapper = function ( .getEventIndexPattern(ctx); const reqData = await getRequestData(req, endpointAppContext); const response = await searchESForAlerts( - ctx.core.elasticsearch.dataClient, + ctx.core.elasticsearch.legacy.client, reqData, indexPattern ); diff --git a/x-pack/plugins/siem/server/endpoint/mocks.ts b/x-pack/plugins/siem/server/endpoint/mocks.ts index 6260a6c630643..d1873cb18ce0a 100644 --- a/x-pack/plugins/siem/server/endpoint/mocks.ts +++ b/x-pack/plugins/siem/server/endpoint/mocks.ts @@ -4,11 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - IScopedClusterClient, - RequestHandlerContext, - SavedObjectsClientContract, -} from 'kibana/server'; +import { IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server'; +import { xpackMocks } from '../../../../mocks'; import { AgentService, IngestManagerStartContract } from '../../../ingest_manager/server'; import { IndexPatternRetriever } from './alerts/index_pattern'; @@ -68,19 +65,8 @@ export function createRouteHandlerContext( dataClient: jest.Mocked, savedObjectsClient: jest.Mocked ) { - return ({ - core: { - elasticsearch: { - dataClient, - }, - savedObjects: { - client: savedObjectsClient, - }, - }, - /** - * Using unknown here because the object defined is not a full `RequestHandlerContext`. We don't - * need all of the fields required to run the tests, but the `routeHandler` function requires a - * `RequestHandlerContext`. - */ - } as unknown) as RequestHandlerContext; + const context = xpackMocks.createRequestHandlerContext(); + context.core.elasticsearch.legacy.client = dataClient; + context.core.savedObjects.client = savedObjectsClient; + return context; } diff --git a/x-pack/plugins/siem/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/siem/server/endpoint/routes/metadata/index.ts index f88fcbd9e5ead..d351054ca2fd8 100644 --- a/x-pack/plugins/siem/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/siem/server/endpoint/routes/metadata/index.ts @@ -75,7 +75,7 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp endpointAppContext, index ); - const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( + const response = (await context.core.elasticsearch.legacy.client.callAsCurrentUser( 'search', queryParams )) as SearchResponse; @@ -129,7 +129,7 @@ export async function getHostData( .getIndexPatternRetriever() .getMetadataIndexPattern(metadataRequestContext.requestHandlerContext); const query = getESQueryHostMetadataByID(id, index); - const response = (await metadataRequestContext.requestHandlerContext.core.elasticsearch.dataClient.callAsCurrentUser( + const response = (await metadataRequestContext.requestHandlerContext.core.elasticsearch.legacy.client.callAsCurrentUser( 'search', query )) as SearchResponse; diff --git a/x-pack/plugins/siem/server/endpoint/routes/policy/handlers.ts b/x-pack/plugins/siem/server/endpoint/routes/policy/handlers.ts index c611e13e6c9c0..beb76a8051c35 100644 --- a/x-pack/plugins/siem/server/endpoint/routes/policy/handlers.ts +++ b/x-pack/plugins/siem/server/endpoint/routes/policy/handlers.ts @@ -21,7 +21,7 @@ export const getHostPolicyResponseHandler = function ( const doc = await getPolicyResponseByHostId( index, request.query.hostId, - context.core.elasticsearch.dataClient + context.core.elasticsearch.legacy.client ); if (doc) { diff --git a/x-pack/plugins/siem/server/endpoint/routes/resolver/ancestry.ts b/x-pack/plugins/siem/server/endpoint/routes/resolver/ancestry.ts index a2af73da454ad..af3fe8d434cbc 100644 --- a/x-pack/plugins/siem/server/endpoint/routes/resolver/ancestry.ts +++ b/x-pack/plugins/siem/server/endpoint/routes/resolver/ancestry.ts @@ -22,7 +22,7 @@ export function handleAncestry( try { const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); - const client = context.core.elasticsearch.dataClient; + const client = context.core.elasticsearch.legacy.client; const indexPattern = await indexRetriever.getEventIndexPattern(context); const fetcher = new Fetcher(client, id, indexPattern, endpointID); diff --git a/x-pack/plugins/siem/server/endpoint/routes/resolver/children.ts b/x-pack/plugins/siem/server/endpoint/routes/resolver/children.ts index 4eb25b03acb45..a8156e9729579 100644 --- a/x-pack/plugins/siem/server/endpoint/routes/resolver/children.ts +++ b/x-pack/plugins/siem/server/endpoint/routes/resolver/children.ts @@ -23,7 +23,7 @@ export function handleChildren( const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); const indexPattern = await indexRetriever.getEventIndexPattern(context); - const client = context.core.elasticsearch.dataClient; + const client = context.core.elasticsearch.legacy.client; const fetcher = new Fetcher(client, id, indexPattern, endpointID); return res.ok({ diff --git a/x-pack/plugins/siem/server/endpoint/routes/resolver/events.ts b/x-pack/plugins/siem/server/endpoint/routes/resolver/events.ts index ac720b7569746..f739f9a1ca2e6 100644 --- a/x-pack/plugins/siem/server/endpoint/routes/resolver/events.ts +++ b/x-pack/plugins/siem/server/endpoint/routes/resolver/events.ts @@ -21,7 +21,7 @@ export function handleEvents( } = req; try { const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); - const client = context.core.elasticsearch.dataClient; + const client = context.core.elasticsearch.legacy.client; const indexPattern = await indexRetriever.getEventIndexPattern(context); const fetcher = new Fetcher(client, id, indexPattern, endpointID); diff --git a/x-pack/plugins/siem/server/endpoint/routes/resolver/tree.ts b/x-pack/plugins/siem/server/endpoint/routes/resolver/tree.ts index b0c8b4411991e..d750fb256a4a0 100644 --- a/x-pack/plugins/siem/server/endpoint/routes/resolver/tree.ts +++ b/x-pack/plugins/siem/server/endpoint/routes/resolver/tree.ts @@ -29,7 +29,7 @@ export function handleTree( }, } = req; try { - const client = context.core.elasticsearch.dataClient; + const client = context.core.elasticsearch.legacy.client; const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); const indexPattern = await indexRetriever.getEventIndexPattern(context); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts index 5f764e1f371ae..a24375c368d63 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_context.ts @@ -30,7 +30,9 @@ const createRequestContextMock = ( alerting: { getAlertsClient: jest.fn(() => clients.alertsClient) }, core: { ...coreContext, - elasticsearch: { ...coreContext.elasticsearch, dataClient: clients.clusterClient }, + elasticsearch: { + legacy: { ...coreContext.elasticsearch, client: clients.clusterClient }, + }, savedObjects: { client: clients.savedObjectsClient }, }, licensing: clients.licensing, diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts index 20b8ad29d2715..02a2545a93826 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/index/create_index_route.ts @@ -29,7 +29,7 @@ export const createIndexRoute = (router: IRouter) => { const siemResponse = buildSiemResponse(response); try { - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.siem?.getSiemClient(); const callCluster = clusterClient.callAsCurrentUser; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts index 79cf4851f9ab8..9d03accff0c4a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/index/delete_index_route.ts @@ -37,7 +37,7 @@ export const deleteIndexRoute = (router: IRouter) => { const siemResponse = buildSiemResponse(response); try { - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.siem?.getSiemClient(); if (!siemClient) { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts index 2b418892f0f39..5c2ffe53b9864 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/index/read_index_route.ts @@ -22,7 +22,7 @@ export const readIndexRoute = (router: IRouter) => { const siemResponse = buildSiemResponse(response); try { - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.siem?.getSiemClient(); if (!siemClient) { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index e3c41c555f297..011d01f841ee7 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -29,7 +29,7 @@ export const readPrivilegesRoute = ( const siemResponse = buildSiemResponse(response); try { - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.siem?.getSiemClient(); if (!siemClient) { diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index ff57ec969997e..83dd87002e8f9 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -34,7 +34,7 @@ export const addPrepackedRulesRoute = (router: IRouter) => { try { const alertsClient = context.alerting?.getAlertsClient(); - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const savedObjectsClient = context.core.savedObjects.client; const siemClient = context.siem?.getSiemClient(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index ec81804edd9ef..ff6d212deb584 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -41,7 +41,7 @@ export const createRulesBulkRoute = (router: IRouter, ml: SetupPlugins['ml']) => async (context, request, response) => { const siemResponse = buildSiemResponse(response); const alertsClient = context.alerting?.getAlertsClient(); - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const savedObjectsClient = context.core.savedObjects.client; const siemClient = context.siem?.getSiemClient(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 7cbb22221679a..4ddecd94676cb 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -69,7 +69,7 @@ export const createRulesRoute = (router: IRouter, ml: SetupPlugins['ml']): void try { const alertsClient = context.alerting?.getAlertsClient(); - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const savedObjectsClient = context.core.savedObjects.client; const siemClient = context.siem?.getSiemClient(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 96aac588c7db4..901e7403bb953 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -61,7 +61,7 @@ export const importRulesRoute = (router: IRouter, config: ConfigType, ml: SetupP try { const alertsClient = context.alerting?.getAlertsClient(); - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const savedObjectsClient = context.core.savedObjects.client; const siemClient = context.siem?.getSiemClient(); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index bcb70b6b4f0dd..0d174edb0e36b 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -23,7 +23,7 @@ export const setSignalsStatusRoute = (router: IRouter) => { }, async (context, request, response) => { const { signal_ids: signalIds, query, status } = request.body; - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.siem?.getSiemClient(); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts b/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts index 41896c725b903..5153ef2048c99 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/routes/signals/query_signals_route.ts @@ -23,7 +23,7 @@ export const querySignalsRoute = (router: IRouter) => { }, async (context, request, response) => { const { query, aggs, _source, track_total_hits, size } = request.body; - const clusterClient = context.core.elasticsearch.dataClient; + const clusterClient = context.core.elasticsearch.legacy.client; const siemClient = context.siem!.getSiemClient(); const siemResponse = buildSiemResponse(response); diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/find_ml_signals.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/find_ml_signals.ts index 342976f3fd0fc..e95b713105fc6 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/find_ml_signals.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/find_ml_signals.ts @@ -27,14 +27,14 @@ export const findMlSignals = async ({ from: string; to: string; }) => { - const { mlSearch } = ml.mlSystemProvider(callCluster, request); + const { mlAnomalySearch } = ml.mlSystemProvider(callCluster, request); const params = { jobIds: [jobId], threshold: anomalyThreshold, earliestMs: dateMath.parse(from)?.valueOf() ?? 0, latestMs: dateMath.parse(to)?.valueOf() ?? 0, }; - const relevantAnomalies = await getAnomalies(params, mlSearch); + const relevantAnomalies = await getAnomalies(params, mlAnomalySearch); return relevantAnomalies; }; diff --git a/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 24cb9102915f8..8cef4c8ea0e6a 100644 --- a/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -161,7 +161,7 @@ export const signalRulesAlertType = ({ ml, callCluster: scopedMlCallCluster, // This is needed to satisfy the ML Services API, but can be empty as it is - // currently unused by the mlSearch function. + // currently unused by the mlAnomalySearch function. request: ({} as unknown) as KibanaRequest, jobId: machineLearningJobId, anomalyThreshold, diff --git a/x-pack/plugins/siem/server/lib/framework/kibana_framework_adapter.ts b/x-pack/plugins/siem/server/lib/framework/kibana_framework_adapter.ts index 1e99c40ef5727..d34703cb27cdf 100644 --- a/x-pack/plugins/siem/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/siem/server/lib/framework/kibana_framework_adapter.ts @@ -49,7 +49,7 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { ? await uiSettings.client.get('courier:maxConcurrentShardRequests') : 0; - return elasticsearch.dataClient.callAsCurrentUser(endpoint, { + return elasticsearch.legacy.client.callAsCurrentUser(endpoint, { ...params, ignore_throttled: !includeFrozen, ...(maxConcurrentShardRequests > 0 diff --git a/x-pack/plugins/siem/server/lib/machine_learning/authz.test.ts b/x-pack/plugins/siem/server/lib/machine_learning/authz.test.ts index 93c3a74c71378..93aa6fca87607 100644 --- a/x-pack/plugins/siem/server/lib/machine_learning/authz.test.ts +++ b/x-pack/plugins/siem/server/lib/machine_learning/authz.test.ts @@ -173,7 +173,7 @@ describe('mlAuthz', () => { const mockMlCapabilities = jest.fn(); mlMock.mlSystemProvider.mockImplementation(() => ({ mlInfo: jest.fn(), - mlSearch: jest.fn(), + mlAnomalySearch: jest.fn(), mlCapabilities: mockMlCapabilities, })); @@ -194,7 +194,7 @@ describe('mlAuthz', () => { const mockMlCapabilities = jest.fn(); mlMock.mlSystemProvider.mockImplementation(() => ({ mlInfo: jest.fn(), - mlSearch: jest.fn(), + mlAnomalySearch: jest.fn(), mlCapabilities: mockMlCapabilities, })); diff --git a/x-pack/plugins/siem/server/lib/machine_learning/index.test.ts b/x-pack/plugins/siem/server/lib/machine_learning/index.test.ts index 35a080f5ade76..63e3f3487e482 100644 --- a/x-pack/plugins/siem/server/lib/machine_learning/index.test.ts +++ b/x-pack/plugins/siem/server/lib/machine_learning/index.test.ts @@ -26,17 +26,17 @@ describe('getAnomalies', () => { }; }); - it('calls the provided mlSearch function', () => { - const mockMlSearch = jest.fn(); - getAnomalies(searchParams, mockMlSearch); + it('calls the provided mlAnomalySearch function', () => { + const mockMlAnomalySearch = jest.fn(); + getAnomalies(searchParams, mockMlAnomalySearch); - expect(mockMlSearch).toHaveBeenCalled(); + expect(mockMlAnomalySearch).toHaveBeenCalled(); }); it('passes anomalyThreshold as part of the query', () => { - const mockMlSearch = jest.fn(); - getAnomalies(searchParams, mockMlSearch); - const filters = getFiltersFromMock(mockMlSearch); + const mockMlAnomalySearch = jest.fn(); + getAnomalies(searchParams, mockMlAnomalySearch); + const filters = getFiltersFromMock(mockMlAnomalySearch); const criteria = getBoolCriteriaFromFilters(filters); expect(criteria).toEqual( @@ -45,9 +45,9 @@ describe('getAnomalies', () => { }); it('passes time range as part of the query', () => { - const mockMlSearch = jest.fn(); - getAnomalies(searchParams, mockMlSearch); - const filters = getFiltersFromMock(mockMlSearch); + const mockMlAnomalySearch = jest.fn(); + getAnomalies(searchParams, mockMlAnomalySearch); + const filters = getFiltersFromMock(mockMlAnomalySearch); const criteria = getBoolCriteriaFromFilters(filters); expect(criteria).toEqual( @@ -66,9 +66,9 @@ describe('getAnomalies', () => { }); it('passes a single jobId as part of the query', () => { - const mockMlSearch = jest.fn(); - getAnomalies(searchParams, mockMlSearch); - const filters = getFiltersFromMock(mockMlSearch); + const mockMlAnomalySearch = jest.fn(); + getAnomalies(searchParams, mockMlAnomalySearch); + const filters = getFiltersFromMock(mockMlAnomalySearch); const criteria = getBoolCriteriaFromFilters(filters); expect(criteria).toEqual( @@ -84,10 +84,10 @@ describe('getAnomalies', () => { }); it('passes multiple jobIds as part of the query', () => { - const mockMlSearch = jest.fn(); + const mockMlAnomalySearch = jest.fn(); searchParams.jobIds = ['jobId1', 'jobId2']; - getAnomalies(searchParams, mockMlSearch); - const filters = getFiltersFromMock(mockMlSearch); + getAnomalies(searchParams, mockMlAnomalySearch); + const filters = getFiltersFromMock(mockMlAnomalySearch); const criteria = getBoolCriteriaFromFilters(filters); expect(criteria).toEqual( diff --git a/x-pack/plugins/siem/server/lib/machine_learning/index.ts b/x-pack/plugins/siem/server/lib/machine_learning/index.ts index 5ff164a3f778c..ad2f1e5a8285c 100644 --- a/x-pack/plugins/siem/server/lib/machine_learning/index.ts +++ b/x-pack/plugins/siem/server/lib/machine_learning/index.ts @@ -10,7 +10,7 @@ import { AnomalyRecordDoc as Anomaly } from '../../../../ml/server'; export { Anomaly }; export type AnomalyResults = SearchResponse; -type MlSearch = (searchParams: SearchParams) => Promise>; +type MlAnomalySearch = (searchParams: SearchParams) => Promise>; export interface AnomaliesSearchParams { jobIds: string[]; @@ -22,11 +22,11 @@ export interface AnomaliesSearchParams { export const getAnomalies = async ( params: AnomaliesSearchParams, - mlSearch: MlSearch + mlAnomalySearch: MlAnomalySearch ): Promise => { const boolCriteria = buildCriteria(params); - return mlSearch({ + return mlAnomalySearch({ size: params.maxRecords || 100, body: { query: { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts index 51cb776ef4e0b..8cb296719c19e 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts @@ -6,6 +6,7 @@ import { SemVer } from 'semver'; import { IScopedClusterClient, kibanaResponseFactory } from 'src/core/server'; +import { xpackMocks } from '../../../../mocks'; import { CURRENT_VERSION } from '../../common/version'; import { esVersionCheck, @@ -74,35 +75,27 @@ describe('EsVersionPrecheck', () => { it('returns a 403 when callCluster fails with a 403', async () => { const fakeCall = jest.fn().mockRejectedValue({ status: 403 }); - const ctx = { - core: { - elasticsearch: { - adminClient: { - callAsInternalUser: fakeCall, - }, - }, - }, - } as any; + const ctx = xpackMocks.createRequestHandlerContext(); + ctx.core.elasticsearch.legacy.client = { + callAsCurrentUser: jest.fn(), + callAsInternalUser: fakeCall, + }; const result = await esVersionCheck(ctx, kibanaResponseFactory); expect(result).toHaveProperty('status', 403); }); it('returns a 426 message w/ allNodesUpgraded = false when nodes are not on same version', async () => { - const ctx = { - core: { - elasticsearch: { - adminClient: { - callAsInternalUser: jest.fn().mockResolvedValue({ - nodes: { - node1: { version: CURRENT_VERSION.raw }, - node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - }, - }), - }, + const ctx = xpackMocks.createRequestHandlerContext(); + ctx.core.elasticsearch.legacy.client = { + callAsCurrentUser: jest.fn(), + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: CURRENT_VERSION.raw }, + node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, }, - }, - } as any; + }), + }; const result = await esVersionCheck(ctx, kibanaResponseFactory); expect(result).toHaveProperty('status', 426); @@ -110,20 +103,16 @@ describe('EsVersionPrecheck', () => { }); it('returns a 426 message w/ allNodesUpgraded = true when nodes are on next version', async () => { - const ctx = { - core: { - elasticsearch: { - adminClient: { - callAsInternalUser: jest.fn().mockResolvedValue({ - nodes: { - node1: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - }, - }), - }, + const ctx = xpackMocks.createRequestHandlerContext(); + ctx.core.elasticsearch.legacy.client = { + callAsCurrentUser: jest.fn(), + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, }, - }, - } as any; + }), + }; const result = await esVersionCheck(ctx, kibanaResponseFactory); expect(result).toHaveProperty('status', 426); @@ -131,20 +120,16 @@ describe('EsVersionPrecheck', () => { }); it('returns undefined when nodes are on same version', async () => { - const ctx = { - core: { - elasticsearch: { - adminClient: { - callAsInternalUser: jest.fn().mockResolvedValue({ - nodes: { - node1: { version: CURRENT_VERSION.raw }, - node2: { version: CURRENT_VERSION.raw }, - }, - }), - }, + const ctx = xpackMocks.createRequestHandlerContext(); + ctx.core.elasticsearch.legacy.client = { + callAsCurrentUser: jest.fn(), + callAsInternalUser: jest.fn().mockResolvedValue({ + nodes: { + node1: { version: CURRENT_VERSION.raw }, + node2: { version: CURRENT_VERSION.raw }, }, - }, - } as any; + }), + }; await expect(esVersionCheck(ctx, kibanaResponseFactory)).resolves.toBe(undefined); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts index 37c3fd34d53d2..e12f501ba02be 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts @@ -62,11 +62,11 @@ export const esVersionCheck = async ( ctx: RequestHandlerContext, response: KibanaResponseFactory ) => { - const { adminClient } = ctx.core.elasticsearch; + const { client } = ctx.core.elasticsearch.legacy; let allNodeVersions: SemVer[]; try { - allNodeVersions = await getAllNodeVersions(adminClient); + allNodeVersions = await getAllNodeVersions(client); } catch (e) { if (e.status === 403) { return response.forbidden({ body: e.message }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts index a8be171dccd98..861ef2d3968dc 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts @@ -12,8 +12,9 @@ import { export const routeHandlerContextMock = ({ core: { elasticsearch: { - adminClient: elasticsearchServiceMock.createScopedClusterClient(), - dataClient: elasticsearchServiceMock.createScopedClusterClient(), + legacy: { + client: elasticsearchServiceMock.createScopedClusterClient(), + }, }, savedObjects: { client: savedObjectsClientMock.create() }, }, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts index fa4649f1c5dcd..ff673adaf1642 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts @@ -23,16 +23,18 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: { core: { savedObjects: { client: savedObjectsClient }, - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client }, + }, }, }, request, response ) => { try { - const status = await getUpgradeAssistantStatus(dataClient, isCloudEnabled); + const status = await getUpgradeAssistantStatus(client, isCloudEnabled); - const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const callAsCurrentUser = client.callAsCurrentUser.bind(client); const reindexActions = reindexActionsFactory(savedObjectsClient, callAsCurrentUser); const reindexService = reindexServiceFactory( callAsCurrentUser, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index 845a0238f7918..b8021eeef75e6 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -37,7 +37,7 @@ describe('deprecation logging API', () => { describe('GET /api/upgrade_assistant/deprecation_logging', () => { it('returns isEnabled', async () => { - (routeHandlerContextMock.core.elasticsearch.dataClient + (routeHandlerContextMock.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock).mockResolvedValue({ default: { logger: { deprecation: 'WARN' } }, }); @@ -51,7 +51,7 @@ describe('deprecation logging API', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.dataClient + (routeHandlerContextMock.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock).mockRejectedValue(new Error(`scary error!`)); const resp = await routeDependencies.router.getHandler({ method: 'get', @@ -64,7 +64,7 @@ describe('deprecation logging API', () => { describe('PUT /api/upgrade_assistant/deprecation_logging', () => { it('returns isEnabled', async () => { - (routeHandlerContextMock.core.elasticsearch.dataClient + (routeHandlerContextMock.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock).mockResolvedValue({ default: { logger: { deprecation: 'ERROR' } }, }); @@ -77,7 +77,7 @@ describe('deprecation logging API', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.dataClient + (routeHandlerContextMock.core.elasticsearch.legacy.client .callAsCurrentUser as jest.Mock).mockRejectedValue(new Error(`scary error!`)); const resp = await routeDependencies.router.getHandler({ method: 'put', diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts index 739a789c95ce0..38f244a4e1154 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -23,14 +23,16 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) async ( { core: { - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client }, + }, }, }, request, response ) => { try { - const result = await getDeprecationLoggingStatus(dataClient); + const result = await getDeprecationLoggingStatus(client); return response.ok({ body: result }); } catch (e) { return response.internalError({ body: e }); @@ -52,7 +54,9 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) async ( { core: { - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client }, + }, }, }, request, @@ -61,7 +65,7 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) try { const { isEnabled } = request.body as { isEnabled: boolean }; return response.ok({ - body: await setDeprecationLogging(dataClient, isEnabled), + body: await setDeprecationLogging(client, isEnabled), }); } catch (e) { return response.internalError({ body: e }); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index 21916c7376628..c745def5e8936 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -100,7 +100,9 @@ export function registerReindexIndicesRoutes( { core: { savedObjects: { client: savedObjectsClient }, - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client: esClient }, + }, }, }, request, @@ -110,7 +112,7 @@ export function registerReindexIndicesRoutes( try { const result = await reindexHandler({ savedObjects: savedObjectsClient, - dataClient, + dataClient: esClient, indexName, log, licensing, @@ -140,7 +142,9 @@ export function registerReindexIndicesRoutes( async ( { core: { - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client: esClient }, + }, savedObjects, }, }, @@ -148,7 +152,7 @@ export function registerReindexIndicesRoutes( response ) => { const { client } = savedObjects; - const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const callAsCurrentUser = esClient.callAsCurrentUser.bind(esClient); const reindexActions = reindexActionsFactory(client, callAsCurrentUser); try { const inProgressOps = await reindexActions.findAllByStatus(ReindexStatus.inProgress); @@ -180,7 +184,9 @@ export function registerReindexIndicesRoutes( { core: { savedObjects: { client: savedObjectsClient }, - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client: esClient }, + }, }, }, request, @@ -195,7 +201,7 @@ export function registerReindexIndicesRoutes( try { const result = await reindexHandler({ savedObjects: savedObjectsClient, - dataClient, + dataClient: esClient, indexName, log, licensing, @@ -239,7 +245,9 @@ export function registerReindexIndicesRoutes( { core: { savedObjects, - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client: esClient }, + }, }, }, request, @@ -247,7 +255,7 @@ export function registerReindexIndicesRoutes( ) => { const { client } = savedObjects; const { indexName } = request.params; - const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const callAsCurrentUser = esClient.callAsCurrentUser.bind(esClient); const reindexActions = reindexActionsFactory(client, callAsCurrentUser); const reindexService = reindexServiceFactory( callAsCurrentUser, @@ -295,7 +303,9 @@ export function registerReindexIndicesRoutes( { core: { savedObjects, - elasticsearch: { dataClient }, + elasticsearch: { + legacy: { client: esClient }, + }, }, }, request, @@ -303,7 +313,7 @@ export function registerReindexIndicesRoutes( ) => { const { indexName } = request.params; const { client } = savedObjects; - const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const callAsCurrentUser = esClient.callAsCurrentUser.bind(esClient); const reindexActions = reindexActionsFactory(client, callAsCurrentUser); const reindexService = reindexServiceFactory( callAsCurrentUser, diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts b/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts index b3c39e5180adf..7fad5051919c4 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts @@ -81,14 +81,13 @@ export const StateType = t.intersection([ export const HistogramPointType = t.type({ timestamp: t.number, - up: t.number, - down: t.number, + up: t.union([t.number, t.undefined]), + down: t.union([t.number, t.undefined]), }); export type HistogramPoint = t.TypeOf; export const HistogramType = t.type({ - count: t.number, points: t.array(HistogramPointType), }); diff --git a/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts index bb729c303fe03..9f153e186420d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/enrich_monitor_groups.ts @@ -6,10 +6,10 @@ import { get, sortBy } from 'lodash'; import { QueryContext } from './query_context'; -import { QUERY } from '../../../../common/constants'; import { Check, Histogram, + HistogramPoint, MonitorSummary, CursorDirection, SortOrder, @@ -294,6 +294,11 @@ const getHistogramForMonitors = async ( query: { bool: { filter: [ + { + range: { + 'summary.down': { gt: 0 }, + }, + }, { terms: { 'monitor.id': monitorIds, @@ -311,25 +316,23 @@ const getHistogramForMonitors = async ( }, }, aggs: { - by_id: { - terms: { - field: 'monitor.id', - size: queryContext.size, + histogram: { + auto_date_histogram: { + field: '@timestamp', + // 12 seems to be a good size for performance given + // long monitor lists of up to 100 on the overview page + buckets: 12, + missing: 0, }, aggs: { - histogram: { - auto_date_histogram: { - field: '@timestamp', - buckets: QUERY.DEFAULT_BUCKET_COUNT, - missing: 0, + by_id: { + terms: { + field: 'monitor.id', + size: Math.max(monitorIds.length, 1), }, aggs: { - status: { - terms: { - field: 'monitor.status', - size: 2, - shard_size: 2, - }, + totalDown: { + sum: { field: 'summary.down' }, }, }, }, @@ -340,32 +343,32 @@ const getHistogramForMonitors = async ( }; const result = await queryContext.search(params); - const buckets: any[] = get(result, 'aggregations.by_id.buckets', []); - return buckets.reduce((map: { [key: string]: any }, item: any) => { - const points = get(item, 'histogram.buckets', []).map((histogram: any) => { - const status = get(histogram, 'status.buckets', []).reduce( - (statuses: { up: number; down: number }, bucket: any) => { - if (bucket.key === 'up') { - statuses.up = bucket.doc_count; - } else if (bucket.key === 'down') { - statuses.down = bucket.doc_count; - } - return statuses; - }, - { up: 0, down: 0 } - ); - return { - timestamp: histogram.key, - ...status, - }; + const histoBuckets: any[] = result.aggregations.histogram.buckets; + const simplified = histoBuckets.map((histoBucket: any): { timestamp: number; byId: any } => { + const byId: { [key: string]: number } = {}; + histoBucket.by_id.buckets.forEach((idBucket: any) => { + byId[idBucket.key] = idBucket.totalDown.value; }); - - map[item.key] = { - count: item.doc_count, - points, + return { + timestamp: parseInt(histoBucket.key, 10), + byId, }; - return map; - }, {}); + }); + + const histosById: { [key: string]: Histogram } = {}; + monitorIds.forEach((id: string) => { + const points: HistogramPoint[] = []; + simplified.forEach((simpleHisto) => { + points.push({ + timestamp: simpleHisto.timestamp, + up: undefined, + down: simpleHisto.byId[id], + }); + }); + histosById[id] = { points }; + }); + + return histosById; }; const cursorDirectionToOrder = (cd: CursorDirection): 'asc' | 'desc' => { diff --git a/x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts b/x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts index 2fb9562028258..1ead81fa7102c 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/monitor_group_iterator.ts @@ -90,7 +90,7 @@ export class MonitorGroupIterator { } while (true) { - const result = await this.attemptBufferMore(CHUNK_SIZE); + const result = await this.attemptBufferMore(size); if (result.gotHit || !result.hasMore) { return; } diff --git a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts index 2503aae671bc2..84a85a54afe13 100644 --- a/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts +++ b/x-pack/plugins/uptime/server/rest_api/uptime_route_wrapper.ts @@ -13,7 +13,7 @@ export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute) => ({ tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], }, handler: async (context, request, response) => { - const { callAsCurrentUser: callES } = context.core.elasticsearch.dataClient; + const { callAsCurrentUser: callES } = context.core.elasticsearch.legacy.client; const { client: savedObjectsClient } = context.core.savedObjects; const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); return uptimeRoute.handler( diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 733bfc83676f9..f3a2d2fa3cd8f 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -27,7 +27,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); loadTestFile(require.resolve('./fleet')); - loadTestFile(require.resolve('./ingest')); + loadTestFile(require.resolve('./ingest_manager')); loadTestFile(require.resolve('./endpoint')); loadTestFile(require.resolve('./ml')); }); diff --git a/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts b/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts index e704a8a938dc2..823c8159a136d 100644 --- a/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts +++ b/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts @@ -46,6 +46,34 @@ export default function ({ getService }: FtrProviderContext) { before(() => esArchiver.load('empty_kibana')); after(() => esArchiver.unload('empty_kibana')); + it('Handles empty responses', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_HIGHLIGHTS_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesHighlightsRequestRT.encode({ + sourceId: 'default', + startTimestamp: KEY_BEFORE_START.time, + endTimestamp: KEY_AFTER_END.time, + highlightTerms: ['some string that does not exist'], + }) + ) + .expect(200); + + const logEntriesHighlightsResponse = pipe( + logEntriesHighlightsResponseRT.decode(body), + fold(throwErrors(createPlainError), identity) + ); + + expect(logEntriesHighlightsResponse.data).to.have.length(1); + + const data = logEntriesHighlightsResponse.data[0]; + + expect(data.entries).to.have.length(0); + expect(data.topCursor).to.be(null); + expect(data.bottomCursor).to.be(null); + }); + it('highlights built-in message column', async () => { const { body } = await supertest .post(LOG_ENTRIES_HIGHLIGHTS_PATH) diff --git a/x-pack/test/api_integration/apis/ingest/policies.ts b/x-pack/test/api_integration/apis/ingest/policies.ts deleted file mode 100644 index 0364f01210f17..0000000000000 --- a/x-pack/test/api_integration/apis/ingest/policies.ts +++ /dev/null @@ -1,59 +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 expect from '@kbn/expect'; - -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - function useFixtures() { - before(async () => { - await esArchiver.loadIfNeeded('ingest/policies'); - }); - after(async () => { - await esArchiver.unload('ingest/policies'); - }); - } - - describe.skip('ingest_policies', () => { - describe('POST /api/ingest/policies', () => { - useFixtures(); - - it('should return a 400 if the request is not valid', async () => { - await supertest.post(`/api/ingest/policies`).set('kbn-xsrf', 'xxx').send({}).expect(400); - }); - - it('should allow to create a new policy', async () => { - const { body: apiResponse } = await supertest - .post(`/api/ingest/policies`) - .set('kbn-xsrf', 'xxx') - .send({ - name: 'Policy from test 1', - description: 'I am a policy', - }) - .expect(200); - - expect(apiResponse.success).to.eql(true); - expect(apiResponse).to.have.keys('success', 'item', 'action'); - expect(apiResponse.item).to.have.keys('id', 'name', 'status', 'description'); - }); - }); - describe('GET /api/ingest/policies', () => { - useFixtures(); - it('should return the list of policies grouped by shared id', async () => { - const { body: apiResponse } = await supertest.get(`/api/ingest/policies`).expect(200); - expect(apiResponse).to.have.keys('success', 'page', 'total', 'list'); - expect(apiResponse.success).to.eql(true); - const policiesIds = (apiResponse.list as Array<{ id: string }>).map((i) => i.id); - expect(policiesIds.length).to.eql(3); - expect(policiesIds).to.contain('1'); - expect(policiesIds).to.contain('3'); - }); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts b/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts new file mode 100644 index 0000000000000..7281e6a06a064 --- /dev/null +++ b/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('ingest_manager_agent_configs', () => { + describe('POST /api/ingest_manager/agent_configs', () => { + it('should work with valid values', async () => { + const { body: apiResponse } = await supertest + .post(`/api/ingest_manager/agent_configs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'TEST', + namespace: 'default', + }) + .expect(200); + + expect(apiResponse.success).to.be(true); + }); + + it('should return a 400 with an invalid namespace', async () => { + await supertest + .post(`/api/ingest_manager/agent_configs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'TEST', + namespace: '', + }) + .expect(400); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/ingest/index.js b/x-pack/test/api_integration/apis/ingest_manager/index.js similarity index 75% rename from x-pack/test/api_integration/apis/ingest/index.js rename to x-pack/test/api_integration/apis/ingest_manager/index.js index 5dac999a86167..1671c423c03c0 100644 --- a/x-pack/test/api_integration/apis/ingest/index.js +++ b/x-pack/test/api_integration/apis/ingest_manager/index.js @@ -5,7 +5,7 @@ */ export default function loadTests({ loadTestFile }) { - describe('Ingest Endpoints', () => { - loadTestFile(require.resolve('./policies')); + describe('Ingest Manager Endpoints', () => { + loadTestFile(require.resolve('./agent_config')); }); } diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index 3a4453e7ae40a..403179ec4ac28 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -23,10 +23,13 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await ml.securityCommon.cleanMlRoles(); await ml.testResources.deleteIndexPattern('kibana_sample_data_logs'); + await ml.testResources.deleteIndexPattern('ft_farequote'); await esArchiver.unload('ml/ecommerce'); await esArchiver.unload('ml/categorization'); await esArchiver.unload('ml/sample_logs'); + await esArchiver.unload('ml/farequote'); + await esArchiver.unload('ml/bm_classification'); await ml.testResources.resetKibanaTimeZone(); }); diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts index 6d407dc6e0330..bcf4e63a9f6eb 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_states_real_data.ts @@ -49,7 +49,7 @@ const checkMonitorStatesResponse = ({ ).to.eql(statuses); (summaries ?? []).forEach((s) => { expect(s.state.url.full).to.be.ok(); - expect(s.histogram?.count).to.be(20); + expect(Array.isArray(s.histogram?.points)).to.be(true); (s.histogram?.points ?? []).forEach((point) => { expect(point.timestamp).to.be.greaterThan(absFrom); expect(point.timestamp).to.be.lessThan(absTo); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts index a0f5e51175e05..68fc37a73b9cd 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/push_case.ts @@ -16,7 +16,7 @@ import { deleteComments, deleteConfiguration, getConfiguration, - getConnector, + getServiceNowConnector, } from '../../../common/lib/utils'; // eslint-disable-next-line import/no-default-export @@ -39,7 +39,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: connector } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') - .send(getConnector()) + .send(getServiceNowConnector()) .expect(200); actionsRemover.add('default', connector.id, 'action', 'actions'); @@ -75,7 +75,7 @@ export default ({ getService }: FtrProviderContext): void => { const { body: connector } = await supertest .post('/api/actions/action') .set('kbn-xsrf', 'true') - .send(getConnector()) + .send(getServiceNowConnector()) .expect(200); actionsRemover.add('default', connector.id, 'action', 'actions'); diff --git a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts index 9160a530d4291..f292f51b377bc 100644 --- a/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts +++ b/x-pack/test/case_api_integration/basic/tests/cases/user_actions/get_all_user_actions.ts @@ -15,12 +15,16 @@ import { deleteComments, deleteConfiguration, getConfiguration, + getServiceNowConnector, } from '../../../../common/lib/utils'; +import { ObjectRemover as ActionsRemover } from '../../../../../alerting_api_integration/common/lib'; + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const actionsRemover = new ActionsRemover(supertest); describe('get_all_user_actions', () => { afterEach(async () => { @@ -28,6 +32,7 @@ export default ({ getService }: FtrProviderContext): void => { await deleteComments(es); await deleteConfiguration(es); await deleteCasesUserActions(es); + await actionsRemover.removeAll(); }); it(`on new case, user action: 'create' should be called with actionFields: ['description', 'status', 'tags', 'title']`, async () => { @@ -264,11 +269,20 @@ export default ({ getService }: FtrProviderContext): void => { }); it(`on new push to service, user action: 'push-to-service' should be called with actionFields: ['pushed']`, async () => { + const { body: connector } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getServiceNowConnector()) + .expect(200); + + actionsRemover.add('default', connector.id, 'action', 'actions'); + const { body: configure } = await supertest .post(CASE_CONFIGURE_URL) .set('kbn-xsrf', 'true') - .send(getConfiguration()) + .send(getConfiguration(connector.id)) .expect(200); + const { body: postedCase } = await supertest .post(CASES_URL) .set('kbn-xsrf', 'true') diff --git a/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts b/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts index 836c76d500034..f5f290476dd0b 100644 --- a/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts +++ b/x-pack/test/case_api_integration/basic/tests/configure/get_connectors.ts @@ -8,12 +8,19 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { CASE_CONFIGURE_CONNECTORS_URL } from '../../../../../plugins/case/common/constants'; +import { ObjectRemover as ActionsRemover } from '../../../../alerting_api_integration/common/lib'; +import { getServiceNowConnector, getJiraConnector } from '../../../common/lib/utils'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const actionsRemover = new ActionsRemover(supertest); describe('get_connectors', () => { + afterEach(async () => { + await actionsRemover.removeAll(); + }); + it('should return an empty find body correctly if no connectors are loaded', async () => { const { body } = await supertest .get(`${CASE_CONFIGURE_CONNECTORS_URL}/_find`) @@ -23,5 +30,54 @@ export default ({ getService }: FtrProviderContext): void => { expect(body).to.eql([]); }); + + it('should return the correct connectors', async () => { + const { body: connectorOne } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getServiceNowConnector()) + .expect(200); + + const { body: connectorTwo } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send({ + name: 'An email action', + actionTypeId: '.email', + config: { + service: '__json', + from: 'bob@example.com', + }, + secrets: { + user: 'bob', + password: 'supersecret', + }, + }) + .expect(200); + + const { body: connectorThree } = await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getJiraConnector()) + .expect(200); + + actionsRemover.add('default', connectorOne.id, 'action', 'actions'); + actionsRemover.add('default', connectorTwo.id, 'action', 'actions'); + actionsRemover.add('default', connectorThree.id, 'action', 'actions'); + + const { body: connectors } = await supertest + .get(`${CASE_CONFIGURE_CONNECTORS_URL}/_find`) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + + expect(connectors.length).to.equal(2); + expect( + connectors.some((c: { actionTypeId: string }) => c.actionTypeId === '.servicenow') + ).to.equal(true); + expect(connectors.some((c: { actionTypeId: string }) => c.actionTypeId === '.jira')).to.equal( + true + ); + }); }); }; diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts index 5861db2eb8e5b..fe9cb48178633 100644 --- a/x-pack/test/case_api_integration/common/lib/utils.ts +++ b/x-pack/test/case_api_integration/common/lib/utils.ts @@ -23,7 +23,7 @@ export const getConfigurationOutput = (update = false): Partial ({ +export const getServiceNowConnector = () => ({ name: 'ServiceNow Connector', actionTypeId: '.servicenow', secrets: { @@ -54,6 +54,38 @@ export const getConnector = () => ({ }, }); +export const getJiraConnector = () => ({ + name: 'Jira Connector', + actionTypeId: '.jira', + secrets: { + email: 'elastic@elastic.co', + apiToken: 'token', + }, + config: { + apiUrl: 'http://some.non.existent.com', + projectKey: 'pkey', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'summary', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, +}); + export const removeServerGeneratedPropertiesFromConfigure = ( config: Partial ): Partial => { diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index b0208fb5e2cf6..39da6d8866e9e 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -41,11 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); await PageObjects.lens.assertExactText( '[data-test-subj="lnsDataTable"] [data-test-subj="lnsDataTableCellValue"]', - '19,985' - ); - await PageObjects.lens.assertExactText( - '[data-test-subj="lnsDataTable"] [data-test-subj="lnsDataTableCellValueFilterable"]', - 'IN' + '19,986' ); } @@ -68,8 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.getActions().move({ x: 5, y: 5, origin: el._webElement }).click().perform(); } - // Failing: https://github.com/elastic/kibana/issues/66779 - describe.skip('lens smokescreen tests', () => { + describe('lens smokescreen tests', () => { it('should allow editing saved visualizations', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); @@ -103,22 +98,15 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(hasIpFilter).to.be(true); }); - it('should allow seamless transition to and from table view and add a filter', async () => { + it('should allow seamless transition to and from table view', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); await PageObjects.lens.goToTimeRange(); await assertExpectedMetric(); await PageObjects.lens.switchToVisualization('lnsChartSwitchPopover_lnsDatatable'); - await PageObjects.lens.configureDimension({ - dimension: '[data-test-subj="lnsDatatable_column"] [data-test-subj="lns-empty-dimension"]', - operation: 'terms', - field: 'geo.dest', - }); - await PageObjects.lens.save('Artistpreviouslyknownaslens'); - await find.clickByCssSelector('[data-test-subj="lensDatatableFilterOut"]'); await assertExpectedTable(); await PageObjects.lens.switchToVisualization('lnsChartSwitchPopover_lnsMetric'); - await assertExpectedMetric('19,985'); + await assertExpectedMetric(); }); it('should allow creation of lens visualizations', async () => { diff --git a/x-pack/test/functional/apps/maps/joins.js b/x-pack/test/functional/apps/maps/joins.js index 2bf52153b7a3d..7534a1b09cc23 100644 --- a/x-pack/test/functional/apps/maps/joins.js +++ b/x-pack/test/functional/apps/maps/joins.js @@ -58,11 +58,18 @@ export default function ({ getPageObjects, getService }) { const layerTOCDetails = await PageObjects.maps.getLayerTOCDetails('geo_shapes*'); const split = layerTOCDetails.trim().split('\n'); - const min = split[0]; - expect(min).to.equal('3'); - - const max = split[2]; - expect(max).to.equal('12'); + //field display name + expect(split[0]).to.equal('max prop1'); + + //bands 1-8 + expect(split[1]).to.equal('3'); + expect(split[2]).to.equal('4.13'); + expect(split[3]).to.equal('5.25'); + expect(split[4]).to.equal('6.38'); + expect(split[5]).to.equal('7.5'); + expect(split[6]).to.equal('8.63'); + expect(split[7]).to.equal('9.75'); + expect(split[8]).to.equal('11'); }); it('should decorate feature properties with join property', async () => { @@ -164,10 +171,10 @@ export default function ({ getPageObjects, getService }) { const split = layerTOCDetails.trim().split('\n'); const min = split[0]; - expect(min).to.equal('12'); + expect(min).to.equal('max prop1'); - const max = split[2]; - expect(max).to.equal('12'); + const max = split[1]; + expect(max).to.equal('12'); // just single band because single value }); it('should flag only the joined features as visible', async () => { diff --git a/x-pack/test/functional/services/ml/anomalies_table.ts b/x-pack/test/functional/services/ml/anomalies_table.ts index c8701099dcd7a..26af97d008feb 100644 --- a/x-pack/test/functional/services/ml/anomalies_table.ts +++ b/x-pack/test/functional/services/ml/anomalies_table.ts @@ -19,7 +19,7 @@ export function MachineLearningAnomaliesTableProvider({ getService }: FtrProvide const tableRows = await testSubjects.findAll('mlAnomaliesTable > ~mlAnomaliesListRow'); expect(tableRows.length).to.be.greaterThan( 0, - 'Anomalies table should have at least one row (got 0)' + `Anomalies table should have at least one row (got '${tableRows.length}')` ); }, }; diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 3f3f6cdde1724..897f37821001e 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -140,7 +140,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getJobState(jobId: string): Promise { const jobStats = await this.getADJobStats(jobId); - expect(jobStats.jobs).to.have.length(1); + expect(jobStats.jobs).to.have.length( + 1, + `Expected job stats to have exactly one job (got '${jobStats.length}')` + ); const state: JOB_STATE = jobStats.jobs[0].state; return state; @@ -178,7 +181,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { .expect(200) .then((res: any) => res.body); - expect(datafeedStats.datafeeds).to.have.length(1); + expect(datafeedStats.datafeeds).to.have.length( + 1, + `Expected datafeed stats to have exactly one datafeed (got '${datafeedStats.datafeeds.length}')` + ); const state: DATAFEED_STATE = datafeedStats.datafeeds[0].state; return state; @@ -206,7 +212,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { .expect(200) .then((res: any) => res.body); - expect(analyticsStats.data_frame_analytics).to.have.length(1); + expect(analyticsStats.data_frame_analytics).to.have.length( + 1, + `Expected dataframe analytics stats to have exactly one object (got '${analyticsStats.data_frame_analytics.length}')` + ); const state: DATA_FRAME_TASK_STATE = analyticsStats.data_frame_analytics[0].state; return state; @@ -414,7 +423,10 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { async getADJobRecordCount(jobId: string): Promise { const jobStats = await this.getADJobStats(jobId); - expect(jobStats.jobs).to.have.length(1); + expect(jobStats.jobs).to.have.length( + 1, + `Expected job stats to have exactly one job (got '${jobStats.jobs.length}')` + ); const processedRecordCount: number = jobStats.jobs[0].data_counts.processed_record_count; return processedRecordCount; diff --git a/x-pack/test/functional/services/ml/custom_urls.ts b/x-pack/test/functional/services/ml/custom_urls.ts index 6842908462018..4acbd23cd3580 100644 --- a/x-pack/test/functional/services/ml/custom_urls.ts +++ b/x-pack/test/functional/services/ml/custom_urls.ts @@ -19,7 +19,10 @@ export function MachineLearningCustomUrlsProvider({ getService }: FtrProviderCon 'mlJobCustomUrlLabelInput', 'value' ); - expect(actualCustomUrlLabel).to.eql(expectedValue); + expect(actualCustomUrlLabel).to.eql( + expectedValue, + `Expected custom url label to be '${expectedValue}' (got '${actualCustomUrlLabel}')` + ); }, async setCustomUrlLabel(customUrlsLabel: string) { @@ -29,11 +32,16 @@ export function MachineLearningCustomUrlsProvider({ getService }: FtrProviderCon await this.assertCustomUrlLabelValue(customUrlsLabel); }, - async assertCustomUrlItem(index: number, label: string) { + async assertCustomUrlItem(index: number, expectedLabel: string) { await testSubjects.existOrFail(`mlJobEditCustomUrlItem_${index}`); - expect( - await testSubjects.getAttribute(`mlJobEditCustomUrlLabelInput_${index}`, 'value') - ).to.eql(label); + const actualLabel = await testSubjects.getAttribute( + `mlJobEditCustomUrlLabelInput_${index}`, + 'value' + ); + expect(actualLabel).to.eql( + expectedLabel, + `Expected custom url item to be '${expectedLabel}' (got '${actualLabel}')` + ); }, /** diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index d67f6bc946df2..cff7e00eef688 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -75,7 +75,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( )) === 'true'; expect(actualCheckState).to.eql( expectedCheckState, - `Advanced editor switch check state should be ${expectedCheckState} (got ${actualCheckState})` + `Advanced editor switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, @@ -317,7 +317,7 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( const actualCheckState = await this.getCreateIndexPatternSwitchCheckState(); expect(actualCheckState).to.eql( expectedCheckState, - `Create index pattern switch check state should be ${expectedCheckState} (got ${actualCheckState})` + `Create index pattern switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_table.ts b/x-pack/test/functional/services/ml/data_frame_analytics_table.ts index 2a621aaf28fe3..d5f4ee63f615b 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_table.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_table.ts @@ -107,7 +107,12 @@ export function MachineLearningDataFrameAnalyticsTableProvider({ getService }: F public async assertAnalyticsRowFields(analyticsId: string, expectedRow: object) { const rows = await this.parseAnalyticsTable(); const analyticsRow = rows.filter((row) => row.id === analyticsId)[0]; - expect(analyticsRow).to.eql(expectedRow); + expect(analyticsRow).to.eql( + expectedRow, + `Expected analytics row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify( + analyticsRow + )}')` + ); } public async openRowActions(analyticsId: string) { diff --git a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts index 792dd5f90ca11..7789ca78363df 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_index_based.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_index_based.ts @@ -23,7 +23,10 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ async assertTotalDocumentCount(expectedTotalDocCount: number) { await retry.tryForTime(5000, async () => { const docCount = await testSubjects.getVisibleText('mlDataVisualizerTotalDocCount'); - expect(docCount).to.eql(expectedTotalDocCount); + expect(docCount).to.eql( + expectedTotalDocCount, + `Expected total document count to be '${expectedTotalDocCount}' (got '${docCount}')` + ); }); }, @@ -34,7 +37,10 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ async assertFieldsPanelsExist(expectedPanelCount: number) { const allPanels = await testSubjects.findAll('~mlDataVisualizerFieldsPanel'); - expect(allPanels).to.have.length(expectedPanelCount); + expect(allPanels).to.have.length( + expectedPanelCount, + `Expected field panels count to be '${expectedPanelCount}' (got '${allPanels.length}')` + ); }, async assertFieldsPanelForTypesExist(fieldTypes: ML_JOB_FIELD_TYPES[]) { @@ -50,7 +56,10 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ const filteredCards = await testSubjects.findAll( `mlDataVisualizerFieldsPanel ${panelFieldTypes} > ~mlFieldDataCard` ); - expect(filteredCards).to.have.length(expectedCardCount); + expect(filteredCards).to.have.length( + expectedCardCount, + `Expected field card count for panels '${panelFieldTypes}' to be '${expectedCardCount}' (got '${filteredCards.length}')` + ); }); }, @@ -60,7 +69,10 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ ); const searchBarInput = await searchBar.findByTagName('input'); const actualSearchValue = await searchBarInput.getAttribute('value'); - expect(actualSearchValue).to.eql(expectedSearchValue); + expect(actualSearchValue).to.eql( + expectedSearchValue, + `Expected search value for field types '${fieldTypes}' to be '${expectedSearchValue}' (got '${actualSearchValue}')` + ); }, async filterFieldsPanelWithSearchString( @@ -91,7 +103,10 @@ export function MachineLearningDataVisualizerIndexBasedProvider({ 'mlDataVisualizerFieldTypesSelect', 'value' ); - expect(actualTypeValue).to.eql(expectedTypeValue); + expect(actualTypeValue).to.eql( + expectedTypeValue, + `Expected fields panel type value to be '${expectedTypeValue}' (got '${actualTypeValue}')` + ); }, async setFieldsPanelTypeInputValue( diff --git a/x-pack/test/functional/services/ml/job_table.ts b/x-pack/test/functional/services/ml/job_table.ts index cfb3ed8977716..a72d9c204060b 100644 --- a/x-pack/test/functional/services/ml/job_table.ts +++ b/x-pack/test/functional/services/ml/job_table.ts @@ -163,7 +163,10 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte await this.refreshJobList(); const rows = await this.parseJobTable(); const jobRow = rows.filter((row) => row.id === jobId)[0]; - expect(jobRow).to.eql(expectedRow); + expect(jobRow).to.eql( + expectedRow, + `Expected job row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify(jobRow)}')` + ); } public async assertJobRowDetailsCounts( diff --git a/x-pack/test/functional/services/ml/job_wizard_advanced.ts b/x-pack/test/functional/services/ml/job_wizard_advanced.ts index 755091ca10f3b..e4d2ecf66f646 100644 --- a/x-pack/test/functional/services/ml/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/ml/job_wizard_advanced.ts @@ -35,7 +35,10 @@ export function MachineLearningJobWizardAdvancedProvider( const actualValue = await aceEditor.getValue( 'mlAdvancedDatafeedQueryEditor > codeEditorContainer' ); - expect(actualValue).to.eql(expectedValue); + expect(actualValue).to.eql( + expectedValue, + `Expected datafeed query editor value to be '${expectedValue}' (got '${actualValue}')` + ); }, async assertQueryDelayInputExists() { @@ -44,7 +47,10 @@ export function MachineLearningJobWizardAdvancedProvider( async assertQueryDelayValue(expectedValue: string) { const actualQueryDelay = await this.getValueOrPlaceholder('mlJobWizardInputQueryDelay'); - expect(actualQueryDelay).to.eql(expectedValue); + expect(actualQueryDelay).to.eql( + expectedValue, + `Expected query delay value to be '${expectedValue}' (got '${actualQueryDelay}')` + ); }, async setQueryDelay(queryDelay: string) { @@ -61,7 +67,10 @@ export function MachineLearningJobWizardAdvancedProvider( async assertFrequencyValue(expectedValue: string) { const actualFrequency = await this.getValueOrPlaceholder('mlJobWizardInputFrequency'); - expect(actualFrequency).to.eql(expectedValue); + expect(actualFrequency).to.eql( + expectedValue, + `Expected frequency value to be '${expectedValue}' (got '${actualFrequency}')` + ); }, async setFrequency(frequency: string) { @@ -78,7 +87,10 @@ export function MachineLearningJobWizardAdvancedProvider( async assertScrollSizeValue(expectedValue: string) { const actualScrollSize = await this.getValueOrPlaceholder('mlJobWizardInputScrollSize'); - expect(actualScrollSize).to.eql(expectedValue); + expect(actualScrollSize).to.eql( + expectedValue, + `Expected scroll size value to be '${expectedValue}' (got '${actualScrollSize}')` + ); }, async setScrollSize(scrollSize: string) { @@ -97,7 +109,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlTimeFieldNameSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected time field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectTimeField(identifier: string) { @@ -113,7 +128,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlCategorizationFieldNameSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected categorization field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectCategorizationField(identifier: string) { @@ -129,7 +147,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlSummaryCountFieldNameSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected summary count field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectSummaryCountField(identifier: string) { @@ -160,7 +181,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedFunctionSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector function selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorFunction(identifier: string) { @@ -176,7 +200,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorField(identifier: string) { @@ -192,7 +219,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedByFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector by field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorByField(identifier: string) { @@ -208,7 +238,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedOverFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector over field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorOverField(identifier: string) { @@ -224,7 +257,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedPartitionFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector partition field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorPartitionField(identifier: string) { @@ -240,7 +276,10 @@ export function MachineLearningJobWizardAdvancedProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlAdvancedExcludeFrequentSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector exclude frequent selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorExcludeFrequent(identifier: string) { @@ -257,7 +296,10 @@ export function MachineLearningJobWizardAdvancedProvider( 'mlAdvancedDetectorDescriptionInput', 'value' ); - expect(actualDetectorDescription).to.eql(expectedValue); + expect(actualDetectorDescription).to.eql( + expectedValue, + `Expected detector description value to be '${expectedValue}' (got '${actualDetectorDescription}')` + ); }, async setDetectorDescription(description: string) { @@ -287,13 +329,19 @@ export function MachineLearningJobWizardAdvancedProvider( const actualDetectorIdentifier = await testSubjects.getVisibleText( `mlAdvancedDetector ${detectorIndex} > mlDetectorIdentifier` ); - expect(actualDetectorIdentifier).to.eql(expectedDetectorName); + expect(actualDetectorIdentifier).to.eql( + expectedDetectorName, + `Expected detector name to be '${expectedDetectorName}' (got '${actualDetectorIdentifier}')` + ); if (expectedDetectorDescription !== undefined) { const actualDetectorDescription = await testSubjects.getVisibleText( `mlAdvancedDetector ${detectorIndex} > mlDetectorDescription` ); - expect(actualDetectorDescription).to.eql(expectedDetectorDescription); + expect(actualDetectorDescription).to.eql( + expectedDetectorDescription, + `Expected detector description to be '${expectedDetectorDescription}' (got '${actualDetectorDescription}')` + ); } }, diff --git a/x-pack/test/functional/services/ml/job_wizard_categorization.ts b/x-pack/test/functional/services/ml/job_wizard_categorization.ts index 97d45701a2685..705cc29938dfb 100644 --- a/x-pack/test/functional/services/ml/job_wizard_categorization.ts +++ b/x-pack/test/functional/services/ml/job_wizard_categorization.ts @@ -41,7 +41,7 @@ export function MachineLearningJobWizardCategorizationProvider({ getService }: F ); expect(comboBoxSelectedOptions).to.eql( expectedIdentifier, - `Expected categorization field selection to be '${expectedIdentifier}' (got ${comboBoxSelectedOptions}')` + `Expected categorization field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` ); }, @@ -56,7 +56,7 @@ export function MachineLearningJobWizardCategorizationProvider({ getService }: F const rows = await body[0].findAllByTagName('tr'); expect(rows.length).to.eql( exampleCount, - `Expected categorization field examples table to have '${exampleCount}' rows (got ${rows.length}')` + `Expected categorization field examples table to have '${exampleCount}' rows (got '${rows.length}')` ); }, }; diff --git a/x-pack/test/functional/services/ml/job_wizard_common.ts b/x-pack/test/functional/services/ml/job_wizard_common.ts index af33ec2301edc..2843c36e08a1d 100644 --- a/x-pack/test/functional/services/ml/job_wizard_common.ts +++ b/x-pack/test/functional/services/ml/job_wizard_common.ts @@ -101,7 +101,10 @@ export function MachineLearningJobWizardCommonProvider( const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlJobWizardAggSelection > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected agg and field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectAggAndField(identifier: string, isIdentifierKeptInField: boolean) { @@ -118,7 +121,10 @@ export function MachineLearningJobWizardCommonProvider( 'mlJobWizardInputBucketSpan', 'value' ); - expect(actualBucketSpan).to.eql(expectedValue); + expect(actualBucketSpan).to.eql( + expectedValue, + `Expected bucket span value to be '${expectedValue}' (got '${actualBucketSpan}')` + ); }, async setBucketSpan(bucketSpan: string) { @@ -135,7 +141,10 @@ export function MachineLearningJobWizardCommonProvider( async assertJobIdValue(expectedValue: string) { const actualJobId = await testSubjects.getAttribute('mlJobWizardInputJobId', 'value'); - expect(actualJobId).to.eql(expectedValue); + expect(actualJobId).to.eql( + expectedValue, + `Expected job id value to be '${expectedValue}' (got '${actualJobId}')` + ); }, async setJobId(jobId: string) { @@ -153,7 +162,10 @@ export function MachineLearningJobWizardCommonProvider( const actualJobDescription = await testSubjects.getVisibleText( 'mlJobWizardInputJobDescription' ); - expect(actualJobDescription).to.eql(expectedValue); + expect(actualJobDescription).to.eql( + expectedValue, + `Expected job description value to be '${expectedValue}' (got '${actualJobDescription}')` + ); }, async setJobDescription(jobDescription: string) { @@ -174,12 +186,20 @@ export function MachineLearningJobWizardCommonProvider( }, async assertJobGroupSelection(jobGroups: string[]) { - expect(await this.getSelectedJobGroups()).to.eql(jobGroups); + const actualJobGroupSelection = await this.getSelectedJobGroups(); + expect(actualJobGroupSelection).to.eql( + jobGroups, + `Expected job group selection to be '${jobGroups}' (got '${actualJobGroupSelection}')` + ); }, async addJobGroup(jobGroup: string) { await comboBox.setCustom('mlJobWizardComboBoxJobGroups > comboBoxInput', jobGroup); - expect(await this.getSelectedJobGroups()).to.contain(jobGroup); + const actualJobGroupSelection = await this.getSelectedJobGroups(); + expect(actualJobGroupSelection).to.contain( + jobGroup, + `Expected job group selection to contain '${jobGroup}' (got '${actualJobGroupSelection}')` + ); }, async getSelectedCalendars(): Promise { @@ -190,13 +210,21 @@ export function MachineLearningJobWizardCommonProvider( }, async assertCalendarsSelection(calendars: string[]) { - expect(await this.getSelectedCalendars()).to.eql(calendars); + const actualCalendarSelection = await this.getSelectedCalendars(); + expect(actualCalendarSelection).to.eql( + calendars, + `Expected calendar selection to be '${calendars}' (got '${actualCalendarSelection}')` + ); }, async addCalendar(calendarId: string) { await this.ensureAdditionalSettingsSectionOpen(); await comboBox.setCustom('mlJobWizardComboBoxCalendars > comboBoxInput', calendarId); - expect(await this.getSelectedCalendars()).to.contain(calendarId); + const actualCalendarSelection = await this.getSelectedCalendars(); + expect(actualCalendarSelection).to.contain( + calendarId, + `Expected calendar selection to conatin '${calendarId}' (got '${actualCalendarSelection}')` + ); }, async assertModelPlotSwitchExists( @@ -229,14 +257,19 @@ export function MachineLearningJobWizardCommonProvider( const actualCheckedState = await this.getModelPlotSwitchCheckedState({ withAdvancedSection: sectionOptions.withAdvancedSection, }); - expect(actualCheckedState).to.eql(expectedValue); + expect(actualCheckedState).to.eql( + expectedValue, + `Expected model plot switch to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + actualCheckedState ? 'enabled' : 'disabled' + }')` + ); }, async assertModelPlotSwitchEnabled(expectedValue: boolean) { const isEnabled = await testSubjects.isEnabled('mlJobWizardSwitchModelPlot'); expect(isEnabled).to.eql( expectedValue, - `Expected model plot switch to be '${expectedValue ? 'enabled' : 'disabled'}' (got ${ + `Expected model plot switch to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ); @@ -272,7 +305,12 @@ export function MachineLearningJobWizardCommonProvider( const actualCheckedState = await this.getDedicatedIndexSwitchCheckedState({ withAdvancedSection: sectionOptions.withAdvancedSection, }); - expect(actualCheckedState).to.eql(expectedValue); + expect(actualCheckedState).to.eql( + expectedValue, + `Expected dedicated index switch to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ + actualCheckedState ? 'enabled' : 'disabled' + }')` + ); }, async activateDedicatedIndexSwitch( @@ -318,7 +356,10 @@ export function MachineLearningJobWizardCommonProvider( subj = advancedSectionSelector(subj); } const actualModelMemoryLimit = await testSubjects.getAttribute(subj, 'value'); - expect(actualModelMemoryLimit).to.eql(expectedValue); + expect(actualModelMemoryLimit).to.eql( + expectedValue, + `Expected model memory limit value to be '${expectedValue}' (got '${actualModelMemoryLimit}')` + ); }, async setModelMemoryLimit( @@ -347,12 +388,20 @@ export function MachineLearningJobWizardCommonProvider( }, async assertInfluencerSelection(influencers: string[]) { - expect(await this.getSelectedInfluencers()).to.eql(influencers); + const actualInfluencerSelection = await this.getSelectedInfluencers(); + expect(actualInfluencerSelection).to.eql( + influencers, + `Expected influencer selection to be '${influencers}' (got '${actualInfluencerSelection}')` + ); }, async addInfluencer(influencer: string) { await comboBox.set('mlInfluencerSelect > comboBoxInput', influencer); - expect(await this.getSelectedInfluencers()).to.contain(influencer); + const actualInfluencerSelection = await this.getSelectedInfluencers(); + expect(actualInfluencerSelection).to.contain( + influencer, + `Expected influencer selection to contain '${influencer}' (got '${actualInfluencerSelection}')` + ); }, async assertAnomalyChartExists(chartType: string, preSelector?: string) { @@ -367,9 +416,13 @@ export function MachineLearningJobWizardCommonProvider( ) { await testSubjects.existOrFail(`mlDetector ${detectorPosition}`); await testSubjects.existOrFail(`mlDetector ${detectorPosition} > mlDetectorTitle`); - expect( - await testSubjects.getVisibleText(`mlDetector ${detectorPosition} > mlDetectorTitle`) - ).to.eql(aggAndFieldIdentifier); + const actualDetectorTitle = await testSubjects.getVisibleText( + `mlDetector ${detectorPosition} > mlDetectorTitle` + ); + expect(actualDetectorTitle).to.eql( + aggAndFieldIdentifier, + `Expected detector title at position '${detectorPosition}' to be '${aggAndFieldIdentifier}' (got '${actualDetectorTitle}')` + ); await this.assertAnomalyChartExists(chartType, `mlDetector ${detectorPosition}`); }, @@ -393,10 +446,15 @@ export function MachineLearningJobWizardCommonProvider( async assertDateRangeSelection(expectedStartDate: string, expectedEndDate: string) { await retry.tryForTime(5000, async () => { - expect(await this.getSelectedDateRange()).to.eql({ - startDate: expectedStartDate, - endDate: expectedEndDate, - }); + const { startDate, endDate } = await this.getSelectedDateRange(); + expect(startDate).to.eql( + expectedStartDate, + `Expected start date to be '${expectedStartDate}' (got '${startDate}')` + ); + expect(endDate).to.eql( + expectedEndDate, + `Expected end date to be '${expectedEndDate}' (got '${endDate}')` + ); }); }, diff --git a/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts b/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts index 2fb768d924cff..c1c945933b106 100644 --- a/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts +++ b/x-pack/test/functional/services/ml/job_wizard_multi_metric.ts @@ -20,7 +20,10 @@ export function MachineLearningJobWizardMultiMetricProvider({ getService }: FtrP const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlMultiMetricSplitFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected split field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectSplitField(identifier: string) { @@ -33,15 +36,21 @@ export function MachineLearningJobWizardMultiMetricProvider({ getService }: FtrP await testSubjects.existOrFail(`mlDataSplit > mlSplitCard front`); }, - async assertDetectorSplitFrontCardTitle(frontCardTitle: string) { - expect( - await testSubjects.getVisibleText(`mlDataSplit > mlSplitCard front > mlSplitCardTitle`) - ).to.eql(frontCardTitle); + async assertDetectorSplitFrontCardTitle(expectedFrontCardTitle: string) { + const actualFrontCardTitle = await testSubjects.getVisibleText( + `mlDataSplit > mlSplitCard front > mlSplitCardTitle` + ); + expect(actualFrontCardTitle).to.eql( + expectedFrontCardTitle, + `Expected front card title to be '${expectedFrontCardTitle}' (got '${actualFrontCardTitle}')` + ); }, - async assertDetectorSplitNumberOfBackCards(numberOfBackCards: number) { - expect(await testSubjects.findAll(`mlDataSplit > mlSplitCard back`)).to.have.length( - numberOfBackCards + async assertDetectorSplitNumberOfBackCards(expectedNumberOfBackCards: number) { + const allBackCards = await testSubjects.findAll(`mlDataSplit > mlSplitCard back`); + expect(allBackCards).to.have.length( + expectedNumberOfBackCards, + `Expected number of back cards to be '${expectedNumberOfBackCards}' (got '${allBackCards.length}')` ); }, }; diff --git a/x-pack/test/functional/services/ml/job_wizard_population.ts b/x-pack/test/functional/services/ml/job_wizard_population.ts index 8ff9d5c12a642..88b773b201c51 100644 --- a/x-pack/test/functional/services/ml/job_wizard_population.ts +++ b/x-pack/test/functional/services/ml/job_wizard_population.ts @@ -20,7 +20,10 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlPopulationSplitFieldSelect > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected population field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectPopulationField(identifier: string) { @@ -41,7 +44,10 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( `mlDetector ${detectorPosition} > mlByFieldSelect > comboBoxInput` ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected detector split field selection to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }, async selectDetectorSplitField(detectorPosition: number, identifier: string) { @@ -59,23 +65,30 @@ export function MachineLearningJobWizardPopulationProvider({ getService }: FtrPr ); }, - async assertDetectorSplitFrontCardTitle(detectorPosition: number, frontCardTitle: string) { - expect( - await testSubjects.getVisibleText( - `mlDetector ${detectorPosition} > mlDataSplit > mlSplitCard front > mlSplitCardTitle` - ) - ).to.eql(frontCardTitle); + async assertDetectorSplitFrontCardTitle( + detectorPosition: number, + expectedFrontCardTitle: string + ) { + const actualSplitFrontCardTitle = await testSubjects.getVisibleText( + `mlDetector ${detectorPosition} > mlDataSplit > mlSplitCard front > mlSplitCardTitle` + ); + expect(actualSplitFrontCardTitle).to.eql( + expectedFrontCardTitle, + `Expected front card title for detector position '${detectorPosition}' to be '${expectedFrontCardTitle}' (got '${actualSplitFrontCardTitle}')` + ); }, async assertDetectorSplitNumberOfBackCards( detectorPosition: number, - numberOfBackCards: number + expectedNumberOfBackCards: number ) { - expect( - await testSubjects.findAll( - `mlDetector ${detectorPosition} > mlDataSplit > mlSplitCard back` - ) - ).to.have.length(numberOfBackCards); + const allBackCards = await testSubjects.findAll( + `mlDetector ${detectorPosition} > mlDataSplit > mlSplitCard back` + ); + expect(allBackCards).to.have.length( + expectedNumberOfBackCards, + `Expected number of back cards for detector position '${detectorPosition}' to be '${expectedNumberOfBackCards}' (got '${allBackCards.length}')` + ); }, }; } diff --git a/x-pack/test/functional/services/ml/navigation.ts b/x-pack/test/functional/services/ml/navigation.ts index b0f993eab1a2b..8454a0b071b8f 100644 --- a/x-pack/test/functional/services/ml/navigation.ts +++ b/x-pack/test/functional/services/ml/navigation.ts @@ -25,8 +25,10 @@ export function MachineLearningNavigationProvider({ async assertTabsExist(tabTypeSubject: string, areaSubjects: string[]) { await retry.tryForTime(10000, async () => { - expect(await testSubjects.findAll(`~${tabTypeSubject}`, 3)).to.have.length( - areaSubjects.length + const allTabs = await testSubjects.findAll(`~${tabTypeSubject}`, 3); + expect(allTabs).to.have.length( + areaSubjects.length, + `Expected number of '${tabTypeSubject}' to be '${areaSubjects.length}' (got '${allTabs.length}')` ); for (const areaSubj of areaSubjects) { await testSubjects.existOrFail(`~${tabTypeSubject}&~${areaSubj}`, { timeout: 1000 }); diff --git a/x-pack/test/functional/services/transform/api.ts b/x-pack/test/functional/services/transform/api.ts index 5c7f04e7bc1a5..a805f5a3b6013 100644 --- a/x-pack/test/functional/services/transform/api.ts +++ b/x-pack/test/functional/services/transform/api.ts @@ -54,7 +54,10 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { .expect(200) .then((res: any) => res.body); - expect(statsResponse.transforms).to.have.length(1); + expect(statsResponse.transforms).to.have.length( + 1, + `Expected transform stats to contain exactly 1 object (got '${statsResponse.transforms.length}')` + ); return statsResponse.transforms[0]; }, diff --git a/x-pack/test/functional/services/transform/transform_table.ts b/x-pack/test/functional/services/transform/transform_table.ts index 7bbe6724e3fff..3155ef0b26050 100644 --- a/x-pack/test/functional/services/transform/transform_table.ts +++ b/x-pack/test/functional/services/transform/transform_table.ts @@ -98,7 +98,10 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { uniqueColumnValues.sort(); // check if the returned unique value matches the supplied filter value - expect(uniqueColumnValues).to.eql(expectedColumnValues); + expect(uniqueColumnValues).to.eql( + expectedColumnValues, + `Expected '${tableSubj}' column values to be '${expectedColumnValues}' (got '${uniqueColumnValues}')` + ); }); } @@ -123,7 +126,12 @@ export function TransformTableProvider({ getService }: FtrProviderContext) { public async assertTransformRowFields(transformId: string, expectedRow: object) { const rows = await this.parseTransformTable(); const transformRow = rows.filter((row) => row.id === transformId)[0]; - expect(transformRow).to.eql(expectedRow); + expect(transformRow).to.eql( + expectedRow, + `Expected transform row to be '${JSON.stringify(expectedRow)}' (got '${JSON.stringify( + transformRow + )}')` + ); } public async assertTransformExpandedRow() { diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts index f2753ab645b9d..6a99e6ed007b6 100644 --- a/x-pack/test/functional/services/transform/wizard.ts +++ b/x-pack/test/functional/services/transform/wizard.ts @@ -120,20 +120,20 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { }); }, - async assertIndexPreview(columns: number, rows: number) { + async assertIndexPreview(columns: number, expectedNumberOfRows: number) { await retry.tryForTime(2000, async () => { // get a 2D array of rows and cell values const rowsData = await this.parseEuiDataGrid('transformIndexPreview'); expect(rowsData).to.length( - rows, - `EuiDataGrid rows should be ${rows} (got ${rowsData.length})` + expectedNumberOfRows, + `EuiDataGrid rows should be '${expectedNumberOfRows}' (got '${rowsData.length}')` ); rowsData.map((r, i) => expect(r).to.length( columns, - `EuiDataGrid row #${i + 1} column count should be ${columns} (got ${r.length})` + `EuiDataGrid row #${i + 1} column count should be '${columns}' (got '${r.length}')` ) ); }); @@ -185,7 +185,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { 'true'; expect(actualCheckState).to.eql( expectedCheckState, - `Advanced query editor switch check state should be ${expectedCheckState} (got ${actualCheckState})` + `Advanced query editor switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, @@ -198,7 +198,10 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'transformGroupBySelection > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected group by value to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }); }, @@ -214,7 +217,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { ); expect(actualLabel).to.eql( expectedLabel, - `Label for group by entry ${index} should be '${expectedLabel}' (got '${actualLabel}')` + `Label for group by entry '${index}' should be '${expectedLabel}' (got '${actualLabel}')` ); if (expectedIntervalLabel !== undefined) { @@ -223,7 +226,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { ); expect(actualIntervalLabel).to.eql( expectedIntervalLabel, - `Label for group by entry ${index} should be '${expectedIntervalLabel}' (got '${actualIntervalLabel}')` + `Label for group by entry '${index}' should be '${expectedIntervalLabel}' (got '${actualIntervalLabel}')` ); } }, @@ -248,7 +251,10 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'transformAggregationSelection > comboBoxInput' ); - expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql( + expectedIdentifier, + `Expected aggregation value to be '${expectedIdentifier}' (got '${comboBoxSelectedOptions}')` + ); }); }, @@ -260,7 +266,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { ); expect(actualLabel).to.eql( expectedLabel, - `Label for aggregation entry ${index} should be '${expectedLabel}' (got '${actualLabel}')` + `Label for aggregation entry '${index}' should be '${expectedLabel}' (got '${actualLabel}')` ); }, @@ -278,7 +284,11 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { // const advancedEditorValue = JSON.parse(advancedEditorString); // expect(advancedEditorValue).to.eql(expectedValue); - expect(advancedEditorString.split('\n').splice(0, 3)).to.eql(expectedValue); + const splicedAdvancedEditorValue = advancedEditorString.split('\n').splice(0, 3); + expect(splicedAdvancedEditorValue).to.eql( + expectedValue, + `Expected the first editor lines to be '${expectedValue}' (got '${splicedAdvancedEditorValue}')` + ); }, async assertAdvancedPivotEditorSwitchExists() { @@ -291,7 +301,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { 'true'; expect(actualCheckState).to.eql( expectedCheckState, - `Advanced pivot editor switch check state should be ${expectedCheckState} (got ${actualCheckState})` + `Advanced pivot editor switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, @@ -310,7 +320,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { const actualTransformId = await testSubjects.getAttribute('transformIdInput', 'value'); expect(actualTransformId).to.eql( expectedValue, - `Transform id input text should be ${expectedValue} (got ${actualTransformId})` + `Transform id input text should be '${expectedValue}' (got '${actualTransformId}')` ); }, @@ -330,7 +340,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { ); expect(actualTransformDescription).to.eql( expectedValue, - `Transform description input text should be ${expectedValue} (got ${actualTransformDescription})` + `Transform description input text should be '${expectedValue}' (got '${actualTransformDescription}')` ); }, @@ -352,7 +362,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { ); expect(actualDestinationIndex).to.eql( expectedValue, - `Destination index input text should be ${expectedValue} (got ${actualDestinationIndex})` + `Destination index input text should be '${expectedValue}' (got '${actualDestinationIndex}')` ); }, @@ -373,7 +383,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { 'true'; expect(actualCheckState).to.eql( expectedCheckState, - `Create index pattern switch check state should be ${expectedCheckState} (got ${actualCheckState})` + `Create index pattern switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, @@ -387,7 +397,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { 'true'; expect(actualCheckState).to.eql( expectedCheckState, - `Continuous mode switch check state should be ${expectedCheckState} (got ${actualCheckState})` + `Continuous mode switch check state should be '${expectedCheckState}' (got '${actualCheckState}')` ); }, @@ -405,7 +415,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { expectedValue, `Expected 'Create and start' button to be '${ expectedValue ? 'enabled' : 'disabled' - }' (got ${isEnabled ? 'enabled' : 'disabled'}')` + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` ); }, @@ -421,7 +431,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { const isEnabled = await testSubjects.isEnabled('transformWizardCreateButton'); expect(isEnabled).to.eql( expectedValue, - `Expected 'Create' button to be '${expectedValue ? 'enabled' : 'disabled'}' (got ${ + `Expected 'Create' button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ); @@ -441,7 +451,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { expectedValue, `Expected 'Copy to clipboard' button to be '${ expectedValue ? 'enabled' : 'disabled' - }' (got ${isEnabled ? 'enabled' : 'disabled'}')` + }' (got '${isEnabled ? 'enabled' : 'disabled'}')` ); }, @@ -460,7 +470,7 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) { const isEnabled = await testSubjects.isEnabled('transformWizardStartButton'); expect(isEnabled).to.eql( expectedValue, - `Expected 'Start' button to be '${expectedValue ? 'enabled' : 'disabled'}' (got ${ + `Expected 'Start' button to be '${expectedValue ? 'enabled' : 'disabled'}' (got '${ isEnabled ? 'enabled' : 'disabled' }')` ); diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 81b29732377da..f5da24fc74d13 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../tsconfig.json", "include": [ + "mocks.ts", "typings/**/*", "legacy/common/**/*", "legacy/server/**/*",