diff --git a/.backportrc.json b/.backportrc.json index 14cb04da188d4..eab70a1fa4de1 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "repoName": "kibana", "targetBranchChoices": [ "main", + "8.1", "8.0", "7.17", "7.16", @@ -37,7 +38,7 @@ "backport" ], "branchLabelMapping": { - "^v8.1.0$": "main", + "^v8.2.0$": "main", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, diff --git a/.buildkite/pipelines/pull_request/deploy_cloud.yml b/.buildkite/pipelines/pull_request/deploy_cloud.yml new file mode 100644 index 0000000000000..c6a15ec32e179 --- /dev/null +++ b/.buildkite/pipelines/pull_request/deploy_cloud.yml @@ -0,0 +1,7 @@ +steps: + - command: .buildkite/scripts/steps/cloud/build_and_deploy.sh + label: 'Build and Deploy to Cloud' + agents: + queue: n2-2 + depends_on: build + timeout_in_minutes: 30 diff --git a/.buildkite/pipelines/purge_cloud_deployments.yml b/.buildkite/pipelines/purge_cloud_deployments.yml new file mode 100644 index 0000000000000..8287abf2ca5a2 --- /dev/null +++ b/.buildkite/pipelines/purge_cloud_deployments.yml @@ -0,0 +1,4 @@ +steps: + - command: .buildkite/scripts/steps/cloud/purge.sh + label: Purge old cloud deployments + timeout_in_minutes: 10 diff --git a/.buildkite/scripts/build_kibana.sh b/.buildkite/scripts/build_kibana.sh index d05fe178b72db..a7fbcc0ea4b92 100755 --- a/.buildkite/scripts/build_kibana.sh +++ b/.buildkite/scripts/build_kibana.sh @@ -13,30 +13,6 @@ else node scripts/build fi -if [[ "${GITHUB_PR_LABELS:-}" == *"ci:deploy-cloud"* ]]; then - echo "--- Build and push Kibana Cloud Distribution" - - echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co - trap 'docker logout docker.elastic.co' EXIT - - node scripts/build \ - --skip-initialize \ - --skip-generic-folders \ - --skip-platform-folders \ - --skip-archives \ - --docker-images \ - --docker-tag-qualifier="$GIT_COMMIT" \ - --docker-push \ - --skip-docker-ubi \ - --skip-docker-ubuntu \ - --skip-docker-contexts - - CLOUD_IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" docker.elastic.co/kibana-ci/kibana-cloud) - cat << EOF | buildkite-agent annotate --style "info" --context cloud-image - Cloud image: $CLOUD_IMAGE -EOF -fi - echo "--- Archive Kibana Distribution" linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" diff --git a/.buildkite/scripts/lifecycle/pre_command.sh b/.buildkite/scripts/lifecycle/pre_command.sh index b957203fc7912..7adae7ff74904 100755 --- a/.buildkite/scripts/lifecycle/pre_command.sh +++ b/.buildkite/scripts/lifecycle/pre_command.sh @@ -92,6 +92,9 @@ export KIBANA_DOCKER_USERNAME KIBANA_DOCKER_PASSWORD="$(retry 5 5 vault read -field=password secret/kibana-issues/dev/container-registry)" export KIBANA_DOCKER_PASSWORD +EC_API_KEY="$(retry 5 5 vault read -field=pr_deploy_api_key secret/kibana-issues/dev/kibana-ci-cloud-deploy)" +export EC_API_KEY + SYNTHETICS_SERVICE_USERNAME="$(retry 5 5 vault read -field=username secret/kibana-issues/dev/kibana-ci-synthetics-credentials)" export SYNTHETICS_SERVICE_USERNAME diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.js b/.buildkite/scripts/pipelines/pull_request/pipeline.js index 1df3b5f64b1df..882f76ebeedda 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.js +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.js @@ -103,6 +103,10 @@ const uploadPipeline = (pipelineContent) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/uptime.yml')); } + if (process.env.GITHUB_PR_LABELS.includes('ci:deploy-cloud')) { + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/deploy_cloud.yml')); + } + pipeline.push(getPipeline('.buildkite/pipelines/pull_request/post_build.yml')); uploadPipeline(pipeline.join('\n')); diff --git a/.buildkite/scripts/steps/cloud/build_and_deploy.sh b/.buildkite/scripts/steps/cloud/build_and_deploy.sh new file mode 100755 index 0000000000000..9ea6c4f445328 --- /dev/null +++ b/.buildkite/scripts/steps/cloud/build_and_deploy.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source .buildkite/scripts/common/util.sh + +.buildkite/scripts/bootstrap.sh + +export KBN_NP_PLUGINS_BUILT=true + +VERSION="$(jq -r '.version' package.json)-SNAPSHOT" + +echo "--- Download kibana distribution" + +mkdir -p ./target +buildkite-agent artifact download "kibana-$VERSION-linux-x86_64.tar.gz" ./target --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" + +echo "--- Build and push Kibana Cloud Distribution" + +echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co +trap 'docker logout docker.elastic.co' EXIT + +node scripts/build \ + --skip-initialize \ + --skip-generic-folders \ + --skip-platform-folders \ + --skip-archives \ + --docker-images \ + --docker-tag-qualifier="$GIT_COMMIT" \ + --docker-push \ + --skip-docker-ubi \ + --skip-docker-ubuntu \ + --skip-docker-contexts + +CLOUD_IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" docker.elastic.co/kibana-ci/kibana-cloud) +CLOUD_DEPLOYMENT_NAME="kibana-pr-$BUILDKITE_PULL_REQUEST" + +jq ' + .resources.kibana[0].plan.kibana.docker_image = "'$CLOUD_IMAGE'" | + .name = "'$CLOUD_DEPLOYMENT_NAME'" | + .resources.kibana[0].plan.kibana.version = "'$VERSION'" | + .resources.elasticsearch[0].plan.elasticsearch.version = "'$VERSION'" + ' .buildkite/scripts/steps/cloud/deploy.json > /tmp/deploy.json + +CLOUD_DEPLOYMENT_ID=$(ecctl deployment list --output json | jq -r '.deployments[] | select(.name == "'$CLOUD_DEPLOYMENT_NAME'") | .id') +JSON_FILE=$(mktemp --suffix ".json") +if [ -z "${CLOUD_DEPLOYMENT_ID}" ]; then + ecctl deployment create --track --output json --file /tmp/deploy.json &> "$JSON_FILE" + CLOUD_DEPLOYMENT_USERNAME=$(jq --slurp '.[]|select(.resources).resources[] | select(.credentials).credentials.username' "$JSON_FILE") + CLOUD_DEPLOYMENT_PASSWORD=$(jq --slurp '.[]|select(.resources).resources[] | select(.credentials).credentials.password' "$JSON_FILE") + CLOUD_DEPLOYMENT_ID=$(jq -r --slurp '.[0].id' "$JSON_FILE") + CLOUD_DEPLOYMENT_STATUS_MESSAGES=$(jq --slurp '[.[]|select(.resources == null)]' "$JSON_FILE") + + # Refresh vault token + VAULT_ROLE_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-role-id)" + VAULT_SECRET_ID="$(retry 5 15 gcloud secrets versions access latest --secret=kibana-buildkite-vault-secret-id)" + VAULT_TOKEN=$(retry 5 30 vault write -field=token auth/approle/login role_id="$VAULT_ROLE_ID" secret_id="$VAULT_SECRET_ID") + retry 5 30 vault login -no-print "$VAULT_TOKEN" + + retry 5 5 vault write "secret/kibana-issues/dev/cloud-deploy/$CLOUD_DEPLOYMENT_NAME" username="$CLOUD_DEPLOYMENT_USERNAME" password="$CLOUD_DEPLOYMENT_PASSWORD" +else + ecctl deployment update "$CLOUD_DEPLOYMENT_ID" --track --output json --file /tmp/deploy.json &> "$JSON_FILE" +fi + +CLOUD_DEPLOYMENT_KIBANA_URL=$(ecctl deployment show "$CLOUD_DEPLOYMENT_ID" | jq -r '.resources.kibana[0].info.metadata.aliased_url') +CLOUD_DEPLOYMENT_ELASTICSEARCH_URL=$(ecctl deployment show "$CLOUD_DEPLOYMENT_ID" | jq -r '.resources.elasticsearch[0].info.metadata.aliased_url') + +cat << EOF | buildkite-agent annotate --style "info" --context cloud + ### Cloud Deployment + + Kibana: $CLOUD_DEPLOYMENT_KIBANA_URL + + Elasticsearch: $CLOUD_DEPLOYMENT_ELASTICSEARCH_URL + + Credentials: \`vault read secret/kibana-issues/dev/cloud-deploy/$CLOUD_DEPLOYMENT_NAME\` + + Image: $CLOUD_IMAGE +EOF + +buildkite-agent meta-data set pr_comment:deploy_cloud:head "* [Cloud Deployment](${CLOUD_DEPLOYMENT_KIBANA_URL})" diff --git a/.buildkite/scripts/steps/cloud/deploy.json b/.buildkite/scripts/steps/cloud/deploy.json new file mode 100644 index 0000000000000..37768144f138a --- /dev/null +++ b/.buildkite/scripts/steps/cloud/deploy.json @@ -0,0 +1,163 @@ +{ + "resources": { + "elasticsearch": [ + { + "region": "gcp-us-west2", + "settings": { + "dedicated_masters_threshold": 6 + }, + "plan": { + "autoscaling_enabled": false, + "cluster_topology": [ + { + "zone_count": 2, + "instance_configuration_id": "gcp.coordinating.1", + "node_roles": ["ingest", "remote_cluster_client"], + "id": "coordinating", + "size": { + "resource": "memory", + "value": 0 + }, + "elasticsearch": { + "enabled_built_in_plugins": [] + } + }, + { + "zone_count": 1, + "elasticsearch": { + "node_attributes": { + "data": "hot" + }, + "enabled_built_in_plugins": [] + }, + "instance_configuration_id": "gcp.data.highio.1", + "node_roles": [ + "master", + "ingest", + "transform", + "data_hot", + "remote_cluster_client", + "data_content" + ], + "id": "hot_content", + "size": { + "value": 1024, + "resource": "memory" + } + }, + { + "zone_count": 2, + "elasticsearch": { + "node_attributes": { + "data": "warm" + }, + "enabled_built_in_plugins": [] + }, + "instance_configuration_id": "gcp.data.highstorage.1", + "node_roles": ["data_warm", "remote_cluster_client"], + "id": "warm", + "size": { + "resource": "memory", + "value": 0 + } + }, + { + "zone_count": 1, + "elasticsearch": { + "node_attributes": { + "data": "cold" + }, + "enabled_built_in_plugins": [] + }, + "instance_configuration_id": "gcp.data.highstorage.1", + "node_roles": ["data_cold", "remote_cluster_client"], + "id": "cold", + "size": { + "resource": "memory", + "value": 0 + } + }, + { + "zone_count": 1, + "elasticsearch": { + "node_attributes": { + "data": "frozen" + }, + "enabled_built_in_plugins": [] + }, + "instance_configuration_id": "gcp.es.datafrozen.n1.64x10x95", + "node_roles": ["data_frozen"], + "id": "frozen", + "size": { + "resource": "memory", + "value": 0 + } + }, + { + "zone_count": 3, + "instance_configuration_id": "gcp.master.1", + "node_roles": ["master", "remote_cluster_client"], + "id": "master", + "size": { + "resource": "memory", + "value": 0 + }, + "elasticsearch": { + "enabled_built_in_plugins": [] + } + }, + { + "zone_count": 1, + "instance_configuration_id": "gcp.ml.1", + "node_roles": ["ml", "remote_cluster_client"], + "id": "ml", + "size": { + "resource": "memory", + "value": 0 + }, + "elasticsearch": { + "enabled_built_in_plugins": [] + } + } + ], + "elasticsearch": { + "version": null + }, + "deployment_template": { + "id": "gcp-io-optimized-v2" + } + }, + "ref_id": "main-elasticsearch" + } + ], + "enterprise_search": [], + "kibana": [ + { + "elasticsearch_cluster_ref_id": "main-elasticsearch", + "region": "gcp-us-west2", + "plan": { + "cluster_topology": [ + { + "instance_configuration_id": "gcp.kibana.1", + "zone_count": 1, + "size": { + "resource": "memory", + "value": 1024 + } + } + ], + "kibana": { + "version": null, + "docker_image": null + } + }, + "ref_id": "main-kibana" + } + ], + "apm": [] + }, + "name": null, + "metadata": { + "system_owned": false + } +} diff --git a/.buildkite/scripts/steps/cloud/purge.js b/.buildkite/scripts/steps/cloud/purge.js new file mode 100644 index 0000000000000..0eccb55cef830 --- /dev/null +++ b/.buildkite/scripts/steps/cloud/purge.js @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const { execSync } = require('child_process'); + +const deploymentsListJson = execSync('ecctl deployment list --output json').toString(); +const { deployments } = JSON.parse(deploymentsListJson); + +const prDeployments = deployments.filter((deployment) => deployment.name.startsWith('kibana-pr-')); + +const deploymentsToPurge = []; + +const NOW = new Date().getTime() / 1000; + +for (const deployment of prDeployments) { + try { + const prNumber = deployment.name.match(/^kibana-pr-([0-9]+)$/)[1]; + const prJson = execSync(`gh pr view '${prNumber}' --json state,labels,commits`).toString(); + const pullRequest = JSON.parse(prJson); + + const lastCommit = pullRequest.commits.slice(-1)[0]; + const lastCommitTimestamp = new Date(lastCommit.committedDate).getTime() / 1000; + + if (pullRequest.state !== 'open') { + console.log(`Pull Request #${prNumber} is no longer open, will delete associated deployment`); + deploymentsToPurge.push(deployment); + } else if (!pullRequest.labels.filter((label) => label.name === 'ci:deploy-cloud')) { + console.log( + `Pull Request #${prNumber} no longer has the ci:deploy-cloud label, will delete associated deployment` + ); + deploymentsToPurge.push(deployment); + } else if (lastCommitTimestamp < NOW - 60 * 60 * 24 * 7) { + console.log( + `Pull Request #${prNumber} has not been updated in more than 7 days, will delete associated deployment` + ); + deploymentsToPurge.push(deployment); + } + } catch (ex) { + console.error(ex.toString()); + // deploymentsToPurge.push(deployment); // TODO should we delete on error? + } +} + +for (const deployment of deploymentsToPurge) { + console.log(`Scheduling deployment for deletion: ${deployment.name} / ${deployment.id}`); + try { + execSync(`ecctl deployment shutdown --force '${deployment.id}'`, { stdio: 'inherit' }); + } catch (ex) { + console.error(ex.toString()); + } +} diff --git a/.buildkite/scripts/steps/cloud/purge.sh b/.buildkite/scripts/steps/cloud/purge.sh new file mode 100755 index 0000000000000..265148c013400 --- /dev/null +++ b/.buildkite/scripts/steps/cloud/purge.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +node .buildkite/scripts/steps/cloud/purge.js diff --git a/.eslintrc.js b/.eslintrc.js index b21ad1f520a08..ce7e2dea0a14f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -853,12 +853,13 @@ module.exports = { }, /** - * APM and Observability overrides + * APM, UX and Observability overrides */ { files: [ 'x-pack/plugins/apm/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/observability/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/ux/**/*.{js,mjs,ts,tsx}', ], rules: { 'no-console': ['warn', { allow: ['error'] }], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f7709dbcfa8a9..604179ec75706 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -40,7 +40,7 @@ /src/plugins/chart_expressions/expression_metric/ @elastic/kibana-vis-editors /src/plugins/chart_expressions/expression_heatmap/ @elastic/kibana-vis-editors /src/plugins/chart_expressions/expression_gauge/ @elastic/kibana-vis-editors -/src/plugins/chart_expressions/expression_pie/ @elastic/kibana-vis-editors +/src/plugins/chart_expressions/expression_partition_vis/ @elastic/kibana-vis-editors /src/plugins/url_forwarding/ @elastic/kibana-vis-editors /packages/kbn-tinymath/ @elastic/kibana-vis-editors /x-pack/test/functional/apps/lens @elastic/kibana-vis-editors @@ -96,6 +96,7 @@ # Observability Shared /x-pack/plugins/observability/ @elastic/observability-ui +/x-pack/plugins/observability/public/components/shared/date_picker/ @elastic/uptime # Unified Observability /x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/unified-observability @@ -136,6 +137,8 @@ # Uptime /x-pack/plugins/uptime @elastic/uptime +/x-pack/plugins/ux @elastic/uptime +/x-pack/plugins/observability/public/components/shared/exploratory_view @elastic/uptime /x-pack/test/functional_with_es_ssl/apps/uptime @elastic/uptime /x-pack/test/functional/apps/uptime @elastic/uptime /x-pack/test/functional/es_archives/uptime @elastic/uptime diff --git a/.github/workflows/dev-doc-builder.yml b/.github/workflows/dev-doc-builder.yml new file mode 100644 index 0000000000000..bbc8745854e48 --- /dev/null +++ b/.github/workflows/dev-doc-builder.yml @@ -0,0 +1,22 @@ +name: Dev docs +on: + pull_request_target: + paths: + - '**.mdx' + - '**.docnav.json' + - '**.docapi.json' + - '**.devdocs.json' + - '**.jpg' + - '**.jpeg' + - '**.png' + - '**.gif' + types: [closed, opened, synchronize, reopened] + +jobs: + internal-docs: + uses: elastic/workflows/.github/workflows/dev-docs-builder.yml@main + secrets: + VERCEL_GITHUB_TOKEN: ${{ secrets.VERCEL_GITHUB_TOKEN }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID_DOCS_DEV: ${{ secrets.VERCEL_PROJECT_ID_DOCS_DEV }} diff --git a/.i18nrc.json b/.i18nrc.json index 043e1e28a0e9d..5c362908a1876 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -25,7 +25,7 @@ "expressionImage": "src/plugins/expression_image", "expressionMetric": "src/plugins/expression_metric", "expressionMetricVis": "src/plugins/chart_expressions/expression_metric", - "expressionPie": "src/plugins/chart_expressions/expression_pie", + "expressionPartitionVis": "src/plugins/chart_expressions/expression_partition_vis", "expressionRepeatImage": "src/plugins/expression_repeat_image", "expressionRevealImage": "src/plugins/expression_reveal_image", "expressions": "src/plugins/expressions", diff --git a/dev_docs/api_welcome.mdx b/dev_docs/api_welcome.mdx index cf88bf7eec0da..1127ecc8603b6 100644 --- a/dev_docs/api_welcome.mdx +++ b/dev_docs/api_welcome.mdx @@ -2,7 +2,7 @@ id: kibDevDocsApiWelcome slug: /kibana-dev-docs/api-meta/welcome title: Welcome -summary: How to use our automatically generated API documentation +description: How to use our automatically generated API documentation date: 2021-02-25 tags: ['kibana','dev', 'contributor', 'api docs'] --- diff --git a/dev_docs/getting_started/add_data.mdx b/dev_docs/getting_started/add_data.mdx index 46822b82fc40d..a94ec511cda5e 100644 --- a/dev_docs/getting_started/add_data.mdx +++ b/dev_docs/getting_started/add_data.mdx @@ -2,14 +2,14 @@ id: kibDevAddData slug: /kibana-dev-docs/getting-started/sample-data title: Add data -summary: Learn how to add data to Kibana +description: Learn how to add data to Kibana date: 2021-08-11 tags: ['kibana', 'onboarding', 'dev', 'architecture', 'tutorials'] --- Building a feature and need an easy way to test it out with some data? Below are three options. -## 1. Add Sample Data from the UI +## Sample data Kibana ships with sample data that you can install at the click of the button. If you are building a feature and need some data to test it out with, sample data is a great option. The only limitation is that this data will not work for Security or Observability solutions (see [#62962](https://github.com/elastic/kibana/issues/62962)). @@ -20,7 +20,7 @@ Kibana ships with sample data that you can install at the click of the button. I ![Sample Data](../assets/sample_data.png) -## CSV Upload +## CSV upload 1. If you don't have any data, navigate to Stack Management > Index Patterns and click the link to the uploader. If you do have data, navigate to the **Machine Learning** application. 2. Click on the **Data Visualizer** tab. @@ -35,3 +35,11 @@ The makelogs script generates sample web server logs. Make sure Elasticsearch is ```sh node scripts/makelogs --auth : ``` + +## Realistic solution data + + + +Security and Observability solution applications only work if data exists in particularly named indices, abiding by our [ECS format](https://www.elastic.co/guide/en/ecs/current/index.html). If you would like to use these applications with realistic data, check out the [oblt_cli tool](https://github.com/elastic/observability-test-environments/blob/master/tools/oblt_cli/README.md). This tool sets you up to connect to a remote Elasticsearch cluster that contains the appropriate data via CCS. + + diff --git a/dev_docs/kibana_server_core_components.mdx b/dev_docs/kibana_server_core_components.mdx index 503d8401d4492..c60e085144fff 100644 --- a/dev_docs/kibana_server_core_components.mdx +++ b/dev_docs/kibana_server_core_components.mdx @@ -2,7 +2,7 @@ id: kibServerAndCoreComponents slug: /kibana-dev-docs/core-intro title: Kibana Server and Core components -summary: An introduction to the Kibana server and core components. +description: An introduction to the Kibana server and core components. date: 2021-02-26 tags: ['kibana','onboarding', 'dev', 'architecture'] --- diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 03bccef3b822e..446c305c03b95 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -20,8 +20,6 @@ Review important information about the {kib} 8.0.0 releases. [[release-notes-8.0.0-rc2]] == {kib} 8.0.0-rc2 -coming::[8.0.0-rc2] - For information about the {kib} 8.0.0-rc2 release, review the following information. [float] diff --git a/docs/api/data-views/create.asciidoc b/docs/api/data-views/create.asciidoc index c1666589b7e2b..5224a238ea8f4 100644 --- a/docs/api/data-views/create.asciidoc +++ b/docs/api/data-views/create.asciidoc @@ -6,12 +6,14 @@ experimental[] Create data views. + [[data-views-api-create-request]] ==== Request -`POST :/api/index_patterns/index_pattern` +`POST :/api/data_views/data_view` + +`POST :/s//api/data_views/data_view` -`POST :/s//api/index_patterns/index_pattern` [[data-views-api-create-path-params]] ==== Path parameters @@ -19,6 +21,7 @@ experimental[] Create data views. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + [[data-views-api-create-body-params]] ==== Request body @@ -28,7 +31,8 @@ data view with the provided title already exists. The default is `false`. `refresh_fields`:: (Optional, boolean) Reloads data view fields after the data view is stored. The default is `false`. -`index_pattern`:: (Required, object) The data view object. All fields are optional. +`data_view`:: (Required, object) The data view object. All fields are optional. + [[data-views-api-create-request-codes]] ==== Response code @@ -36,6 +40,7 @@ the data view is stored. The default is `false`. `200`:: Indicates a successful call. + [[data-views-api-create-example]] ==== Examples @@ -43,9 +48,9 @@ Create a data view with a custom title: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/index_pattern +$ curl -X POST api/data_views/data_view { - "index_pattern": { + "data_view": { "title": "hello" } } @@ -56,11 +61,11 @@ Customize the creation behavior: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/index_pattern +$ curl -X POST api/data_views/data_view { "override": false, "refresh_fields": true, - "index_pattern": { + "data_view": { "title": "hello" } } @@ -71,9 +76,9 @@ At creation, all data view fields are optional: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/index_pattern +$ curl -X POST api/data_views/data_view { - "index_pattern": { + "data_view": { "id": "...", "version": "...", "title": "...", @@ -97,7 +102,6 @@ The API returns the data view object: [source,sh] -------------------------------------------------- { - "index_pattern": {...} + "data_view": {...} } -------------------------------------------------- - diff --git a/docs/api/data-views/default-get.asciidoc b/docs/api/data-views/default-get.asciidoc index 300a0612b18ea..51e5bf60d7097 100644 --- a/docs/api/data-views/default-get.asciidoc +++ b/docs/api/data-views/default-get.asciidoc @@ -6,12 +6,14 @@ experimental[] Retrieve a default data view ID. Kibana UI uses the default data view unless user picks a different one. + [[data-views-api-default-get-request]] ==== Request -`GET :/api/index_patterns/default` +`GET :/api/data_views/default` + +`GET :/s//api/data_views/default` -`GET :/s//api/index_patterns/default` [[data-views-api-default-get-params]] ==== Path parameters @@ -19,12 +21,14 @@ experimental[] Retrieve a default data view ID. Kibana UI uses the default data `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + [[data-views-api-default-get-codes]] ==== Response code `200`:: Indicates a successful call. + [[data-views-api-default-get-example]] ==== Example @@ -32,7 +36,7 @@ Retrieve the default data view id: [source,sh] -------------------------------------------------- -$ curl -X GET api/index_patterns/default +$ curl -X GET api/data_views/default -------------------------------------------------- // KIBANA @@ -41,7 +45,7 @@ The API returns an ID of a default data view: [source,sh] -------------------------------------------------- { - "index_pattern_id": "..." + "data_view_id": "..." } -------------------------------------------------- @@ -50,6 +54,7 @@ In case there is no default data view, the API returns: [source,sh] -------------------------------------------------- { - "index_pattern_id": null + "data_view_id": null } -------------------------------------------------- + diff --git a/docs/api/data-views/default-set.asciidoc b/docs/api/data-views/default-set.asciidoc index 752054cfb1748..dd62f859f7220 100644 --- a/docs/api/data-views/default-set.asciidoc +++ b/docs/api/data-views/default-set.asciidoc @@ -5,14 +5,16 @@ ++++ experimental[] Set a default data view ID. Kibana UI will use the default data view unless user picks a different one. -The API doesn't validate if given `index_pattern_id` is a valid id. +The API doesn't validate if given `data_view_id` is a valid id. + [[data-views-api-default-set-request]] ==== Request -`POST :/api/index_patterns/default` +`POST :/api/data_views/default` + +`POST :/s//api/data_views/default` -`POST :/s//api/index_patterns/default` [[data-views-api-default-set-params]] ==== Path parameters @@ -20,20 +22,23 @@ The API doesn't validate if given `index_pattern_id` is a valid id. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. + [[data-views-api-default-set-body]] ==== Request body -`index_pattern_id`:: (Required, `string` or `null`) Sets a default data view id. Use `null` to unset a default data view. +`data_view_id`:: (Required, `string` or `null`) Sets a default data view id. Use `null` to unset a default data view. `force`:: (Optional, boolean) Updates existing default data view id. The default is `false`. + [[data-views-api-default-set-codes]] ==== Response code `200`:: Indicates a successful call. + [[data-views-api-default-set-example]] ==== Example @@ -41,9 +46,9 @@ Set the default data view id if none is set: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/default +$ curl -X POST api/data_views/default { - "index_pattern_id": "..." + "data_view_id": "..." } -------------------------------------------------- // KIBANA @@ -53,9 +58,9 @@ Upsert the default data view: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/default +$ curl -X POST api/data_views/default { - "index_pattern_id": "...", + "data_view_id": "...", "force": true } -------------------------------------------------- @@ -65,9 +70,9 @@ Unset the default data view: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/default +$ curl -X POST api/data_views/default { - "index_pattern_id": null, + "data_view_id": null, "force": true } -------------------------------------------------- @@ -81,4 +86,3 @@ The API returns: "acknowledged": true } -------------------------------------------------- - diff --git a/docs/api/data-views/delete.asciidoc b/docs/api/data-views/delete.asciidoc index 9bc63c89365e7..a3165c799243d 100644 --- a/docs/api/data-views/delete.asciidoc +++ b/docs/api/data-views/delete.asciidoc @@ -8,12 +8,14 @@ experimental[] Delete data views. WARNING: Once you delete a data view, _it cannot be recovered_. + [[data-views-api-delete-request]] ==== Request -`DELETE :/api/index_patterns/index_pattern/` +`DELETE :/api/data_views/data_view/` + +`DELETE :/s//api/data_views/data_view/` -`DELETE :/s//api/index_patterns/index_pattern/` [[data-views-api-delete-path-params]] ==== Path parameters @@ -24,18 +26,20 @@ WARNING: Once you delete a data view, _it cannot be recovered_. `id`:: (Required, string) The ID of the data view you want to delete. + [[data-views-api-delete-response-codes]] ==== Response code `200`:: Indicates that data view is deleted. Returns an empty response body. + ==== Example -Delete a data view object with the `my-pattern` ID: +Delete a data view object with the `my-view` ID: [source,sh] -------------------------------------------------- -$ curl -X DELETE api/index_patterns/index_pattern/my-pattern +$ curl -X DELETE api/data_views/data_view/my-view -------------------------------------------------- // KIBANA diff --git a/docs/api/data-views/get.asciidoc b/docs/api/data-views/get.asciidoc index 0584dfeae1cd5..9d6a4160aeacf 100644 --- a/docs/api/data-views/get.asciidoc +++ b/docs/api/data-views/get.asciidoc @@ -6,12 +6,14 @@ experimental[] Retrieve a single data view by ID. + [[data-views-api-get-request]] ==== Request -`GET :/api/index_patterns/index_pattern/` +`GET :/api/data_views/data_view/` + +`GET :/s//api/data_views/data_view/` -`GET :/s//api/index_patterns/index_pattern/` [[data-views-api-get-params]] ==== Path parameters @@ -22,6 +24,7 @@ experimental[] Retrieve a single data view by ID. `id`:: (Required, string) The ID of the data view you want to retrieve. + [[data-views-api-get-codes]] ==== Response code @@ -31,14 +34,15 @@ Indicates a successful call. `404`:: The specified data view and ID doesn't exist. + [[data-views-api-get-example]] ==== Example -Retrieve the data view object with the `my-pattern` ID: +Retrieve the data view object with the `my-view` ID: [source,sh] -------------------------------------------------- -$ curl -X GET api/index_patterns/index_pattern/my-pattern +$ curl -X GET api/data_views/data_view/my-view -------------------------------------------------- // KIBANA @@ -47,8 +51,8 @@ The API returns a data view object: [source,sh] -------------------------------------------------- { - "index_pattern": { - "id": "my-pattern", + "data_view": { + "id": "my-view", "version": "...", "title": "...", "type": "...", diff --git a/docs/api/data-views/runtime-fields/create.asciidoc b/docs/api/data-views/runtime-fields/create.asciidoc index 442bede9cba7a..8f63de5bcf9e4 100644 --- a/docs/api/data-views/runtime-fields/create.asciidoc +++ b/docs/api/data-views/runtime-fields/create.asciidoc @@ -9,9 +9,9 @@ experimental[] Create a runtime field [[data-views-runtime-field-create-request]] ==== Request -`POST :/api/index_patterns/index_pattern//runtime_field` +`POST :/api/data_views/data_view//runtime_field` -`POST :/s//api/index_patterns/index_pattern//runtime_field` +`POST :/s//api/data_views/data_view//runtime_field` [[data-views-runtime-field-create-params]] ==== Path parameters @@ -19,7 +19,7 @@ experimental[] Create a runtime field `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. -`index_pattern_id`:: +`data_view_id`:: (Required, string) The ID of the data view. [[data-views-runtime-field-create-body]] @@ -37,7 +37,7 @@ Create a runtime field on a data view: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/index_pattern//runtime_field +$ curl -X POST api/data_views/data_view//runtime_field { "name": "runtimeFoo", "runtimeField": { @@ -50,12 +50,12 @@ $ curl -X POST api/index_patterns/index_pattern//runtime_field -------------------------------------------------- // KIBANA -The API returns created runtime field object and updated data view object: +The API returns created runtime field object array and updated data view object: [source,sh] -------------------------------------------------- { - "index_pattern": {...}, - "field": {...} + "data_view": {...}, + "fields": [...] } -------------------------------------------------- diff --git a/docs/api/data-views/runtime-fields/delete.asciidoc b/docs/api/data-views/runtime-fields/delete.asciidoc index b2a8e9f310477..a57d8f73ce140 100644 --- a/docs/api/data-views/runtime-fields/delete.asciidoc +++ b/docs/api/data-views/runtime-fields/delete.asciidoc @@ -9,9 +9,10 @@ experimental[] Delete a runtime field from a data view. [[data-views-runtime-field-api-delete-request]] ==== Request -`DELETE :/api/index_patterns/index_pattern//runtime_field/` +`DELETE :/api/data_views/data_view//runtime_field/` + +`DELETE :/s//api/data_views/data_view//runtime_field/` -`DELETE :/s//api/index_patterns/index_pattern//runtime_field/` [[data-views-runtime-field-api-delete-path-params]] ==== Path parameters @@ -19,19 +20,20 @@ experimental[] Delete a runtime field from a data view. `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. -`index_pattern_id`:: +`data_view_id`:: (Required, string) The ID of the data view your want to delete a runtime field from. `name`:: (Required, string) The name of the runtime field you want to delete. + ==== Example Delete a runtime field from a data view: [source,sh] -------------------------------------------------- -$ curl -X DELETE api/index_patterns/index_pattern//runtime_field/ +$ curl -X DELETE api/data_views/data_view//runtime_field/ -------------------------------------------------- // KIBANA diff --git a/docs/api/data-views/runtime-fields/get.asciidoc b/docs/api/data-views/runtime-fields/get.asciidoc index 66530ae5f3f3f..831744fdc06b5 100644 --- a/docs/api/data-views/runtime-fields/get.asciidoc +++ b/docs/api/data-views/runtime-fields/get.asciidoc @@ -6,12 +6,14 @@ experimental[] Get a runtime field + [[data-views-runtime-field-get-request]] ==== Request -`GET :/api/index_patterns/index_pattern//runtime_field/` +`GET :/api/data_views/data_view//runtime_field/` + +`GET :/s//api/data_views/data_view//runtime_field/` -`GET :/s//api/index_patterns/index_pattern//runtime_field/` [[data-views-runtime-field-get-params]] ==== Path parameters @@ -19,7 +21,7 @@ experimental[] Get a runtime field `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. -`index_pattern_id`:: +`data_view_id`:: (Required, string) The ID of the data view. `name`:: @@ -29,22 +31,22 @@ experimental[] Get a runtime field [[data-views-runtime-field-get-example]] ==== Example -Retrieve a runtime field named `foo` of data view with the `my-pattern` ID: +Retrieve a runtime field named `foo` of data view with the `my-view` ID: [source,sh] -------------------------------------------------- -$ curl -X GET api/index_patterns/index_pattern/my-pattern/runtime_field/foo +$ curl -X GET api/data_views/data_view/my-view/runtime_field/foo -------------------------------------------------- // KIBANA -The API returns a runtime `field` object, and a `runtimeField` definition object: +The API returns a runtime `field` object array, and a `runtimeField` definition object: [source,sh] -------------------------------------------------- { - "field": { + "fields": [ ... - }, + ], "runtimeField": { ... } diff --git a/docs/api/data-views/runtime-fields/update.asciidoc b/docs/api/data-views/runtime-fields/update.asciidoc index 970826560f4a8..86ef4cae93e1e 100644 --- a/docs/api/data-views/runtime-fields/update.asciidoc +++ b/docs/api/data-views/runtime-fields/update.asciidoc @@ -6,12 +6,14 @@ experimental[] Update an existing runtime field + [[data-views-runtime-field-update-request]] ==== Request -`POST :/api/index_patterns/index_pattern//runtime_field/` +`POST :/api/data_views/data_view//runtime_field/` + +`POST :/s//api/data_views/data_view//runtime_field/` -`POST :/s//api/index_patterns/index_pattern//runtime_field/` [[data-views-runtime-field-update-params]] ==== Path parameters @@ -19,12 +21,13 @@ experimental[] Update an existing runtime field `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. -`index_pattern_id`:: +`data_view_id`:: (Required, string) The ID of the data view. `name`:: (Required, string) The name of the runtime field you want to update. + [[data-views-runtime-field-update-body]] ==== Request body @@ -37,6 +40,7 @@ You can update following fields: + [[data-views-runtime-field-update-example]] ==== Examples @@ -44,7 +48,7 @@ Update an existing runtime field on a data view: [source,sh] -------------------------------------------------- -$ curl -X POST api/index_patterns/index_pattern//runtime_field/ +$ curl -X POST api/data_views/data_view//runtime_field/ { "runtimeField": { "script": { @@ -55,12 +59,13 @@ $ curl -X POST api/index_patterns/index_pattern//runtime_field -------------------------------------------------- // KIBANA -The API returns updated runtime field object and updated data view object: +The API returns updated runtime field object array and updated data view object: [source,sh] -------------------------------------------------- { - "index_pattern": {...}, - "field": {...} + "data_view": {...}, + "fields": [...] } -------------------------------------------------- + diff --git a/docs/api/data-views/runtime-fields/upsert.asciidoc b/docs/api/data-views/runtime-fields/upsert.asciidoc index 427aa30afa94a..959e0f0846b85 100644 --- a/docs/api/data-views/runtime-fields/upsert.asciidoc +++ b/docs/api/data-views/runtime-fields/upsert.asciidoc @@ -6,12 +6,14 @@ experimental[] Create or update an existing runtime field + [[data-views-runtime-field-upsert-request]] ==== Request -`PUT :/api/index_patterns/index_pattern//runtime_field` +`PUT :/api/data_views/data_view//runtime_field` + +`PUT :/s//api/data_views/data_view//runtime_field` -`PUT :/s//api/index_patterns/index_pattern//runtime_field` [[data-views-runtime-field-upsert-params]] ==== Path parameters @@ -19,9 +21,10 @@ experimental[] Create or update an existing runtime field `space_id`:: (Optional, string) An identifier for the space. If `space_id` is not provided in the URL, the default space is used. -`index_pattern_id`:: +`data_view_id`:: (Required, string) The ID of the data view. + [[data-views-runtime-field-upsert-body]] ==== Request body @@ -30,6 +33,7 @@ experimental[] Create or update an existing runtime field `runtimeField`:: (Required, object) The runtime field definition object. + [[data-views-runtime-field-upsert-example]] ==== Examples @@ -37,7 +41,7 @@ Create or update an existing runtime field on a data view: [source,sh] -------------------------------------------------- -$ curl -X PUT api/index_patterns/index_pattern//runtime_field +$ curl -X PUT api/data_views/data_view//runtime_field { "name": "runtimeFoo", "runtimeField": { @@ -50,12 +54,13 @@ $ curl -X PUT api/index_patterns/index_pattern//runtime_field -------------------------------------------------- // KIBANA -The API returns created or updated runtime field object and updated data view object: +The API returns created or updated runtime field object array and updated data view object: [source,sh] -------------------------------------------------- { - "index_pattern": {...}, - "field": {...} + "data_view": {...}, + "fields": [...] } -------------------------------------------------- + diff --git a/docs/api/data-views/update-fields.asciidoc b/docs/api/data-views/update-fields.asciidoc index 929dee27b9da1..babe689ce6b23 100644 --- a/docs/api/data-views/update-fields.asciidoc +++ b/docs/api/data-views/update-fields.asciidoc @@ -8,12 +8,14 @@ experimental[] Update fields presentation metadata, such as `count`, `customLabel`, and `format`. You can update multiple fields in one request. Updates are merged with persisted metadata. To remove existing metadata, specify `null` as the value. + [[data-views-fields-api-update-request]] ==== Request -`POST :/api/index_patterns/index_pattern//fields` +`POST :/api/data_views/data_view//fields` + +`POST :/s//api/data_views/data_view//fields` -`POST :/s//api/index_patterns/index_pattern//fields` [[data-views-fields-api-update-path-params]] ==== Path parameters @@ -24,6 +26,7 @@ are merged with persisted metadata. To remove existing metadata, specify `null` `id`:: (Required, string) The ID of the data view fields you want to update. + [[data-views-fields-api-update-request-body]] ==== Request body @@ -31,12 +34,14 @@ are merged with persisted metadata. To remove existing metadata, specify `null` (Required, object) the field object + [[data-views-fields-api-update-errors-codes]] ==== Response code `200`:: Indicates a successful call. + [[data-views-fields-api-update-example]] ==== Examples @@ -44,7 +49,7 @@ Set popularity `count` for field `foo`: [source,sh] -------------------------------------------------- -$ curl -X POST api/saved_objects/index-pattern/my-pattern/fields +$ curl -X POST api/saved_objects/data-view/my-view/fields { "fields": { "foo": { @@ -59,7 +64,7 @@ Update multiple metadata fields in one request: [source,sh] -------------------------------------------------- -$ curl -X POST api/saved_objects/index-pattern/my-pattern/fields +$ curl -X POST api/saved_objects/data-view/my-view/fields { "fields": { "foo": { @@ -77,7 +82,7 @@ $ curl -X POST api/saved_objects/index-pattern/my-pattern/fields Use `null` value to delete metadata: [source,sh] -------------------------------------------------- -$ curl -X POST api/saved_objects/index-pattern/my-pattern/fields +$ curl -X POST api/saved_objects/data-view/my-pattern/fields { "fields": { "foo": { @@ -93,7 +98,7 @@ The endpoint returns the updated data view object: [source,sh] -------------------------------------------------- { - "index_pattern": { + "data_view": { } } diff --git a/docs/api/data-views/update.asciidoc b/docs/api/data-views/update.asciidoc index 6157d46888277..b86a13682cb0b 100644 --- a/docs/api/data-views/update.asciidoc +++ b/docs/api/data-views/update.asciidoc @@ -7,12 +7,14 @@ experimental[] Update part of an data view. Only the specified fields are updated in the data view. Unspecified fields stay as they are persisted. + [[data-views-api-update-request]] ==== Request -`POST :/api/index_patterns/index_pattern/` +`POST :/api/data_views/data_view/` + +`POST :/s//api/data_views/data_view/` -`POST :/s//api/index_patterns/index_pattern/` [[data-views-api-update-path-params]] ==== Path parameters @@ -23,13 +25,14 @@ data view. Unspecified fields stay as they are persisted. `id`:: (Required, string) The ID of the data view you want to update. + [[data-views-api-update-request-body]] ==== Request body `refresh_fields`:: (Optional, boolean) Reloads the data view fields after the data view is updated. The default is `false`. -`index_pattern`:: +`data_view`:: (Required, object) The data view fields you want to update. + @@ -43,23 +46,25 @@ You can partially update the following fields: * `type` * `typeMeta` + [[data-views-api-update-errors-codes]] ==== Response code `200`:: Indicates a successful call. + [[data-views-api-update-example]] ==== Examples -Update a title of the `` data view: +Update a title of the `` data view: [source,sh] -------------------------------------------------- -$ curl -X POST api/saved_objects/index-pattern/my-pattern +$ curl -X POST api/saved_objects/data-view/my-view { - "index_pattern": { - "title": "some-other-pattern-*" + "data_view": { + "title": "some-other-view-*" } } -------------------------------------------------- @@ -69,10 +74,10 @@ Customize the update behavior: [source,sh] -------------------------------------------------- -$ curl -X POST api/saved_objects/index-pattern/my-pattern +$ curl -X POST api/saved_objects/data-view/my-view { "refresh_fields": true, - "index_pattern": { + "data_view": { "fields": {} } } @@ -84,9 +89,9 @@ All update fields are optional, but you can specify the following fields: [source,sh] -------------------------------------------------- -$ curl -X POST api/saved_objects/index-pattern/my-pattern +$ curl -X POST api/saved_objects/data-view/my-view { - "index_pattern": { + "data_view": { "title": "...", "timeFieldName": "...", "sourceFilters": [], @@ -105,8 +110,9 @@ The API returns the updated data view object: [source,sh] -------------------------------------------------- { - "index_pattern": { + "data_view": { } } -------------------------------------------------- + diff --git a/docs/developer/architecture/core/uisettings-service.asciidoc b/docs/developer/architecture/core/uisettings-service.asciidoc index 85ed9c9eabc72..e39213cb03cd7 100644 --- a/docs/developer/architecture/core/uisettings-service.asciidoc +++ b/docs/developer/architecture/core/uisettings-service.asciidoc @@ -3,14 +3,71 @@ NOTE: The UI settings service is available both server and client side. -=== Server side usage +=== Overview + +UI settings are configurable from the Advanced Settings page in Management and control the behavior of {kib}. uiSettings are stored in a config saved object and, as such, conform to the same conditions as other <>. + +There are several ways to configure an advanced setting: + +- <> +- <> +- <> +- <> + +Keep in mind that once you add a new advanced setting, you cannot change or remove it without <>. + +[[advanced-settings-ui]] +=== Configuration with Advanced Settings UI +The uiSettings service is the programmatic interface to Kibana's Advanced Settings UI. Kibana plugins use the service to extend Kibana UI Settings Management with custom settings for their plugin. + +Configuration through the Advanced Settings UI is restricted to users authorised to acces the Advanced Settings page. Users who don't have permissions to change these values default to using the settings configured to the space they are in. Config saved objects can be shared between spaces. + +[[uisettings-overrides]] +=== Configuration with UI settings overrides +experimental[] When a setting is configured as an override in kibana.yml, it will override any other value stored in the config saved object. If an override is misconfigured, it will fail config validation and prevent Kibana from starting up. The override applies to Kibana as a whole for all spaces and users and the option will be disabled in the Advanced Settings page. We refer to these as "global" overrides. + +[[client-side-usage]] +=== Client side usage +On the client, the `uiSettings` service is exposed directly from `core` and the {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.iuisettingsclient.md[client] provides plugins access to the `config` entries stored in {es}. + +In the interest of performance, `uiSettings` are cached. Any changes that require cache refreshes should register an instruction to reload the page when settings are configured in Advanced Settings using the `requiresPageReload` {kib-repo}blob/{branch}/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md[parameter]. + +[source,typescript] +---- +import { CoreSetup, Plugin } from 'src/core/public'; + +export class MyPlugin implements Plugin { + public setup(core: CoreSetup): MyPluginSetup { + … + core.uiSettings.getUpdate$().subscribe(({ key, newValue }) => { + if (key === 'custom') { + // do something with changes... + myPluginService.register({ + … + }) + } + }); + … + } + + public start(core: CoreStart): MyPluginStart { + return { + … + settings: { + getCustomValue: () => core.uiSettings.get('custom'), + … + }, + }; + } +} -The program interface to <>. -It makes it possible for Kibana plugins to extend Kibana UI Settings Management with custom settings. +---- -See: +[[server-side-usage]] +=== Server side usage +On the server, `uiSettings` are exposed directly from `core`. -- {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[UI settings service Setup API docs] +The following example shows how to {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsservicesetup.register.md[register] a new `custom` setting with a default value of '42'. When registering a new setting, you must provide a schema against which validations are performed on read and write. All the other {kib-repo}blob/{branch}/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md[parameters] are optional. [source,typescript] ---- @@ -38,3 +95,68 @@ export class MyPlugin implements Plugin { } ---- + +[[uisettings-migrations]] +=== Migrations + +[IMPORTANT] +============================================== +Migrations for 3rd party plugin advanced settings are not currently supported. If a 3rd party plugin registers an advanced setting, the setting is essentially permanent and cannot be fixed without manual intervention. +============================================== + +To change or remove a `uiSetting`, the whole `config` Saved Object needs to be migrated. `uiSettings` {kib-repo}blob/{branch}/src/core/server/ui_settings/saved_objects/migrations.ts[migrations] are declared directly in the service. + +For example, if we wanted to remove a `custom` setting, or rename `my_setting:fourtyTwo` to `my_other_setting:fourtyTwo`, we'd need two migration entries, one for each change targeting the version in which these changes apply: + +[source,typescript] +---- +export const migrations = { + ... + '8.1.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ + ...doc, + ...(doc.attributes && { + attributes: Object.keys(doc.attributes).reduce( + (acc, key) => + [ + // other settings to remove for 8.1.0... + 'custom', + ].includes(key) + ? { + ...acc, + } + : { + ...acc, + [key]: doc.attributes[key], + }, + {} + ), + }), + references: doc.references || [], + }), + '8.2.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ + ...doc, + ...(doc.attributes && { + attributes: Object.keys(doc.attributes).reduce( + (acc, key) => + key.startsWith('my_setting:') + ? { + ...acc, + [key.replace('my_setting', 'my_other_setting')]: doc.attributes[key], + } + : { + ...acc, + [key]: doc.attributes[key], + }, + {} + ), + }), + references: doc.references || [], + }), + … +} +---- + +[TIP] +============================================== +Plugins can leverage the optional deprecation parameter on registration for handling deprecation notices and renames. The deprecation warnings are rendered in the Advanced Settings UI and should also be added to the <> guide. +============================================== diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index fa7613049f9d6..ea7ea24690886 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -118,8 +118,8 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |Expression MetricVis plugin adds a metric renderer and function to the expression plugin. The renderer will display the metric chart. -|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_pie/README.md[expressionPie] -|Expression Pie plugin adds a pie renderer and function to the expression plugin. The renderer will display the Pie chart. +|{kib-repo}blob/{branch}/src/plugins/chart_expressions/expression_partition_vis/README.md[expressionPartitionVis] +|Expression Partition Visualization plugin adds a partitionVis renderer and pieVis, mosaicVis, treemapVis, waffleVis functions to the expression plugin. The renderer will display the pie, waffle, treemap and mosaic charts. |{kib-repo}blob/{branch}/src/plugins/expression_repeat_image/README.md[expressionRepeatImage] @@ -635,6 +635,10 @@ in their infrastructure. |NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin. +|{kib-repo}blob/{branch}/x-pack/plugins/ux/readme.md[ux] +|https://docs.elastic.dev/kibana-dev-docs/welcome + + |{kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher] |This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation): diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index ede29f5e3d2c8..450af99a5b234 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -244,7 +244,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [APP\_WRAPPER\_CLASS](./kibana-plugin-core-server.app_wrapper_class.md) | The class name for top level \*and\* nested application wrappers to ensure proper layout | | [kibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution. | -| [mergeSavedObjectMigrationMaps](./kibana-plugin-core-server.mergesavedobjectmigrationmaps.md) | Merges two saved object migration maps. | +| [mergeSavedObjectMigrationMaps](./kibana-plugin-core-server.mergesavedobjectmigrationmaps.md) | Merges two saved object migration maps.If there is a migration for a given version on only one of the maps, that migration function will be used:mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '4.5.6': g }) -> { '1.2.3': f, '4.5.6': g }If there is a migration for a given version on both maps, the migrations will be composed:mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '1.2.3': g }) -> { '1.2.3': (doc, context) => f(g(doc, context), context) } | | [pollEsNodesVersion](./kibana-plugin-core-server.pollesnodesversion.md) | | | [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) | The current "level" of availability of a service. | | [validBodyOutput](./kibana-plugin-core-server.validbodyoutput.md) | The set of valid body.output | diff --git a/docs/development/core/server/kibana-plugin-core-server.mergesavedobjectmigrationmaps.md b/docs/development/core/server/kibana-plugin-core-server.mergesavedobjectmigrationmaps.md index 52d40097ca487..68cd580b57882 100644 --- a/docs/development/core/server/kibana-plugin-core-server.mergesavedobjectmigrationmaps.md +++ b/docs/development/core/server/kibana-plugin-core-server.mergesavedobjectmigrationmaps.md @@ -6,6 +6,14 @@ Merges two saved object migration maps. +If there is a migration for a given version on only one of the maps, that migration function will be used: + +mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '4.5.6': g }) -> { '1.2.3': f, '4.5.6': g } + +If there is a migration for a given version on both maps, the migrations will be composed: + +mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '1.2.3': g }) -> { '1.2.3': (doc, context) => f(g(doc, context), context) } + Signature: ```typescript diff --git a/docs/management/connectors/action-types/index.asciidoc b/docs/management/connectors/action-types/index.asciidoc index 98f7dac4de81d..01e9e3b22e2c2 100644 --- a/docs/management/connectors/action-types/index.asciidoc +++ b/docs/management/connectors/action-types/index.asciidoc @@ -105,7 +105,7 @@ experimental[] {kib} offers a preconfigured index connector to facilitate indexi [WARNING] ================================================== -This functionality is experimental and may be changed or removed completely in a future release. +This functionality is in technical preview and may be changed or removed completely in a future release. ================================================== To use this connector, set the <> configuration to `true`. diff --git a/docs/management/upgrade-assistant/index.asciidoc b/docs/management/upgrade-assistant/index.asciidoc index dbff211cef372..ccd3f41b9d886 100644 --- a/docs/management/upgrade-assistant/index.asciidoc +++ b/docs/management/upgrade-assistant/index.asciidoc @@ -11,10 +11,13 @@ enables you to see if you are using deprecated features, and guides you through the process of resolving issues. If you have indices that were created prior to 7.0, -you can use the assistant to reindex them so they can be accessed from 8.0. +you can use the assistant to reindex them so they can be accessed from 8.0+. IMPORTANT: To see the most up-to-date deprecation information before -upgrading to 8.0, upgrade to the latest 7.n release. +upgrading to 8.0, upgrade to the latest {prev-major-last} release. + +For more information about upgrading, +refer to {stack-ref}/upgrading-elastic-stack.html[Upgrading to Elastic {version}.] [discrete] === Required permissions diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 4e0f0d4f99e66..ff6ccbd6fab36 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -385,3 +385,13 @@ This content has moved. Refer to <>. == Kibana role management. This content has moved. Refer to <>. + +[role="exclude" logging-configuration-changes] +== Logging configuration changes + +This content has moved. Refer to <>. + +[role="exclude" upgrade-migrations] +== Upgrade migrations + +This content has moved. Refer to <>. diff --git a/docs/settings/task-manager-settings.asciidoc b/docs/settings/task-manager-settings.asciidoc index 286bb71542b3a..b7423d7c37b31 100644 --- a/docs/settings/task-manager-settings.asciidoc +++ b/docs/settings/task-manager-settings.asciidoc @@ -33,7 +33,7 @@ This flag will enable automatic warn and error logging if task manager self dete The amount of seconds we allow a task to delay before printing a warning server log. Defaults to 60. `xpack.task_manager.ephemeral_tasks.enabled`:: -Enables an experimental feature that executes a limited (and configurable) number of actions in the same task as the alert which triggered them. +Enables a technical preview feature that executes a limited (and configurable) number of actions in the same task as the alert which triggered them. These action tasks will reduce the latency of the time it takes an action to run after it's triggered, but are not persisted as SavedObjects. These non-persisted action tasks have a risk that they won't be run at all if the Kibana instance running them exits unexpectedly. Defaults to false. diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index c828b837d8efd..d8e08b460e5f6 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -1,114 +1,34 @@ [[upgrade]] == Upgrade {kib} -You can always upgrade to the latest patch release or from one minor version -to another within the same major version series. +To upgrade from 7.16 or earlier to {version}, +**You must first upgrade to {prev-major-last}**. +This enables you to use the Upgrade Assistant to +{stack-ref}/upgrading-elastic-stack.html#prepare-to-upgrade[prepare to upgrade]. +You must resolve all critical issues identified by the Upgrade Assistant +before proceeding with the upgrade. -For major version upgrades: - -. Upgrade to the last minor version released before the new major version. -. Use the Upgrade Assistant to determine what changes you need to make before the major version upgrade. -. When you've addressed all the critical issues, upgrade {es} and then upgrade {kib}. - -IMPORTANT: You can upgrade to pre-release versions of 8.0 for testing, -but upgrading from a pre-release to the final GA version is not supported. -Pre-releases should only be used for testing in a temporary environment. - -[discrete] -[[upgrade-paths]] -=== Recommended upgrade paths to 8.0 - -[cols="<1,3",options="header",] -|==== -|Upgrading from -|Upgrade path - -|7.16 -|Upgrade to 8.0 - -|6.8–7.15 -a| - -. Upgrade to 7.16 -. Upgrade to 8.0 - -|6.0–6.7 -a| - -. Upgrade to 6.8 -. Upgrade to 7.16 -. Upgrade to 8.0 -|==== - -[float] -[[upgrade-before-you-begin]] -=== Before you begin +{kib} does not support rolling upgrades. +You must shut down all {kib} instances, install the new software, and restart {kib}. +Upgrading while older {kib} instances are running can cause data loss or upgrade failures. [WARNING] ==== -{kib} automatically runs upgrade migrations when required. To roll back to an -earlier version in case of an upgrade failure, you **must** have a +{kib} automatically runs <> +when required. +In case of an upgrade failure, you can roll back to an +earlier version of {kib}. To roll back, you **must** have a {ref}/snapshot-restore.html[backup snapshot] that includes the `kibana` feature state. Snapshots include this feature state by default. - -For more information, refer to <>. ==== -Before you upgrade {kib}: +For more information about upgrading, +refer to {stack-ref}/upgrading-elastic-stack.html[Upgrading to Elastic {version}.] -* Consult the <>. -* {ref}/snapshots-take-snapshot.html[Take a snapshot] of your data. To roll back to an earlier version, the snapshot must include the `kibana` feature state. -* Before you upgrade production servers, test the upgrades in a dev environment. -* See <> for common reasons upgrades fail and how to prevent these. -* If you are using custom plugins, check that a compatible version is - available. -* Shut down all {kib} instances. Running more than one {kib} version against - the same Elasticseach index is unsupported. Upgrading while older {kib} - instances are running can cause data loss or upgrade failures. - -NOTE: {kib} logging system may have changed, depending on your target version. For details, see <>. - -To identify the changes you need to make to upgrade, and to enable you to -perform an Elasticsearch rolling upgrade with no downtime, you must upgrade to -6.7 before you upgrade to 7.0. - -For a comprehensive overview of the upgrade process, refer to -*{stack-ref}/upgrading-elastic-stack.html[Upgrading the Elastic Stack]*. - -[float] -[[upgrade-5x-earlier]] -=== Upgrade from 5.x or earlier -{es} can read indices created in the previous major version. Before you upgrade -to 7.0.0, you must reindex or delete any indices created in 5.x or earlier. -For more information, refer to -{stack-ref}/upgrading-elastic-stack.html[Upgrading the Elastic Stack]. - -When your reindex is complete, follow the <> -instructions. - -[float] -[[upgrade-6x]] -=== Upgrade from 6.x - -The recommended path is to upgrade to 6.8 before upgrading to 7.0. This makes it -easier to identify the required changes, and enables you to use the Upgrade -Assistant to prepare for your upgrade to 7.0. - -TIP: The ability to import {kib} 6.x saved searches, visualizations, and -dashboards is supported. - -[float] -[[upgrade-67]] -=== Upgrade from 6.8 -To help you prepare for your upgrade to 7.0, 6.8 includes an https://www.elastic.co/guide/en/kibana/6.8/upgrade-assistant.html[Upgrade Assistant] -To access the assistant, go to *Management > 7.0 Upgrade Assistant*. - -After you have addressed any issues that were identified by the Upgrade -Assistant, <>. - - -include::upgrade/upgrade-standard.asciidoc[] +IMPORTANT: You can upgrade to pre-release versions for testing, +but upgrading from a pre-release to the General Available version is not supported. +Pre-releases should only be used for testing in a temporary environment. -include::upgrade/upgrade-migrations.asciidoc[] +include::upgrade/upgrade-migrations.asciidoc[leveloffset=-1] include::upgrade/logging-configuration-changes.asciidoc[] diff --git a/docs/setup/upgrade/logging-configuration-changes.asciidoc b/docs/setup/upgrade/logging-configuration-changes.asciidoc index a7a86fcb45b14..4d5f5f732536e 100644 --- a/docs/setup/upgrade/logging-configuration-changes.asciidoc +++ b/docs/setup/upgrade/logging-configuration-changes.asciidoc @@ -1,4 +1,5 @@ -[[logging-configuration-changes]] +[discrete] +[[logging-config-changes]] === Logging configuration changes WARNING: {kib} 8.0 and later uses a new logging system. Be sure to read the documentation for your version of {kib} before proceeding. diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index e9e1b757fd71d..fc921f9118bdf 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -1,5 +1,6 @@ -[[upgrade-migrations]] -=== Upgrade migrations +[float] +[[saved-object-migrations]] +=== Saved object migrations Every time {kib} is upgraded it will perform an upgrade migration to ensure that all <> are compatible with the new version. diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index 15d5db7395ec6..58f61b79f3ba6 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -462,7 +462,7 @@ When "thom" logs in, a "user_login" {kib} audit event is written: [source,json] ------------- -{"event":{"action":"user_login","category":["authentication"],"outcome":"success"},"user":{"name":"thom","roles":["superuser"]},"@timestamp":"2022-01-25T09:40:39.267-05:00","message":"User [thom] has logged in using basic provider [name=basic]","trace":{"id":"818cbf3..."}} +{"event":{"action":"user_login","category":["authentication"],"outcome":"success"},"kibana":{"session_id":"ab93zdA..."},"user":{"name":"thom","roles":["superuser"]},"@timestamp":"2022-01-25T09:40:39.267-05:00","message":"User [thom] has logged in using basic provider [name=basic]","trace":{"id":"818cbf3..."}} ------------- The `trace.id` value `"818cbf3..."` in the {kib} audit event can be correlated with the `opaque_id` value in these six {es} audit events: diff --git a/examples/embeddable_explorer/public/plugin.tsx b/examples/embeddable_explorer/public/plugin.tsx index f6cdee1da126c..55c2a215e436d 100644 --- a/examples/embeddable_explorer/public/plugin.tsx +++ b/examples/embeddable_explorer/public/plugin.tsx @@ -63,7 +63,7 @@ export class EmbeddableExplorerPlugin implements Plugin `[${group}/${id}=${value}]`) .join(' ')}`, + })); + } + + /** + * Send test reports to ci-stats + */ + async reportTests({ group, testRuns }: CiStatsReportTestsOptions) { + if (!this.config?.buildId || !this.config?.apiToken) { + throw new Error( + 'unable to report tests unless buildId is configured and auth config available' + ); + } + + return await this.req({ + auth: true, + path: '/v1/test_group', + query: { + buildId: this.config?.buildId, + }, + bodyDesc: `[${group.name}/${group.type}] test groups with ${testRuns.length} tests`, + body: [ + JSON.stringify({ group }), + ...testRuns.map((testRun) => JSON.stringify({ testRun })), + ].join('\n'), }); } @@ -241,7 +295,7 @@ export class CiStatsReporter { } } - private async req({ auth, body, bodyDesc, path }: ReqOptions) { + private async req({ auth, body, bodyDesc, path, query }: ReqOptions) { let attempt = 0; const maxAttempts = 5; @@ -251,23 +305,24 @@ export class CiStatsReporter { Authorization: `token ${this.config.apiToken}`, }; } else if (auth) { - throw new Error('this.req() shouldnt be called with auth=true if this.config is defined'); + throw new Error('this.req() shouldnt be called with auth=true if this.config is not defined'); } while (true) { attempt += 1; try { - await Axios.request({ + const resp = await Axios.request({ method: 'POST', url: path, baseURL: BASE_URL, headers, data: body, + params: query, adapter: httpAdapter, }); - return true; + return resp.data; } catch (error) { if (!error?.request) { // not an axios error, must be a usage error that we should notify user about diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_test_group_types.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_test_group_types.ts new file mode 100644 index 0000000000000..147d4e19325b2 --- /dev/null +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_test_group_types.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CiStatsMetadata } from './ci_stats_reporter'; + +export type CiStatsTestResult = 'fail' | 'pass' | 'skip'; +export type CiStatsTestType = + | 'after all hook' + | 'after each hook' + | 'before all hook' + | 'before each hook' + | 'test'; + +export interface CiStatsTestRun { + /** + * ISO-8601 formatted datetime representing when the tests started running + */ + startTime: string; + /** + * Duration of the tests in milliseconds + */ + durationMs: number; + /** + * A sequence number, this is used to order the tests in a specific test run + */ + seq: number; + /** + * The type of this "test run", usually this is just "test" but when reporting issues in hooks it can be set to the type of hook + */ + type: CiStatsTestType; + /** + * "fail", "pass" or "skip", the result of the tests + */ + result: CiStatsTestResult; + /** + * The list of suite names containing this test, the first being the outermost suite + */ + suites: string[]; + /** + * The name of this specific test run + */ + name: string; + /** + * Relative path from the root of the repo contianing this test + */ + file: string; + /** + * Error message if the test failed + */ + error?: string; + /** + * Debug output/stdout produced by the test + */ + stdout?: string; + /** + * Screenshots captured during the test run + */ + screenshots?: Array<{ + name: string; + base64Png: string; + }>; +} + +export interface CiStatsTestGroupInfo { + /** + * ISO-8601 formatted datetime representing when the group of tests started running + */ + startTime: string; + /** + * The number of miliseconds that the tests ran for + */ + durationMs: number; + /** + * The type of tests run in this group, any value is valid but test groups are groupped by type in the UI so use something consistent + */ + type: string; + /** + * The name of this specific group (within the "type") + */ + name: string; + /** + * Arbitrary metadata associated with this group. We currently look for a ciGroup metadata property for highlighting that when appropriate + */ + meta: CiStatsMetadata; +} diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/index.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/index.ts index 318a2921517f1..cf80d06613dbf 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/index.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/index.ts @@ -10,3 +10,4 @@ export * from './ci_stats_reporter'; export type { Config } from './ci_stats_config'; export * from './ship_ci_stats_cli'; export { getTimeReporter } from './report_time'; +export * from './ci_stats_test_group_types'; diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index b2e8641cd62f2..5f4c07ee6067c 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -64,6 +64,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { }, discover: { guide: `${KIBANA_DOCS}discover.html`, + fieldStatistics: `${KIBANA_DOCS}show-field-statistics.html`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, @@ -234,6 +235,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { asyncSearch: `${ELASTICSEARCH_DOCS}async-search-intro.html`, dataStreams: `${ELASTICSEARCH_DOCS}data-streams.html`, deprecationLogging: `${ELASTICSEARCH_DOCS}logging.html#deprecation-logging`, + frozenIndices: `${ELASTICSEARCH_DOCS}frozen-indices.html`, hiddenIndices: `${ELASTICSEARCH_DOCS}multi-index.html#hidden`, ilm: `${ELASTICSEARCH_DOCS}index-lifecycle-management.html`, ilmForceMerge: `${ELASTICSEARCH_DOCS}ilm-forcemerge.html`, diff --git a/packages/kbn-es-archiver/BUILD.bazel b/packages/kbn-es-archiver/BUILD.bazel index 06a0ca02da04a..fed3f248c0995 100644 --- a/packages/kbn-es-archiver/BUILD.bazel +++ b/packages/kbn-es-archiver/BUILD.bazel @@ -45,7 +45,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-dev-utils:npm_module_types", - "//packages/kbn-test", + "//packages/kbn-test:npm_module_types", "//packages/kbn-utils:npm_module_types", "@npm//@elastic/elasticsearch", "@npm//aggregate-error", diff --git a/packages/kbn-i18n/GUIDELINE.md b/packages/kbn-i18n/GUIDELINE.md index 7ffc4b078c79b..98f6f176d0be8 100644 --- a/packages/kbn-i18n/GUIDELINE.md +++ b/packages/kbn-i18n/GUIDELINE.md @@ -506,7 +506,7 @@ Testing React component that uses the `injectI18n` higher-order component is mor With shallow rendering only top level component is rendered, that is a wrapper itself, not the original component. Since we want to test the rendering of the original component, we need to access it via the wrapper's `WrappedComponent` property. Its value will be the component we passed into `injectI18n()`. -When testing such component, use the `shallowWithIntl` helper function defined in `@kbn/test/jest` and pass the component's `WrappedComponent` property to render the wrapped component. This will shallow render the component with Enzyme and inject the necessary context and props to use the `intl` mock defined in `test_utils/mocks/intl`. +When testing such component, use the `shallowWithIntl` helper function defined in `@kbn/test-jest-helpers` and pass the component's `WrappedComponent` property to render the wrapped component. This will shallow render the component with Enzyme and inject the necessary context and props to use the `intl` mock defined in `test_utils/mocks/intl`. Use the `mountWithIntl` helper function to mount render the component. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 3e5b2c77d1839..3a7c50feb38b7 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -119,5 +119,6 @@ pageLoadAssetSize: screenshotting: 17017 expressionGauge: 25000 controls: 34788 - expressionPie: 26338 + expressionPartitionVis: 26338 sharedUX: 16225 + ux: 20784 diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index d4105a5edd7a5..d0fe0f269f6fa 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -9049,7 +9049,9 @@ var _ci_stats_config = __webpack_require__(218); */ // @ts-expect-error not "public", but necessary to prevent Jest shimming from breaking things const BASE_URL = 'https://ci-stats.kibana.dev'; +/** Container for metadata that can be attached to different ci-stats objects */ +/** Object that helps report data to the ci-stats service */ class CiStatsReporter { /** * Create a CiStatsReporter by inspecting the ENV for the necessary config @@ -9146,7 +9148,7 @@ class CiStatsReporter { totalMem: _os.default.totalmem() }; this.log.debug('CIStatsReporter committerHash: %s', defaultMeta.committerHash); - return await this.req({ + return !!(await this.req({ auth: !!buildId, path: '/v1/timings', body: { @@ -9156,7 +9158,7 @@ class CiStatsReporter { timings }, bodyDesc: timings.length === 1 ? `${timings.length} timing` : `${timings.length} timings` - }); + })); } /** * Report metrics data to the ci-stats service. If running outside of CI this method @@ -9174,10 +9176,10 @@ class CiStatsReporter { const buildId = (_this$config4 = this.config) === null || _this$config4 === void 0 ? void 0 : _this$config4.buildId; if (!buildId) { - throw new Error(`CiStatsReporter can't be authorized without a buildId`); + throw new Error(`metrics can't be reported without a buildId`); } - return await this.req({ + return !!(await this.req({ auth: true, path: '/v1/metrics', body: { @@ -9190,6 +9192,35 @@ class CiStatsReporter { id, value }) => `[${group}/${id}=${value}]`).join(' ')}` + })); + } + /** + * Send test reports to ci-stats + */ + + + async reportTests({ + group, + testRuns + }) { + var _this$config5, _this$config6, _this$config7; + + if (!((_this$config5 = this.config) !== null && _this$config5 !== void 0 && _this$config5.buildId) || !((_this$config6 = this.config) !== null && _this$config6 !== void 0 && _this$config6.apiToken)) { + throw new Error('unable to report tests unless buildId is configured and auth config available'); + } + + return await this.req({ + auth: true, + path: '/v1/test_group', + query: { + buildId: (_this$config7 = this.config) === null || _this$config7 === void 0 ? void 0 : _this$config7.buildId + }, + bodyDesc: `[${group.name}/${group.type}] test groups with ${testRuns.length} tests`, + body: [JSON.stringify({ + group + }), ...testRuns.map(testRun => JSON.stringify({ + testRun + }))].join('\n') }); } /** @@ -9239,7 +9270,8 @@ class CiStatsReporter { auth, body, bodyDesc, - path + path, + query }) { let attempt = 0; const maxAttempts = 5; @@ -9250,22 +9282,23 @@ class CiStatsReporter { Authorization: `token ${this.config.apiToken}` }; } else if (auth) { - throw new Error('this.req() shouldnt be called with auth=true if this.config is defined'); + throw new Error('this.req() shouldnt be called with auth=true if this.config is not defined'); } while (true) { attempt += 1; try { - await _axios.default.request({ + const resp = await _axios.default.request({ method: 'POST', url: path, baseURL: BASE_URL, headers, data: body, + params: query, adapter: _http.default }); - return true; + return resp.data; } catch (error) { var _error$response; diff --git a/packages/kbn-test-jest-helpers/BUILD.bazel b/packages/kbn-test-jest-helpers/BUILD.bazel new file mode 100644 index 0000000000000..c713e24592944 --- /dev/null +++ b/packages/kbn-test-jest-helpers/BUILD.bazel @@ -0,0 +1,182 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@npm//@babel/cli:index.bzl", "babel") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_BASE_NAME = "kbn-test-jest-helpers" +PKG_REQUIRE_NAME = "@kbn/test-jest-helpers" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__test-jest-helpers" + +SOURCE_FILES = glob( + [ + "src/**/*" + ], + exclude = [ + "**/*.test.*", + "**/*.snap", + "**/__fixture__/**", + "**/__fixtures__/**", + "**/__snapshots__/**", + ] +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "jest.config.js", + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/kbn-dev-utils", + "//packages/kbn-i18n-react", + "//packages/kbn-std", + "//packages/kbn-utils", + "@npm//@elastic/elasticsearch", + "@npm//axios", + "@npm//@babel/traverse", + "@npm//chance", + "@npm//dedent", + "@npm//del", + "@npm//enzyme", + "@npm//execa", + "@npm//exit-hook", + "@npm//form-data", + "@npm//getopts", + "@npm//globby", + "@npm//he", + "@npm//history", + "@npm//jest", + "@npm//jest-cli", + "@npm//jest-snapshot", + "@npm//jest-styled-components", + "@npm//@jest/reporters", + "@npm//joi", + "@npm//mustache", + "@npm//normalize-path", + "@npm//parse-link-header", + "@npm//prettier", + "@npm//react", + "@npm//react-dom", + "@npm//react-redux", + "@npm//react-router-dom", + "@npm//redux", + "@npm//rxjs", + "@npm//semver", + "@npm//strip-ansi", + "@npm//xmlbuilder", + "@npm//xml2js", +] + +TYPES_DEPS = [ + "//packages/kbn-dev-utils:npm_module_types", + "//packages/kbn-i18n-react:npm_module_types", + "//packages/kbn-std:npm_module_types", + "//packages/kbn-utils:npm_module_types", + "@npm//@elastic/elasticsearch", + "@npm//axios", + "@npm//elastic-apm-node", + "@npm//del", + "@npm//exit-hook", + "@npm//form-data", + "@npm//getopts", + "@npm//jest", + "@npm//jest-cli", + "@npm//jest-snapshot", + "@npm//redux", + "@npm//rxjs", + "@npm//xmlbuilder", + "@npm//@types/chance", + "@npm//@types/dedent", + "@npm//@types/enzyme", + "@npm//@types/he", + "@npm//@types/history", + "@npm//@types/jest", + "@npm//@types/joi", + "@npm//@types/lodash", + "@npm//@types/mustache", + "@npm//@types/normalize-path", + "@npm//@types/node", + "@npm//@types/parse-link-header", + "@npm//@types/prettier", + "@npm//@types/react", + "@npm//@types/react-dom", + "@npm//@types/react-redux", + "@npm//@types/react-router-dom", + "@npm//@types/semver", + "@npm//@types/xml2js", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-test-jest-helpers/jest.config.js b/packages/kbn-test-jest-helpers/jest.config.js new file mode 100644 index 0000000000000..5c5d5c6395999 --- /dev/null +++ b/packages/kbn-test-jest-helpers/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-test-jest-helpers'], +}; diff --git a/packages/kbn-test-jest-helpers/package.json b/packages/kbn-test-jest-helpers/package.json new file mode 100644 index 0000000000000..afab6001d605d --- /dev/null +++ b/packages/kbn-test-jest-helpers/package.json @@ -0,0 +1,10 @@ +{ + "name": "@kbn/test-jest-helpers", + "version": "1.0.0", + "private": true, + "license": "SSPL-1.0 OR Elastic License 2.0", + "main": "./target_node", + "kibana": { + "devOnly": true + } +} diff --git a/packages/kbn-test/src/jest/utils/enzyme_helpers.tsx b/packages/kbn-test-jest-helpers/src/enzyme_helpers.tsx similarity index 98% rename from packages/kbn-test/src/jest/utils/enzyme_helpers.tsx rename to packages/kbn-test-jest-helpers/src/enzyme_helpers.tsx index 696a1d1b63163..222689d621b5d 100644 --- a/packages/kbn-test/src/jest/utils/enzyme_helpers.tsx +++ b/packages/kbn-test-jest-helpers/src/enzyme_helpers.tsx @@ -14,6 +14,7 @@ */ import { I18nProvider, InjectedIntl, intlShape, __IntlProvider } from '@kbn/i18n-react'; +// eslint-disable-next-line import/no-extraneous-dependencies import { mount, ReactWrapper, render, shallow } from 'enzyme'; import React, { ReactElement, ValidationMap } from 'react'; import { act as reactAct } from 'react-dom/test-utils'; @@ -118,7 +119,7 @@ export function renderWithIntl( context?: any; childContextTypes?: ValidationMap; } = {} -) { +): any { const options = getOptions(context, childContextTypes, props); return render(nodeWithIntlProp(node), options); diff --git a/packages/kbn-test/src/jest/utils/find_test_subject.ts b/packages/kbn-test-jest-helpers/src/find_test_subject.ts similarity index 97% rename from packages/kbn-test/src/jest/utils/find_test_subject.ts rename to packages/kbn-test-jest-helpers/src/find_test_subject.ts index ef3a744fbd99c..9d519f5197cd7 100644 --- a/packages/kbn-test/src/jest/utils/find_test_subject.ts +++ b/packages/kbn-test-jest-helpers/src/find_test_subject.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +// eslint-disable-next-line import/no-extraneous-dependencies import { ReactWrapper } from 'enzyme'; type Matcher = '=' | '~=' | '|=' | '^=' | '$=' | '*='; diff --git a/packages/kbn-test/src/jest/utils/index.ts b/packages/kbn-test-jest-helpers/src/index.ts similarity index 96% rename from packages/kbn-test/src/jest/utils/index.ts rename to packages/kbn-test-jest-helpers/src/index.ts index a797a801eaf94..3594df854cbe4 100644 --- a/packages/kbn-test/src/jest/utils/index.ts +++ b/packages/kbn-test-jest-helpers/src/index.ts @@ -8,8 +8,6 @@ export * from './enzyme_helpers'; -export * from './get_url'; - export * from './find_test_subject'; export * from './jsdom_svg_mocks'; diff --git a/packages/kbn-test/src/jest/utils/jsdom_svg_mocks.ts b/packages/kbn-test-jest-helpers/src/jsdom_svg_mocks.ts similarity index 100% rename from packages/kbn-test/src/jest/utils/jsdom_svg_mocks.ts rename to packages/kbn-test-jest-helpers/src/jsdom_svg_mocks.ts diff --git a/packages/kbn-test/src/jest/utils/random.ts b/packages/kbn-test-jest-helpers/src/random.ts similarity index 91% rename from packages/kbn-test/src/jest/utils/random.ts rename to packages/kbn-test-jest-helpers/src/random.ts index 4aa8a30555e0c..9f4efccf810f8 100644 --- a/packages/kbn-test/src/jest/utils/random.ts +++ b/packages/kbn-test-jest-helpers/src/random.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +// eslint-disable-next-line import/no-extraneous-dependencies import Chance from 'chance'; const chance = new Chance(); diff --git a/packages/kbn-test/src/jest/utils/redux_helpers.tsx b/packages/kbn-test-jest-helpers/src/redux_helpers.tsx similarity index 100% rename from packages/kbn-test/src/jest/utils/redux_helpers.tsx rename to packages/kbn-test-jest-helpers/src/redux_helpers.tsx diff --git a/packages/kbn-test/src/jest/utils/router_helpers.tsx b/packages/kbn-test-jest-helpers/src/router_helpers.tsx similarity index 100% rename from packages/kbn-test/src/jest/utils/router_helpers.tsx rename to packages/kbn-test-jest-helpers/src/router_helpers.tsx diff --git a/packages/kbn-test/src/jest/utils/stub_browser_storage.test.ts b/packages/kbn-test-jest-helpers/src/stub_browser_storage.test.ts similarity index 100% rename from packages/kbn-test/src/jest/utils/stub_browser_storage.test.ts rename to packages/kbn-test-jest-helpers/src/stub_browser_storage.test.ts diff --git a/packages/kbn-test/src/jest/utils/stub_browser_storage.ts b/packages/kbn-test-jest-helpers/src/stub_browser_storage.ts similarity index 100% rename from packages/kbn-test/src/jest/utils/stub_browser_storage.ts rename to packages/kbn-test-jest-helpers/src/stub_browser_storage.ts diff --git a/packages/kbn-test/src/jest/utils/stub_web_worker.ts b/packages/kbn-test-jest-helpers/src/stub_web_worker.ts similarity index 100% rename from packages/kbn-test/src/jest/utils/stub_web_worker.ts rename to packages/kbn-test-jest-helpers/src/stub_web_worker.ts diff --git a/packages/kbn-test/src/jest/utils/testbed/README.md b/packages/kbn-test-jest-helpers/src/testbed/README.md similarity index 100% rename from packages/kbn-test/src/jest/utils/testbed/README.md rename to packages/kbn-test-jest-helpers/src/testbed/README.md diff --git a/packages/kbn-test/src/jest/utils/testbed/index.ts b/packages/kbn-test-jest-helpers/src/testbed/index.ts similarity index 96% rename from packages/kbn-test/src/jest/utils/testbed/index.ts rename to packages/kbn-test-jest-helpers/src/testbed/index.ts index a283bfc53f4ef..f063f5003fd1c 100644 --- a/packages/kbn-test/src/jest/utils/testbed/index.ts +++ b/packages/kbn-test-jest-helpers/src/testbed/index.ts @@ -14,4 +14,5 @@ export type { SetupFunc, SyncSetupFunc, AsyncSetupFunc, + EuiTableMetaData, } from './types'; diff --git a/packages/kbn-test/src/jest/utils/testbed/mount_component.tsx b/packages/kbn-test-jest-helpers/src/testbed/mount_component.tsx similarity index 97% rename from packages/kbn-test/src/jest/utils/testbed/mount_component.tsx rename to packages/kbn-test-jest-helpers/src/testbed/mount_component.tsx index 2ac482abc0fb2..5c5fd3f2237d1 100644 --- a/packages/kbn-test/src/jest/utils/testbed/mount_component.tsx +++ b/packages/kbn-test-jest-helpers/src/testbed/mount_component.tsx @@ -8,6 +8,7 @@ import React, { ComponentType } from 'react'; import { Store } from 'redux'; +// eslint-disable-next-line import/no-extraneous-dependencies import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; diff --git a/packages/kbn-test/src/jest/utils/testbed/testbed.ts b/packages/kbn-test-jest-helpers/src/testbed/testbed.ts similarity index 98% rename from packages/kbn-test/src/jest/utils/testbed/testbed.ts rename to packages/kbn-test-jest-helpers/src/testbed/testbed.ts index b10f331da10d6..87efb9e61b345 100644 --- a/packages/kbn-test/src/jest/utils/testbed/testbed.ts +++ b/packages/kbn-test-jest-helpers/src/testbed/testbed.ts @@ -7,6 +7,7 @@ */ import { Component as ReactComponent } from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies import { ComponentType, HTMLAttributes, ReactWrapper } from 'enzyme'; import { findTestSubject } from '../find_test_subject'; @@ -41,7 +42,7 @@ const defaultConfig: TestBedConfig = { * * @example ```typescript - import { registerTestBed } from '@kbn/test/jest'; + import { registerTestBed } from '@kbn/test-jest-helpers'; import { RemoteClusterList } from '../../app/sections/remote_cluster_list'; import { remoteClustersStore } from '../../app/store'; diff --git a/packages/kbn-test/src/jest/utils/testbed/types.ts b/packages/kbn-test-jest-helpers/src/testbed/types.ts similarity index 99% rename from packages/kbn-test/src/jest/utils/testbed/types.ts rename to packages/kbn-test-jest-helpers/src/testbed/types.ts index 11f8c802a9751..ff548f3af5f58 100644 --- a/packages/kbn-test/src/jest/utils/testbed/types.ts +++ b/packages/kbn-test-jest-helpers/src/testbed/types.ts @@ -7,6 +7,7 @@ */ import { Store } from 'redux'; +// eslint-disable-next-line import/no-extraneous-dependencies import { ReactWrapper as GenericReactWrapper } from 'enzyme'; import { LocationDescriptor } from 'history'; diff --git a/packages/kbn-test-jest-helpers/tsconfig.json b/packages/kbn-test-jest-helpers/tsconfig.json new file mode 100644 index 0000000000000..7a1121c9e91f1 --- /dev/null +++ b/packages/kbn-test-jest-helpers/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "./target_types", + "stripInternal": true, + "rootDir": "src", + "sourceMap": true, + "sourceRoot": "../../../../../../packages/kbn-test-jest-helpers/src", + "types": ["jest", "node"] + }, + "include": ["src/**/*"], + "exclude": ["**/__fixtures__/**/*"] +} diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index 69addd9e3c4c7..33d299058168d 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -1,10 +1,11 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@npm//@bazel/typescript:index.bzl", "ts_config") load("@npm//@babel/cli:index.bzl", "babel") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-test" PKG_REQUIRE_NAME = "@kbn/test" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__test" SOURCE_FILES = glob( [ @@ -27,7 +28,6 @@ filegroup( ) NPM_MODULE_EXTRA_FILES = [ - "jest/package.json", "jest-preset.js", "jest_integration/jest-preset.js", "jest.config.js", @@ -41,8 +41,10 @@ RUNTIME_DEPS = [ "//packages/kbn-std", "//packages/kbn-utils", "@npm//@elastic/elasticsearch", - "@npm//axios", "@npm//@babel/traverse", + "@npm//@jest/console", + "@npm//@jest/reporters", + "@npm//axios", "@npm//chance", "@npm//dedent", "@npm//del", @@ -58,7 +60,6 @@ RUNTIME_DEPS = [ "@npm//jest-cli", "@npm//jest-snapshot", "@npm//jest-styled-components", - "@npm//@jest/reporters", "@npm//joi", "@npm//mustache", "@npm//normalize-path", @@ -79,8 +80,10 @@ TYPES_DEPS = [ "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-i18n-react:npm_module_types", "//packages/kbn-std:npm_module_types", - "//packages/kbn-utils", + "//packages/kbn-utils:npm_module_types", "@npm//@elastic/elasticsearch", + "@npm//@jest/console", + "@npm//@jest/reporters", "@npm//axios", "@npm//elastic-apm-node", "@npm//del", @@ -145,7 +148,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -164,3 +167,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index e2607100babc5..3c9b222e98df5 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -55,6 +55,12 @@ module.exports = { rootDirectory: '.', }, ], + [ + '@kbn/test/target_node/jest/ci_stats_jest_reporter', + { + testGroupType: 'Jest Unit Tests', + }, + ], ], // The paths to modules that run some code to configure or set up the testing environment before each test diff --git a/packages/kbn-test/jest/package.json b/packages/kbn-test/jest/package.json deleted file mode 100644 index aa0ba83873684..0000000000000 --- a/packages/kbn-test/jest/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "main": "../target_node/jest", - "types": "../target_types/jest/index.d.ts" -} diff --git a/packages/kbn-test/jest_integration/jest-preset.js b/packages/kbn-test/jest_integration/jest-preset.js index 7504dec9e7a20..be007262477d3 100644 --- a/packages/kbn-test/jest_integration/jest-preset.js +++ b/packages/kbn-test/jest_integration/jest-preset.js @@ -21,6 +21,12 @@ module.exports = { reporters: [ 'default', ['@kbn/test/target_node/jest/junit_reporter', { reportName: 'Jest Integration Tests' }], + [ + '@kbn/test/target_node/jest/ci_stats_jest_reporter', + { + testGroupType: 'Jest Integration Tests', + }, + ], ], coverageReporters: !!process.env.CI ? [['json', { file: 'jest-integration.json' }]] diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index a94b3198e24a4..5f5e475e392c4 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -5,7 +5,6 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target_node", - "types": "./target_types", "kibana": { "devOnly": true } diff --git a/packages/kbn-test/src/es/es_client_for_testing.ts b/packages/kbn-test/src/es/es_client_for_testing.ts new file mode 100644 index 0000000000000..084cb8d77eac5 --- /dev/null +++ b/packages/kbn-test/src/es/es_client_for_testing.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Url from 'url'; +import * as Fs from 'fs'; + +import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { Client as EsClient, ClientOptions, HttpConnection } from '@elastic/elasticsearch'; +import type { Config } from '../functional_test_runner'; + +/** options for creating es instances used in functional testing scenarios */ +export interface EsClientForTestingOptions extends Omit { + /** url of es instance */ + esUrl: string; + /** overwrite the auth embedded in the url to use a different user in this client instance */ + authOverride?: { username: string; password: string }; + /** + * are we running tests against cloud? this is automatically determined + * by checking for the TEST_CLOUD environment variable but can be overriden + * for special cases + */ + isCloud?: boolean; +} + +export function createEsClientForFtrConfig( + config: Config, + overrides?: Omit +) { + const esUrl = Url.format(config.get('servers.elasticsearch')); + return createEsClientForTesting({ + esUrl, + requestTimeout: config.get('timeouts.esRequestTimeout'), + ...overrides, + }); +} + +export function createEsClientForTesting(options: EsClientForTestingOptions) { + const { esUrl, authOverride, isCloud = !!process.env.TEST_CLOUD, ...otherOptions } = options; + + const url = options.authOverride + ? Url.format({ + ...Url.parse(options.esUrl), + auth: `${options.authOverride.username}:${options.authOverride.password}`, + }) + : options.esUrl; + + return new EsClient({ + Connection: HttpConnection, + tls: isCloud ? undefined : { ca: Fs.readFileSync(CA_CERT_PATH) }, + + ...otherOptions, + + // force nodes config + nodes: [url], + }); +} diff --git a/packages/kbn-test/src/es/index.ts b/packages/kbn-test/src/es/index.ts index 0c19a6b903742..c823adaab101f 100644 --- a/packages/kbn-test/src/es/index.ts +++ b/packages/kbn-test/src/es/index.ts @@ -10,3 +10,5 @@ export { createTestEsCluster } from './test_es_cluster'; export type { CreateTestEsClusterOptions, EsTestCluster, ICluster } from './test_es_cluster'; export { esTestConfig } from './es_test_config'; export { convertToKibanaClient } from './client_to_kibana_client'; +export { createEsClientForTesting, createEsClientForFtrConfig } from './es_client_for_testing'; +export type { EsClientForTestingOptions } from './es_client_for_testing'; diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index ea55a2672d670..1701a0a2e576c 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -6,14 +6,13 @@ * Side Public License, v 1. */ -import type { Client as EsClient } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { Suite, Test } from './fake_mocha_types'; import { Lifecycle, LifecyclePhase, - FailureMetadata, + TestMetadata, readConfigFile, ProviderCollection, readProviderSpec, @@ -24,10 +23,11 @@ import { SuiteTracker, EsVersion, } from './lib'; +import { createEsClientForFtrConfig } from '../es'; export class FunctionalTestRunner { public readonly lifecycle = new Lifecycle(); - public readonly failureMetadata = new FailureMetadata(this.lifecycle); + public readonly testMetadata = new TestMetadata(this.lifecycle); private closed = false; private readonly esVersion: EsVersion; @@ -61,27 +61,9 @@ export class FunctionalTestRunner { ...readProviderSpec('PageObject', config.get('pageObjects')), ]); - // validate es version if (providers.hasService('es')) { - const es = (await providers.getService('es')) as unknown as EsClient; - let esInfo; - try { - esInfo = await es.info(); - } catch (error) { - throw new Error( - `attempted to use the "es" service to fetch Elasticsearch version info but the request failed: ${error.stack}` - ); - } - - if (!this.esVersion.eql(esInfo.version.number)) { - throw new Error( - `ES reports a version number "${ - esInfo.version.number - }" which doesn't match supplied es version "${this.esVersion.toString()}"` - ); - } + await this.validateEsVersion(config); } - await providers.loadAll(); const customTestRunner = config.get('testRunner'); @@ -100,6 +82,33 @@ export class FunctionalTestRunner { }); } + private async validateEsVersion(config: Config) { + const es = createEsClientForFtrConfig(config); + + let esInfo; + try { + esInfo = await es.info(); + } catch (error) { + throw new Error( + `attempted to use the "es" service to fetch Elasticsearch version info but the request failed: ${error.stack}` + ); + } finally { + try { + await es.close(); + } catch { + // noop + } + } + + if (!this.esVersion.eql(esInfo.version.number)) { + throw new Error( + `ES reports a version number "${ + esInfo.version.number + }" which doesn't match supplied es version "${this.esVersion.toString()}"` + ); + } + } + async getTestStats() { return await this._run(async (config, coreProviders) => { if (config.get('testRunner')) { @@ -181,7 +190,7 @@ export class FunctionalTestRunner { const coreProviders = readProviderSpec('Service', { lifecycle: () => this.lifecycle, log: () => this.log, - failureMetadata: () => this.failureMetadata, + testMetadata: () => this.testMetadata, config: () => config, dockerServers: () => dockerServers, esVersion: () => this.esVersion, diff --git a/packages/kbn-test/src/functional_test_runner/index.ts b/packages/kbn-test/src/functional_test_runner/index.ts index 1718b5f7a4bc5..e67e72fd5801a 100644 --- a/packages/kbn-test/src/functional_test_runner/index.ts +++ b/packages/kbn-test/src/functional_test_runner/index.ts @@ -7,7 +7,8 @@ */ export { FunctionalTestRunner } from './functional_test_runner'; -export { readConfigFile, Config, EsVersion } from './lib'; +export { readConfigFile, Config, EsVersion, Lifecycle, LifecyclePhase } from './lib'; +export type { ScreenshotRecord } from './lib'; export { runFtrCli } from './cli'; export * from './lib/docker_servers'; export * from './public_types'; diff --git a/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js b/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js index 1375e5a3df2fd..6e25e4c073ab0 100644 --- a/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js +++ b/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/failure_hooks/config.js @@ -35,6 +35,7 @@ export default function () { }, mochaReporter: { captureLogOutput: false, + sendToCiStats: false, }, }; } diff --git a/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/simple_project/config.js b/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/simple_project/config.js index 7163058b78523..4c87b53b5753b 100644 --- a/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/simple_project/config.js +++ b/packages/kbn-test/src/functional_test_runner/integration_tests/__fixtures__/simple_project/config.js @@ -10,4 +10,7 @@ import { resolve } from 'path'; export default () => ({ testFiles: [resolve(__dirname, 'tests.js')], + mochaReporter: { + sendToCiStats: false, + }, }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts index 1d4af9c33fb79..d6248b9628e73 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/config.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/config.ts @@ -20,6 +20,7 @@ interface Options { } export class Config { + public readonly path: string; private [$values]: Record; constructor(options: Options) { @@ -29,6 +30,7 @@ export class Config { throw new TypeError('path is a required option'); } + this.path = path; const { error, value } = schema.validate(settings, { abortEarly: false, context: { diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index a9ceaa643a60f..e51ebc4538343 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -152,6 +152,7 @@ export const schema = Joi.object() mochaReporter: Joi.object() .keys({ captureLogOutput: Joi.boolean().default(!!process.env.CI), + sendToCiStats: Joi.boolean().default(!!process.env.CI), }) .default(), diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts deleted file mode 100644 index b40f6a5c83688..0000000000000 --- a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts +++ /dev/null @@ -1,61 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Lifecycle } from './lifecycle'; -import { FailureMetadata } from './failure_metadata'; -import { Test } from '../fake_mocha_types'; - -it('collects metadata for the current test', async () => { - const lifecycle = new Lifecycle(); - const failureMetadata = new FailureMetadata(lifecycle); - - const test1 = {} as Test; - await lifecycle.beforeEachRunnable.trigger(test1); - failureMetadata.add({ foo: 'bar' }); - - expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` - Object { - "foo": "bar", - } - `); - - const test2 = {} as Test; - await lifecycle.beforeEachRunnable.trigger(test2); - failureMetadata.add({ test: 2 }); - - expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` - Object { - "foo": "bar", - } - `); - expect(failureMetadata.get(test2)).toMatchInlineSnapshot(` - Object { - "test": 2, - } - `); -}); - -it('adds messages to the messages state', () => { - const lifecycle = new Lifecycle(); - const failureMetadata = new FailureMetadata(lifecycle); - - const test1 = {} as Test; - lifecycle.beforeEachRunnable.trigger(test1); - failureMetadata.addMessages(['foo', 'bar']); - failureMetadata.addMessages(['baz']); - - expect(failureMetadata.get(test1)).toMatchInlineSnapshot(` - Object { - "messages": Array [ - "foo", - "bar", - "baz", - ], - } - `); -}); diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts deleted file mode 100644 index a766c73f4c727..0000000000000 --- a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.ts +++ /dev/null @@ -1,93 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Path from 'path'; - -import { REPO_ROOT } from '@kbn/utils'; - -import { Lifecycle } from './lifecycle'; - -interface Metadata { - [key: string]: unknown; -} - -export class FailureMetadata { - // mocha's global types mean we can't import Mocha or it will override the global jest types.............. - private currentRunnable?: any; - private readonly allMetadata = new Map(); - - constructor(lifecycle: Lifecycle) { - if (!process.env.GCS_UPLOAD_PREFIX && process.env.CI) { - throw new Error( - 'GCS_UPLOAD_PREFIX environment variable is not set and must always be set on CI' - ); - } - - lifecycle.beforeEachRunnable.add((runnable) => { - this.currentRunnable = runnable; - }); - } - - add(metadata: Metadata | ((current: Metadata) => Metadata)) { - if (!this.currentRunnable) { - throw new Error('no current runnable to associate metadata with'); - } - - const current = this.allMetadata.get(this.currentRunnable); - this.allMetadata.set(this.currentRunnable, { - ...current, - ...(typeof metadata === 'function' ? metadata(current || {}) : metadata), - }); - } - - addMessages(messages: string[]) { - this.add((current) => ({ - messages: [...(Array.isArray(current.messages) ? current.messages : []), ...messages], - })); - } - - /** - * @param name Name to label the URL with - * @param repoPath absolute path, within the repo, that will be uploaded - */ - addScreenshot(name: string, repoPath: string) { - const prefix = process.env.GCS_UPLOAD_PREFIX; - - if (!prefix) { - return; - } - - const slash = prefix.endsWith('/') ? '' : '/'; - const urlPath = Path.relative(REPO_ROOT, repoPath) - .split(Path.sep) - .map((c) => encodeURIComponent(c)) - .join('/'); - - if (urlPath.startsWith('..')) { - throw new Error( - `Only call addUploadLink() with paths that are within the repo root, received ${repoPath} and repo root is ${REPO_ROOT}` - ); - } - - const url = `https://storage.googleapis.com/${prefix}${slash}${urlPath}`; - const screenshot = { - name, - url, - }; - - this.add((current) => ({ - screenshots: [...(Array.isArray(current.screenshots) ? current.screenshots : []), screenshot], - })); - - return screenshot; - } - - get(runnable: any) { - return this.allMetadata.get(runnable); - } -} diff --git a/packages/kbn-test/src/functional_test_runner/lib/index.ts b/packages/kbn-test/src/functional_test_runner/lib/index.ts index 98b5fec0597e4..e387fd156fe8a 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/index.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/index.ts @@ -12,7 +12,7 @@ export { readConfigFile, Config } from './config'; export { readProviderSpec, ProviderCollection } from './providers'; // @internal export { runTests, setupMocha } from './mocha'; -export { FailureMetadata } from './failure_metadata'; +export * from './test_metadata'; export * from './docker_servers'; export { SuiteTracker } from './suite_tracker'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 17dcaa8d7447d..e683ec23a8d84 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -11,15 +11,23 @@ import { LifecyclePhase } from './lifecycle_phase'; import { Suite, Test } from '../fake_mocha_types'; export class Lifecycle { + /** lifecycle phase that will run handlers once before tests execute */ public readonly beforeTests = new LifecyclePhase<[Suite]>({ singular: true, }); + /** lifecycle phase that runs handlers before each runnable (test and hooks) */ public readonly beforeEachRunnable = new LifecyclePhase<[Test]>(); + /** lifecycle phase that runs handlers before each suite */ public readonly beforeTestSuite = new LifecyclePhase<[Suite]>(); + /** lifecycle phase that runs handlers before each test */ public readonly beforeEachTest = new LifecyclePhase<[Test]>(); + /** lifecycle phase that runs handlers after each suite */ public readonly afterTestSuite = new LifecyclePhase<[Suite]>(); + /** lifecycle phase that runs handlers after a test fails */ public readonly testFailure = new LifecyclePhase<[Error, Test]>(); + /** lifecycle phase that runs handlers after a hook fails */ public readonly testHookFailure = new LifecyclePhase<[Error, Test]>(); + /** lifecycle phase that runs handlers at the very end of execution */ public readonly cleanup = new LifecyclePhase<[]>({ singular: true, }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts new file mode 100644 index 0000000000000..61eb7eccce430 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/ci_stats_ftr_reporter.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Path from 'path'; + +import { REPO_ROOT } from '@kbn/utils'; +import { CiStatsReporter, CiStatsReportTestsOptions, CiStatsTestType } from '@kbn/dev-utils'; + +import { Config } from '../../config'; +import { Runner } from '../../../fake_mocha_types'; +import { TestMetadata, ScreenshotRecord } from '../../test_metadata'; +import { Lifecycle } from '../../lifecycle'; +import { getSnapshotOfRunnableLogs } from '../../../../mocha'; + +interface Suite { + _beforeAll: Runnable[]; + _beforeEach: Runnable[]; + _afterEach: Runnable[]; + _afterAll: Runnable[]; +} + +interface Runnable { + isFailed(): boolean; + isPending(): boolean; + duration?: number; + titlePath(): string[]; + file: string; + title: string; + parent: Suite; + _screenshots?: ScreenshotRecord[]; +} + +function getHookType(hook: Runnable): CiStatsTestType { + if (hook.parent._afterAll.includes(hook)) { + return 'after all hook'; + } + if (hook.parent._afterEach.includes(hook)) { + return 'after each hook'; + } + if (hook.parent._beforeEach.includes(hook)) { + return 'before each hook'; + } + if (hook.parent._beforeAll.includes(hook)) { + return 'before all hook'; + } + + throw new Error(`unable to determine hook type, hook is not owned by it's parent`); +} + +export function setupCiStatsFtrTestGroupReporter({ + config, + lifecycle, + runner, + testMetadata, + reporter, +}: { + config: Config; + lifecycle: Lifecycle; + runner: Runner; + testMetadata: TestMetadata; + reporter: CiStatsReporter; +}) { + let startMs: number | undefined; + runner.on('start', () => { + startMs = Date.now(); + }); + + const start = Date.now(); + const group: CiStatsReportTestsOptions['group'] = { + startTime: new Date(start).toJSON(), + durationMs: 0, + type: config.path.startsWith('x-pack') ? 'X-Pack Functional Tests' : 'Functional Tests', + name: Path.relative(REPO_ROOT, config.path), + meta: { + ciGroup: config.get('suiteTags.include').find((t: string) => t.startsWith('ciGroup')), + tags: [ + ...config.get('suiteTags.include'), + ...config.get('suiteTags.exclude').map((t: string) => `-${t}`), + ].filter((t) => !t.startsWith('ciGroup')), + }, + }; + + const testRuns: CiStatsReportTestsOptions['testRuns'] = []; + function trackRunnable( + runnable: Runnable, + { error, type }: { error?: Error; type: CiStatsTestType } + ) { + testRuns.push({ + startTime: new Date(Date.now() - (runnable.duration ?? 0)).toJSON(), + durationMs: runnable.duration ?? 0, + seq: testRuns.length + 1, + file: Path.relative(REPO_ROOT, runnable.file), + name: runnable.title, + suites: runnable.titlePath().slice(0, -1), + result: runnable.isFailed() ? 'fail' : runnable.isPending() ? 'skip' : 'pass', + type, + error: error?.stack, + stdout: getSnapshotOfRunnableLogs(runnable), + screenshots: testMetadata.getScreenshots(runnable).map((s) => ({ + base64Png: s.base64Png, + name: s.name, + })), + }); + } + + const errors = new Map(); + runner.on('fail', (test: Runnable, error: Error) => { + errors.set(test, error); + }); + + runner.on('hook end', (hook: Runnable) => { + if (hook.isFailed()) { + const error = errors.get(hook); + if (!error) { + throw new Error(`no error recorded for failed hook`); + } + + trackRunnable(hook, { + type: getHookType(hook), + error, + }); + } + }); + + runner.on('test end', (test: Runnable) => { + const error = errors.get(test); + if (test.isFailed() && !error) { + throw new Error('no error recorded for failed test'); + } + + trackRunnable(test, { + type: 'test', + error, + }); + }); + + runner.on('end', () => { + if (!startMs) { + throw new Error('startMs was not defined'); + } + + // update the durationMs + group.durationMs = Date.now() - startMs; + }); + + lifecycle.cleanup.add(async () => { + await reporter.reportTests({ + group, + testRuns, + }); + }); +} diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js index d6045b71bf3a7..84299cba14eaa 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/reporter/reporter.js @@ -9,7 +9,7 @@ import { format } from 'util'; import Mocha from 'mocha'; -import { ToolingLogTextWriter } from '@kbn/dev-utils'; +import { ToolingLogTextWriter, CiStatsReporter } from '@kbn/dev-utils'; import moment from 'moment'; import { recordLog, snapshotLogsForRunnable, setupJUnitReportGeneration } from '../../../../mocha'; @@ -17,11 +17,13 @@ import * as colors from './colors'; import * as symbols from './symbols'; import { ms } from './ms'; import { writeEpilogue } from './write_epilogue'; +import { setupCiStatsFtrTestGroupReporter } from './ci_stats_ftr_reporter'; export function MochaReporterProvider({ getService }) { const log = getService('log'); const config = getService('config'); - const failureMetadata = getService('failureMetadata'); + const lifecycle = getService('lifecycle'); + const testMetadata = getService('testMetadata'); let originalLogWriters; let reporterCaptureStartTime; @@ -45,9 +47,23 @@ export function MochaReporterProvider({ getService }) { if (config.get('junit.enabled') && config.get('junit.reportName')) { setupJUnitReportGeneration(runner, { reportName: config.get('junit.reportName'), - getTestMetadata: (t) => failureMetadata.get(t), }); } + + if (config.get('mochaReporter.sendToCiStats')) { + const reporter = CiStatsReporter.fromEnv(log); + if (!reporter.hasBuildConfig()) { + log.warning('ci-stats reporter config is not available so test results will not be sent'); + } else { + setupCiStatsFtrTestGroupReporter({ + reporter, + config, + lifecycle, + runner, + testMetadata, + }); + } + } } onStart = () => { diff --git a/packages/kbn-test/src/functional_test_runner/lib/test_metadata.ts b/packages/kbn-test/src/functional_test_runner/lib/test_metadata.ts new file mode 100644 index 0000000000000..5789231f87044 --- /dev/null +++ b/packages/kbn-test/src/functional_test_runner/lib/test_metadata.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Lifecycle } from './lifecycle'; + +export interface ScreenshotRecord { + name: string; + base64Png: string; + baselinePath?: string; + failurePath?: string; +} + +export class TestMetadata { + // mocha's global types mean we can't import Mocha or it will override the global jest types.............. + private currentRunnable?: any; + + constructor(lifecycle: Lifecycle) { + lifecycle.beforeEachRunnable.add((runnable) => { + this.currentRunnable = runnable; + }); + } + + addScreenshot(screenshot: ScreenshotRecord) { + this.currentRunnable._screenshots = (this.currentRunnable._screenshots || []).concat( + screenshot + ); + } + + getScreenshots(test: any): ScreenshotRecord[] { + if (!test || typeof test !== 'object' || !test._screenshots) { + return []; + } + + return test._screenshots.slice(); + } +} diff --git a/packages/kbn-test/src/functional_test_runner/public_types.ts b/packages/kbn-test/src/functional_test_runner/public_types.ts index 6cb6d5adf4b19..426fdda74d313 100644 --- a/packages/kbn-test/src/functional_test_runner/public_types.ts +++ b/packages/kbn-test/src/functional_test_runner/public_types.ts @@ -8,10 +8,10 @@ import type { ToolingLog } from '@kbn/dev-utils'; -import type { Config, Lifecycle, FailureMetadata, DockerServersService, EsVersion } from './lib'; +import type { Config, Lifecycle, TestMetadata, DockerServersService, EsVersion } from './lib'; import type { Test, Suite } from './fake_mocha_types'; -export { Lifecycle, Config, FailureMetadata }; +export { Lifecycle, Config, TestMetadata }; export interface AsyncInstance { /** @@ -57,7 +57,7 @@ export interface GenericFtrProviderContext< * @param serviceName */ hasService( - serviceName: 'config' | 'log' | 'lifecycle' | 'failureMetadata' | 'dockerServers' | 'esVersion' + serviceName: 'config' | 'log' | 'lifecycle' | 'testMetadata' | 'dockerServers' | 'esVersion' ): true; hasService(serviceName: K): serviceName is K; hasService(serviceName: string): serviceName is Extract; @@ -71,7 +71,7 @@ export interface GenericFtrProviderContext< getService(serviceName: 'log'): ToolingLog; getService(serviceName: 'lifecycle'): Lifecycle; getService(serviceName: 'dockerServers'): DockerServersService; - getService(serviceName: 'failureMetadata'): FailureMetadata; + getService(serviceName: 'testMetadata'): TestMetadata; getService(serviceName: 'esVersion'): EsVersion; getService(serviceName: T): ServiceMap[T]; diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index a3772665b8891..5efd1c1f26852 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -25,8 +25,19 @@ export { runTests, startServers } from './functional_tests/tasks'; // @internal export { KIBANA_ROOT } from './functional_tests/lib/paths'; -export type { CreateTestEsClusterOptions, EsTestCluster, ICluster } from './es'; -export { esTestConfig, createTestEsCluster, convertToKibanaClient } from './es'; +export type { + CreateTestEsClusterOptions, + EsTestCluster, + ICluster, + EsClientForTestingOptions, +} from './es'; +export { + esTestConfig, + createTestEsCluster, + convertToKibanaClient, + createEsClientForTesting, + createEsClientForFtrConfig, +} from './es'; export { kbnTestConfig, @@ -49,7 +60,7 @@ export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; export * from './functional_test_runner'; -export { getUrl } from './jest/utils/get_url'; +export { getUrl } from './jest/get_url'; export { runCheckJestConfigsCli } from './jest/run_check_jest_configs_cli'; diff --git a/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts b/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts new file mode 100644 index 0000000000000..94675d87a3a24 --- /dev/null +++ b/packages/kbn-test/src/jest/ci_stats_jest_reporter.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as Path from 'path'; + +import getopts from 'getopts'; +import { CiStatsReporter, ToolingLog, CiStatsReportTestsOptions } from '@kbn/dev-utils'; +import type { Config } from '@jest/types'; +import { BaseReporter, Test, TestResult } from '@jest/reporters'; +import { ConsoleBuffer } from '@jest/console'; + +type LogEntry = ConsoleBuffer[0]; + +interface ReporterOptions { + testGroupType: string; +} + +function formatConsoleLine({ type, message, origin }: LogEntry) { + const originLines = origin.split('\n'); + + return `console.${type}: ${message}${originLines[0] ? `\n ${originLines[0]}` : ''}`; +} + +/** + * Jest reporter that reports tests to CI Stats + * @class JestJUnitReporter + */ + +// eslint-disable-next-line import/no-default-export +export default class CiStatsJestReporter extends BaseReporter { + private reporter: CiStatsReporter | undefined; + private readonly testGroupType: string; + private readonly reportName: string; + private readonly rootDir: string; + private startTime: number | undefined; + + private group: CiStatsReportTestsOptions['group'] | undefined; + private readonly testRuns: CiStatsReportTestsOptions['testRuns'] = []; + + constructor(config: Config.GlobalConfig, options: ReporterOptions) { + super(); + + this.rootDir = config.rootDir; + this.testGroupType = options?.testGroupType; + if (!this.testGroupType) { + throw new Error('missing testGroupType reporter option'); + } + + const configArg = getopts(process.argv).config; + if (typeof configArg !== 'string') { + throw new Error('expected to find a single --config arg'); + } + this.reportName = configArg; + } + + async onRunStart() { + const reporter = CiStatsReporter.fromEnv( + new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }) + ); + + if (!reporter.hasBuildConfig()) { + return; + } + + this.startTime = Date.now(); + this.reporter = reporter; + this.group = { + name: this.reportName, + type: this.testGroupType, + startTime: new Date(this.startTime).toJSON(), + meta: {}, + durationMs: 0, + }; + } + + async onTestFileResult(_: Test, testResult: TestResult) { + if (!this.reporter || !this.group) { + return; + } + + let elapsedTime = 0; + for (const t of testResult.testResults) { + const startTime = new Date(testResult.perfStats.start + elapsedTime).toJSON(); + elapsedTime += t.duration ?? 0; + this.testRuns.push({ + startTime, + durationMs: t.duration ?? 0, + seq: this.testRuns.length + 1, + file: Path.relative(this.rootDir, testResult.testFilePath), + name: t.title, + result: t.status === 'failed' ? 'fail' : t.status === 'passed' ? 'pass' : 'skip', + suites: t.ancestorTitles, + type: 'test', + error: t.failureMessages.join('\n\n'), + stdout: testResult.console?.map(formatConsoleLine).join('\n'), + }); + } + } + + async onRunComplete() { + if (!this.reporter || !this.group || !this.testRuns.length || !this.startTime) { + return; + } + + this.group.durationMs = Date.now() - this.startTime; + + await this.reporter.reportTests({ + group: this.group, + testRuns: this.testRuns, + }); + } +} diff --git a/packages/kbn-test/src/jest/utils/get_url.test.ts b/packages/kbn-test/src/jest/get_url.test.ts similarity index 100% rename from packages/kbn-test/src/jest/utils/get_url.test.ts rename to packages/kbn-test/src/jest/get_url.test.ts diff --git a/packages/kbn-test/src/jest/utils/get_url.ts b/packages/kbn-test/src/jest/get_url.ts similarity index 100% rename from packages/kbn-test/src/jest/utils/get_url.ts rename to packages/kbn-test/src/jest/get_url.ts diff --git a/packages/kbn-test/src/mocha/index.ts b/packages/kbn-test/src/mocha/index.ts index 4ada51c7ae013..1be65d60a9842 100644 --- a/packages/kbn-test/src/mocha/index.ts +++ b/packages/kbn-test/src/mocha/index.ts @@ -11,7 +11,7 @@ export { setupJUnitReportGeneration } from './junit_report_generation'; // @ts-ignore not typed yet // @internal -export { recordLog, snapshotLogsForRunnable } from './log_cache'; +export { recordLog, snapshotLogsForRunnable, getSnapshotOfRunnableLogs } from './log_cache'; // @ts-ignore not typed yet // @internal export { escapeCdata } from './xml'; diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index 9fc07530a0095..4434f6c1751e0 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { themeServiceMock } from '../../theme/theme_service.mock'; import { AppContainer } from './app_container'; diff --git a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx index abc2fb5cc3356..0102343ca6eb7 100644 --- a/src/core/public/chrome/ui/header/collapsible_nav.test.tsx +++ b/src/core/public/chrome/ui/header/collapsible_nav.test.tsx @@ -10,7 +10,7 @@ import { mount, ReactWrapper } from 'enzyme'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; import sinon from 'sinon'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { ChromeNavLink, DEFAULT_APP_CATEGORIES } from '../../..'; import { httpServiceMock } from '../../../http/http_service.mock'; import { ChromeRecentlyAccessedHistoryItem } from '../../recently_accessed'; diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 2692f2dbd194f..4790dec0f3515 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; -import { StubBrowserStorage, mountWithIntl } from '@kbn/test/jest'; +import { StubBrowserStorage, mountWithIntl } from '@kbn/test-jest-helpers'; import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock } from '../../../mocks'; import { Header } from './header'; diff --git a/src/core/public/core_app/status/components/status_badge.test.tsx b/src/core/public/core_app/status/components/status_badge.test.tsx index b0870e51d98d1..5c908cf5021cc 100644 --- a/src/core/public/core_app/status/components/status_badge.test.tsx +++ b/src/core/public/core_app/status/components/status_badge.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { StatusBadge, StatusWithoutMessage } from './status_badge'; const getStatus = (parts: Partial = {}): StatusWithoutMessage => ({ diff --git a/src/core/public/core_app/status/components/version_header.test.tsx b/src/core/public/core_app/status/components/version_header.test.tsx index d51927f83550b..172a720a51751 100644 --- a/src/core/public/core_app/status/components/version_header.test.tsx +++ b/src/core/public/core_app/status/components/version_header.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl, findTestSubject } from '@kbn/test/jest'; +import { mountWithIntl, findTestSubject } from '@kbn/test-jest-helpers'; import type { ServerVersion } from '../../../../types/status'; import { VersionHeader } from './version_header'; diff --git a/src/core/public/fatal_errors/fatal_errors_screen.test.tsx b/src/core/public/fatal_errors/fatal_errors_screen.test.tsx index 7c9abed79f4b7..b460e62b1151d 100644 --- a/src/core/public/fatal_errors/fatal_errors_screen.test.tsx +++ b/src/core/public/fatal_errors/fatal_errors_screen.test.tsx @@ -10,7 +10,7 @@ import { EuiCallOut } from '@elastic/eui'; import testSubjSelector from '@kbn/test-subj-selector'; import React from 'react'; import * as Rx from 'rxjs'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { FatalErrorsScreen } from './fatal_errors_screen'; diff --git a/src/core/public/notifications/toasts/error_toast.test.tsx b/src/core/public/notifications/toasts/error_toast.test.tsx index e0f917fb7f078..2af342c2107a7 100644 --- a/src/core/public/notifications/toasts/error_toast.test.tsx +++ b/src/core/public/notifications/toasts/error_toast.test.tsx @@ -8,7 +8,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ErrorToast } from './error_toast'; diff --git a/src/core/public/theme/core_theme_provider.test.tsx b/src/core/public/theme/core_theme_provider.test.tsx index a0f0faf13b9da..1ce26650dcccd 100644 --- a/src/core/public/theme/core_theme_provider.test.tsx +++ b/src/core/public/theme/core_theme_provider.test.tsx @@ -12,7 +12,7 @@ import type { ReactWrapper } from 'enzyme'; import { of, BehaviorSubject } from 'rxjs'; import { useEuiTheme } from '@elastic/eui'; import type { UseEuiTheme } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { CoreThemeProvider } from './core_theme_provider'; import type { CoreTheme } from './types'; diff --git a/src/core/server/core_usage_data/core_usage_data_service.mock.ts b/src/core/server/core_usage_data/core_usage_data_service.mock.ts index 331a3bbb9c028..fd8f219fb2d55 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.mock.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.mock.ts @@ -140,6 +140,7 @@ const createStartContractMock = () => { alias: 'test_index', primaryStoreSizeBytes: 1, storeSizeBytes: 1, + savedObjectsDocsCount: 1, }, ], legacyUrlAliases: { diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index 59f673bbe3dfa..e6e890b1a7dab 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -218,6 +218,11 @@ describe('CoreUsageDataService', () => { }, ], } as any); + elasticsearch.client.asInternalUser.count.mockResolvedValueOnce({ + body: { + count: '15', + }, + } as any); elasticsearch.client.asInternalUser.cat.indices.mockResolvedValueOnce({ body: [ { @@ -229,6 +234,11 @@ describe('CoreUsageDataService', () => { }, ], } as any); + elasticsearch.client.asInternalUser.count.mockResolvedValueOnce({ + body: { + count: '10', + }, + } as any); elasticsearch.client.asInternalUser.search.mockResolvedValueOnce({ body: { hits: { total: { value: 6 } }, @@ -384,6 +394,7 @@ describe('CoreUsageDataService', () => { "docsCount": 10, "docsDeleted": 10, "primaryStoreSizeBytes": 2000, + "savedObjectsDocsCount": "15", "storeSizeBytes": 1000, }, Object { @@ -391,6 +402,7 @@ describe('CoreUsageDataService', () => { "docsCount": 20, "docsDeleted": 20, "primaryStoreSizeBytes": 4000, + "savedObjectsDocsCount": "10", "storeSizeBytes": 2000, }, ], diff --git a/src/core/server/core_usage_data/core_usage_data_service.ts b/src/core/server/core_usage_data/core_usage_data_service.ts index 0665aed0e1950..824bfdffdceec 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.ts @@ -131,11 +131,11 @@ export class CoreUsageDataService return acc.add(index); }, new Set()) .values() - ).map((index) => { + ).map(async (index) => { // The _cat/indices API returns the _index_ and doesn't return a way // to map back from the index to the alias. So we have to make an API - // call for every alias - return elasticsearch.client.asInternalUser.cat + // call for every alias. The document count is the lucene document count. + const catIndicesResults = await elasticsearch.client.asInternalUser.cat .indices({ index, format: 'JSON', @@ -143,6 +143,7 @@ export class CoreUsageDataService }) .then(({ body }) => { const stats = body[0]; + return { alias: kibanaOrTaskManagerIndex(index, kibanaIndex), docsCount: stats['docs.count'] ? parseInt(stats['docs.count'], 10) : 0, @@ -153,6 +154,27 @@ export class CoreUsageDataService : 0, }; }); + // We use the GET /_count API to get the number of saved objects + // to monitor if the cluster will hit the scalling limit of saved object migrations + const savedObjectsCounts = await elasticsearch.client.asInternalUser + .count({ + index, + }) + .then(({ body }) => { + return { + savedObjectsDocsCount: body.count ? body.count : 0, + }; + }); + this.logger.debug( + `Lucene documents count ${catIndicesResults.docsCount} from index ${catIndicesResults.alias}` + ); + this.logger.debug( + `Saved objects documents count ${savedObjectsCounts.savedObjectsDocsCount} from index ${catIndicesResults.alias}` + ); + return { + ...catIndicesResults, + ...savedObjectsCounts, + }; }) ); } diff --git a/src/core/server/core_usage_data/types.ts b/src/core/server/core_usage_data/types.ts index 59e220fac4efe..0b09444da493d 100644 --- a/src/core/server/core_usage_data/types.ts +++ b/src/core/server/core_usage_data/types.ts @@ -177,6 +177,7 @@ export interface CoreServicesUsageData { docsDeleted: number; storeSizeBytes: number; primaryStoreSizeBytes: number; + savedObjectsDocsCount: number; }[]; legacyUrlAliases: { activeCount: number; diff --git a/src/core/server/preboot/preboot_service.test.ts b/src/core/server/preboot/preboot_service.test.ts index 77242f0c5765f..8a95f4bc9ab0e 100644 --- a/src/core/server/preboot/preboot_service.test.ts +++ b/src/core/server/preboot/preboot_service.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { REPO_ROOT } from '@kbn/utils'; import { LoggerFactory } from '@kbn/logging'; import { Env } from '@kbn/config'; diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts index f85576aa64451..acbbd0fe0d778 100644 --- a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts @@ -19,6 +19,10 @@ import { sortOrderSchema } from './common_schemas'; * - reverse_nested * - terms * + * Not fully supported: + * - filter + * - filters + * * Not implemented: * - adjacency_matrix * - auto_date_histogram @@ -27,7 +31,6 @@ import { sortOrderSchema } from './common_schemas'; * - date_histogram * - date_range * - diversified_sampler - * - filters * - geo_distance * - geohash_grid * - geotile_grid @@ -44,9 +47,26 @@ import { sortOrderSchema } from './common_schemas'; * - variable_width_histogram */ +// TODO: it would be great if we could recursively build the schema since the aggregation have be nested +// For more details see how the types are defined in the elasticsearch javascript client: +// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295 +const termSchema = s.object({ + term: s.recordOf(s.string(), s.oneOf([s.string(), s.boolean(), s.number()])), +}); + +// TODO: it would be great if we could recursively build the schema since the aggregation have be nested +// For more details see how the types are defined in the elasticsearch javascript client: +// https://github.com/elastic/elasticsearch-js/blob/4ad5daeaf401ce8ebb28b940075e0a67e56ff9ce/src/api/typesWithBodyKey.ts#L5295 +const boolSchema = s.object({ + bool: s.object({ + must_not: s.oneOf([termSchema]), + }), +}); + export const bucketAggsSchemas: Record = { - filter: s.object({ - term: s.recordOf(s.string(), s.oneOf([s.string(), s.boolean(), s.number()])), + filter: termSchema, + filters: s.object({ + filters: s.recordOf(s.string(), s.oneOf([termSchema, boolSchema])), }), histogram: s.object({ field: s.maybe(s.string()), diff --git a/src/core/server/saved_objects/validation/integration_tests/validator.test.ts b/src/core/server/saved_objects/validation/integration_tests/validator.test.ts index d47b42e36ce81..21e8973769710 100644 --- a/src/core/server/saved_objects/validation/integration_tests/validator.test.ts +++ b/src/core/server/saved_objects/validation/integration_tests/validator.test.ts @@ -191,7 +191,7 @@ describe('validates saved object types when a schema is provided', () => { { migrationVersion: { foo: '7.16.0' } } ); }).rejects.toThrowErrorMatchingInlineSnapshot( - `"Migration function for version 8.1.0 threw an error"` + `"Migration function for version 8.2.0 threw an error"` ); }); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 2f68b82ac5635..a722e6eb98b02 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -426,6 +426,7 @@ export interface CoreServicesUsageData { docsDeleted: number; storeSizeBytes: number; primaryStoreSizeBytes: number; + savedObjectsDocsCount: number; }[]; legacyUrlAliases: { activeCount: number; diff --git a/src/dev/i18n/extract_default_translations.js b/src/dev/i18n/extract_default_translations.js index a453b0bbae2fb..7d86105fed7fd 100644 --- a/src/dev/i18n/extract_default_translations.js +++ b/src/dev/i18n/extract_default_translations.js @@ -55,6 +55,7 @@ export async function matchEntriesWithExctractors(inputPath, options = {}) { '**/dist/**', '**/target/**', '**/vendor/**', + '**/build/**', '**/*.test.{js,jsx,ts,tsx}', '**/*.d.ts', ].concat(additionalIgnore); diff --git a/src/dev/i18n/tasks/extract_untracked_translations.ts b/src/dev/i18n/tasks/extract_untracked_translations.ts index 7afaa1ef71a06..1455a9a00f766 100644 --- a/src/dev/i18n/tasks/extract_untracked_translations.ts +++ b/src/dev/i18n/tasks/extract_untracked_translations.ts @@ -38,7 +38,6 @@ export async function extractUntrackedMessagesTask({ '**/packages/kbn-i18n/**', '**/packages/kbn-i18n-react/**', '**/packages/kbn-plugin-generator/template/**', - '**/target/**', '**/test/**', '**/scripts/**', '**/src/dev/**', diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index d6526781e7373..542acf7b0fa8f 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -24,7 +24,7 @@ export const storybookAliases = { expression_image: 'src/plugins/expression_image/.storybook', expression_metric_vis: 'src/plugins/chart_expressions/expression_metric/.storybook', expression_metric: 'src/plugins/expression_metric/.storybook', - expression_pie: 'src/plugins/chart_expressions/expression_pie/.storybook', + expression_partition_vis: 'src/plugins/chart_expressions/expression_partition_vis/.storybook', expression_repeat_image: 'src/plugins/expression_repeat_image/.storybook', expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook', expression_shape: 'src/plugins/expression_shape/.storybook', diff --git a/src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.test.tsx b/src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.test.tsx index dbe5db1ebe2eb..b65c5c5020533 100644 --- a/src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.test.tsx +++ b/src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { PageFooter } from './page_footer'; diff --git a/src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.test.tsx b/src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.test.tsx index 0ffb9ae64e29f..792721490256f 100644 --- a/src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.test.tsx +++ b/src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { PageSubtitle } from './page_subtitle'; diff --git a/src/plugins/advanced_settings/public/component_registry/page_title/page_title.test.tsx b/src/plugins/advanced_settings/public/component_registry/page_title/page_title.test.tsx index e6142fe916ca7..05d44f3aee84d 100644 --- a/src/plugins/advanced_settings/public/component_registry/page_title/page_title.test.tsx +++ b/src/plugins/advanced_settings/public/component_registry/page_title/page_title.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { PageTitle } from './page_title'; diff --git a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx index 91f4a5e6be471..a804b55938861 100644 --- a/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/advanced_settings.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Observable } from 'rxjs'; import { ReactWrapper } from 'enzyme'; -import { mountWithI18nProvider, shallowWithI18nProvider } from '@kbn/test/jest'; +import { mountWithI18nProvider, shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import dedent from 'dedent'; import { PublicUiSettingsParams, diff --git a/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap b/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap index b3863b5452d90..7da96ad98f1bf 100644 --- a/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap +++ b/src/plugins/advanced_settings/public/management_app/components/call_outs/__snapshots__/call_outs.test.tsx.snap @@ -15,7 +15,7 @@ exports[`CallOuts should render normally 1`] = ` >

diff --git a/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx b/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx index b4eea59249c63..dabf44e2ba948 100644 --- a/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/call_outs/call_outs.tsx @@ -29,7 +29,7 @@ export const CallOuts = () => { id="advancedSettings.callOutCautionDescription" defaultMessage="Be careful in here, these settings are for very advanced users only. Tweaks you make here can break large portions of Kibana. - Some of these settings may be undocumented, unsupported or experimental. + Some of these settings may be undocumented, unsupported or in technical preview. If a field has a default value, blanking the field will reset it to its default which may be unacceptable given other configuration directives. Deleting a custom setting will permanently remove it from Kibana's config." diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx index b77a687b50cd9..70d6a7a83d25f 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/field/field.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { I18nProvider } from '@kbn/i18n-react'; -import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { mount, ReactWrapper } from 'enzyme'; import { FieldSetting } from '../../types'; import { UiSettingsType } from '../../../../../../core/public'; diff --git a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx index a371831dcb007..7d598aa182695 100644 --- a/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/form/form.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { UiSettingsType } from '../../../../../../core/public'; import { themeServiceMock } from '../../../../../../core/public/mocks'; diff --git a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx index 18684fa45f3a7..cef5978945bc8 100644 --- a/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/search/search.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx index 935c4d66a4506..ffb0328a1c97c 100644 --- a/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx +++ b/src/plugins/chart_expressions/expression_gauge/public/components/gauge_component.test.tsx @@ -11,7 +11,7 @@ import { chartPluginMock } from '../../../../charts/public/mocks'; import { fieldFormatsServiceMock } from '../../../../field_formats/public/mocks'; import type { Datatable } from '../../../../expressions/public'; import { DatatableColumn, DatatableRow } from 'src/plugins/expressions/common'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { GaugeRenderProps, GaugeArguments, GaugeLabelMajorMode, ColorStop } from '../../common'; import GaugeComponent from './gauge_component'; import { Chart, Goal } from '@elastic/charts'; diff --git a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_renderers.ts index 1498c04ca1b79..fa8bc5ee2e19d 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_renderers.ts @@ -32,6 +32,7 @@ export type HeatmapRenderProps = HeatmapExpressionProps & { onSelectRange: (data: BrushEvent['data']) => void; paletteService: PaletteRegistry; uiState: PersistedState; + interactive: boolean; }; export interface ColorStop { diff --git a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx index 59113e5826d24..031d8de079a7f 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.test.tsx @@ -12,7 +12,7 @@ import { chartPluginMock } from '../../../../charts/public/mocks'; import { EmptyPlaceholder } from '../../../../charts/public'; import { fieldFormatsServiceMock } from '../../../../field_formats/public/mocks'; import type { Datatable } from '../../../../expressions/public'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { act } from 'react-dom/test-utils'; import { HeatmapRenderProps, HeatmapArguments } from '../../common'; @@ -99,6 +99,7 @@ describe('HeatmapComponent', function () { onSelectRange: jest.fn(), paletteService: palettesRegistry, formatFactory: formatService.deserialize, + interactive: true, }; }); @@ -167,6 +168,29 @@ describe('HeatmapComponent', function () { }); }); + it('computes the bands correctly if only value accessor is provided', async () => { + const newData: Datatable = { + type: 'datatable', + rows: [{ 'col-0-1': 571.806 }], + columns: [{ id: 'col-0-1', name: 'Count', meta: { type: 'number' } }], + }; + const newProps = { + ...wrapperProps, + data: newData, + args: { ...wrapperProps.args, lastRangeIsRightOpen: false }, + } as unknown as HeatmapRenderProps; + const component = mountWithIntl(); + await act(async () => { + expect(component.find(Heatmap).prop('colorScale')).toEqual({ + bands: [ + { color: 'rgb(0, 0, 0)', end: 0, start: 0 }, + { color: 'rgb(112, 38, 231)', end: Infinity, start: 571.806 }, + ], + type: 'bands', + }); + }); + }); + it('renders the axis titles', () => { const component = shallowWithIntl(); expect(component.find(Heatmap).prop('xAxisTitle')).toEqual('Dest'); @@ -266,4 +290,10 @@ describe('HeatmapComponent', function () { ]); expect(wrapperProps.onClickValue).toHaveBeenCalled(); }); + + it('does not add callbacks when not interactive', () => { + const component = shallowWithIntl(); + expect(component.find(Settings).first().prop('onElementClick')).toBeUndefined(); + expect(component.find(Settings).first().prop('onBrushEnd')).toBeUndefined(); + }); }); diff --git a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx index 969db9263a4e6..3c751956c0ea2 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/components/heatmap_component.tsx @@ -29,7 +29,10 @@ import type { DatatableColumn } from '../../../../expressions/public'; import { ExpressionValueVisDimension } from '../../../../visualizations/public'; import type { HeatmapRenderProps, FilterEvent, BrushEvent } from '../../common'; import { applyPaletteParams, findMinMaxByColumnId, getSortPredicate } from './helpers'; -import { getColorPicker } from '../utils/get_color_picker'; +import { + LegendColorPickerWrapperContext, + LegendColorPickerWrapper, +} from '../utils/get_color_picker'; import { DEFAULT_PALETTE_NAME, defaultPaletteParams } from '../constants'; import { HeatmapIcon } from './heatmap_icon'; import './index.scss'; @@ -135,6 +138,7 @@ export const HeatmapComponent: FC = memo( onSelectRange, paletteService, uiState, + interactive, }) => { const chartTheme = chartsThemeService.useChartsTheme(); const isDarkTheme = chartsThemeService.useDarkMode(); @@ -145,12 +149,15 @@ export const HeatmapComponent: FC = memo( }); const toggleLegend = useCallback(() => { + if (!interactive) { + return; + } setShowLegend((value) => { const newValue = !value; uiState?.set?.('vis.legendOpen', newValue); return newValue; }); - }, [uiState]); + }, [uiState, interactive]); const setColor = useCallback( (newColor: string | null, seriesLabel: string | number) => { @@ -168,10 +175,6 @@ export const HeatmapComponent: FC = memo( [uiState] ); - const legendColorPicker = useMemo( - () => getColorPicker(args.legend.position, setColor, uiState), - [args.legend.position, setColor, uiState] - ); const table = data; const valueAccessor = args.valueAccessor ? getAccessor(args.valueAccessor, table.columns) @@ -191,6 +194,117 @@ export const HeatmapComponent: FC = memo( const xAxisColumn = table.columns[xAxisColumnIndex]; const yAxisColumn = table.columns[yAxisColumnIndex]; const valueColumn = table.columns.find((v) => v.id === valueAccessor); + const xAxisMeta = xAxisColumn?.meta; + const isTimeBasedSwimLane = xAxisMeta?.type === 'date'; + + const onElementClick = useCallback( + (e: HeatmapElementEvent[]) => { + const cell = e[0][0]; + const { x, y } = cell.datum; + + const xAxisFieldName = xAxisColumn?.meta?.field; + const timeFieldName = isTimeBasedSwimLane ? xAxisFieldName : ''; + + const points = [ + { + row: table.rows.findIndex((r) => r[xAxisColumn.id] === x), + column: xAxisColumnIndex, + value: x, + }, + ...(yAxisColumn + ? [ + { + row: table.rows.findIndex((r) => r[yAxisColumn.id] === y), + column: yAxisColumnIndex, + value: y, + }, + ] + : []), + ]; + + const context: FilterEvent['data'] = { + data: points.map((point) => ({ + row: point.row, + column: point.column, + value: point.value, + table, + })), + timeFieldName, + }; + onClickValue(context); + }, + [ + isTimeBasedSwimLane, + onClickValue, + table, + xAxisColumn?.id, + xAxisColumn?.meta?.field, + xAxisColumnIndex, + yAxisColumn, + yAxisColumnIndex, + ] + ); + + const onBrushEnd = useCallback( + (e: HeatmapBrushEvent) => { + const { x, y } = e; + + const xAxisFieldName = xAxisColumn?.meta?.field; + const timeFieldName = isTimeBasedSwimLane ? xAxisFieldName : ''; + + if (isTimeBasedSwimLane) { + const context: BrushEvent['data'] = { + range: x as number[], + table, + column: xAxisColumnIndex, + timeFieldName, + }; + onSelectRange(context); + } else { + const points: Array<{ row: number; column: number; value: string | number }> = []; + + if (yAxisColumn) { + (y as string[]).forEach((v) => { + points.push({ + row: table.rows.findIndex((r) => r[yAxisColumn.id] === v), + column: yAxisColumnIndex, + value: v, + }); + }); + } + if (xAxisColumn) { + (x as string[]).forEach((v) => { + points.push({ + row: table.rows.findIndex((r) => r[xAxisColumn.id] === v), + column: xAxisColumnIndex, + value: v, + }); + }); + } + + const context: FilterEvent['data'] = { + data: points.map((point) => ({ + row: point.row, + column: point.column, + value: point.value, + table, + })), + timeFieldName, + }; + onClickValue(context); + } + }, + [ + isTimeBasedSwimLane, + onClickValue, + onSelectRange, + table, + xAxisColumn, + xAxisColumnIndex, + yAxisColumn, + yAxisColumnIndex, + ] + ); if (!valueColumn) { // Chart is not ready @@ -213,12 +327,10 @@ export const HeatmapComponent: FC = memo( } const { min, max } = minMaxByColumnId[valueAccessor!]; // formatters - const xAxisMeta = xAxisColumn?.meta; const xValuesFormatter = formatFactory(xAxisMeta?.params); const metricFormatter = formatFactory( typeof args.valueAccessor === 'string' ? valueColumn.meta.params : args?.valueAccessor?.format ); - const isTimeBasedSwimLane = xAxisMeta?.type === 'date'; const dateHistogramMeta = xAxisColumn ? search.aggs.getDateHistogramMetaDataByDatatableColumn(xAxisColumn) : undefined; @@ -271,122 +383,48 @@ export const HeatmapComponent: FC = memo( // adds a very small number to the max value to make sure the max value will be included const smattering = 0.00001; - let endValue = max + smattering; + let endValueDistinctBounds = max + smattering; if (paletteParams?.rangeMax || paletteParams?.rangeMax === 0) { - endValue = + endValueDistinctBounds = (paletteParams?.range === 'number' ? paletteParams.rangeMax : min + ((max - min) * paletteParams.rangeMax) / 100) + smattering; } const overwriteColors = uiState?.get('vis.colors') ?? null; - + const hasSingleValue = max === min; const bands = ranges.map((start, index, array) => { + const isPenultimate = index === array.length - 1; + const nextValue = array[index + 1]; // by default the last range is right-open - let end = index === array.length - 1 ? Infinity : array[index + 1]; + let endValue = isPenultimate ? Number.POSITIVE_INFINITY : nextValue; + const startValue = isPenultimate && hasSingleValue ? min : start; // if the lastRangeIsRightOpen is set to false, we need to set the last range to the max value if (args.lastRangeIsRightOpen === false) { - const lastBand = max === start ? Infinity : endValue; - end = index === array.length - 1 ? lastBand : array[index + 1]; + const lastBand = hasSingleValue ? Number.POSITIVE_INFINITY : endValueDistinctBounds; + endValue = isPenultimate ? lastBand : nextValue; } - const overwriteArrayIdx = `${metricFormatter.convert(start)} - ${metricFormatter.convert( - end - )}`; + + let overwriteArrayIdx; + + if (endValue === Number.POSITIVE_INFINITY) { + overwriteArrayIdx = `≥ ${metricFormatter.convert(startValue)}`; + } else { + overwriteArrayIdx = `${metricFormatter.convert(start)} - ${metricFormatter.convert( + endValue + )}`; + } + const overwriteColor = overwriteColors?.[overwriteArrayIdx]; return { // with the default continuity:above the every range is left-closed - start, - end, + start: startValue, + end: endValue, // the current colors array contains a duplicated color at the beginning that we need to skip color: overwriteColor ?? colors[index + 1], }; }); - const onElementClick = ((e: HeatmapElementEvent[]) => { - const cell = e[0][0]; - const { x, y } = cell.datum; - - const xAxisFieldName = xAxisColumn?.meta?.field; - const timeFieldName = isTimeBasedSwimLane ? xAxisFieldName : ''; - - const points = [ - { - row: table.rows.findIndex((r) => r[xAxisColumn.id] === x), - column: xAxisColumnIndex, - value: x, - }, - ...(yAxisColumn - ? [ - { - row: table.rows.findIndex((r) => r[yAxisColumn.id] === y), - column: yAxisColumnIndex, - value: y, - }, - ] - : []), - ]; - - const context: FilterEvent['data'] = { - data: points.map((point) => ({ - row: point.row, - column: point.column, - value: point.value, - table, - })), - timeFieldName, - }; - onClickValue(context); - }) as ElementClickListener; - - const onBrushEnd = (e: HeatmapBrushEvent) => { - const { x, y } = e; - - const xAxisFieldName = xAxisColumn?.meta?.field; - const timeFieldName = isTimeBasedSwimLane ? xAxisFieldName : ''; - - if (isTimeBasedSwimLane) { - const context: BrushEvent['data'] = { - range: x as number[], - table, - column: xAxisColumnIndex, - timeFieldName, - }; - onSelectRange(context); - } else { - const points: Array<{ row: number; column: number; value: string | number }> = []; - - if (yAxisColumn) { - (y as string[]).forEach((v) => { - points.push({ - row: table.rows.findIndex((r) => r[yAxisColumn.id] === v), - column: yAxisColumnIndex, - value: v, - }); - }); - } - if (xAxisColumn) { - (x as string[]).forEach((v) => { - points.push({ - row: table.rows.findIndex((r) => r[xAxisColumn.id] === v), - column: xAxisColumnIndex, - value: v, - }); - }); - } - - const context: FilterEvent['data'] = { - data: points.map((point) => ({ - row: point.row, - column: point.column, - value: point.value, - table, - })), - timeFieldName, - }; - onClickValue(context); - } - }; - const themeOverrides: PartialTheme = { legend: { labelOptions: { @@ -456,55 +494,63 @@ export const HeatmapComponent: FC = memo( legendPosition={args.legend.position} /> )} - - - `${xValuesFormatter.convert(v) ?? ''}`} - yAxisLabelFormatter={ - yAxisColumn - ? (v) => `${formatFactory(yAxisColumn.meta.params).convert(v) ?? ''}` - : undefined - } - /> - + + + + `${xValuesFormatter.convert(v) ?? ''}`} + yAxisLabelFormatter={ + yAxisColumn + ? (v) => `${formatFactory(yAxisColumn.meta.params).convert(v) ?? ''}` + : undefined + } + /> + + ); } diff --git a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx index 2acb5942098b1..0d3d9dcf93be7 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx @@ -46,6 +46,7 @@ export const heatmapRenderer: ( const timeZone = getTimeZone(getUISettings()); const { HeatmapComponent } = await import('../components/heatmap_component'); + const { isInteractive } = handlers; render(

@@ -58,6 +59,7 @@ export const heatmapRenderer: ( chartsThemeService={getThemeService()} paletteService={getPaletteService()} uiState={handlers.uiState as PersistedState} + interactive={isInteractive()} />
, diff --git a/src/plugins/chart_expressions/expression_heatmap/public/utils/get_color_picker.tsx b/src/plugins/chart_expressions/expression_heatmap/public/utils/get_color_picker.tsx index 2f5297c5fd475..771a633456e13 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/utils/get_color_picker.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/utils/get_color_picker.tsx @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import React, { useCallback } from 'react'; +import React, { createContext, useCallback, useContext } from 'react'; import { LegendColorPicker, Position } from '@elastic/charts'; -import { PopoverAnchorPosition, EuiPopover, EuiOutsideClickDetector } from '@elastic/eui'; +import { PopoverAnchorPosition, EuiWrappingPopover, EuiOutsideClickDetector } from '@elastic/eui'; import type { PersistedState } from '../../../../visualizations/public'; import { ColorPicker } from '../../../../charts/public'; @@ -27,59 +27,77 @@ function getAnchorPosition(legendPosition: Position): PopoverAnchorPosition { } } -export const getColorPicker = - ( - legendPosition: Position, - setColor: (newColor: string | null, seriesKey: string | number) => void, - uiState: PersistedState - ): LegendColorPicker => - ({ anchor, color, onClose, onChange, seriesIdentifiers: [seriesIdentifier] }) => { - const seriesName = seriesIdentifier.key; - const overwriteColors: Record = uiState?.get('vis.colors', {}) ?? {}; - const colorIsOverwritten = seriesName.toString() in overwriteColors; - let keyDownEventOn = false; - const handleChange = (newColor: string | null) => { - if (newColor) { - onChange(newColor); - } - setColor(newColor, seriesName); - // close the popover if no color is applied or the user has clicked a color - if (!newColor || !keyDownEventOn) { - onClose(); - } - }; +export interface LegendColorPickerWrapperContextType { + legendPosition: Position; + setColor: (newColor: string | null, seriesKey: string | number) => void; + uiState: PersistedState; +} - const onKeyDown = (e: React.KeyboardEvent) => { - if (e.keyCode === KEY_CODE_ENTER) { - onClose?.(); - } - keyDownEventOn = true; - }; +export const LegendColorPickerWrapperContext = createContext< + LegendColorPickerWrapperContextType | undefined +>(undefined); - const handleOutsideClick = useCallback(() => { - onClose?.(); - }, [onClose]); +export const LegendColorPickerWrapper: LegendColorPicker = ({ + anchor, + color, + onClose, + onChange, + seriesIdentifiers: [seriesIdentifier], +}) => { + const colorPickerWrappingContext = useContext(LegendColorPickerWrapperContext); + const handleOutsideClick = useCallback(() => { + onClose?.(); + }, [onClose]); + + if (!colorPickerWrappingContext) { + return null; + } + + const { legendPosition, setColor, uiState } = colorPickerWrappingContext; + const seriesName = seriesIdentifier.key; - return ( - - - - - - ); + const overwriteColors: Record = uiState?.get('vis.colors', {}) ?? {}; + const colorIsOverwritten = seriesName.toString() in overwriteColors; + let keyDownEventOn = false; + + const handleChange = (newColor: string | null) => { + if (newColor) { + onChange(newColor); + } + setColor(newColor, seriesName); + // close the popover if no color is applied or the user has clicked a color + if (!newColor || !keyDownEventOn) { + onClose(); + } }; + + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.keyCode === KEY_CODE_ENTER) { + onClose?.(); + } + keyDownEventOn = true; + }; + + return ( + + + + + + ); +}; diff --git a/src/plugins/chart_expressions/expression_pie/.storybook/main.js b/src/plugins/chart_expressions/expression_partition_vis/.storybook/main.js similarity index 100% rename from src/plugins/chart_expressions/expression_pie/.storybook/main.js rename to src/plugins/chart_expressions/expression_partition_vis/.storybook/main.js diff --git a/src/plugins/chart_expressions/expression_partition_vis/README.md b/src/plugins/chart_expressions/expression_partition_vis/README.md new file mode 100755 index 0000000000000..9a499280236e4 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/README.md @@ -0,0 +1,9 @@ +# expressionPartitionVis + +Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/constants.ts b/src/plugins/chart_expressions/expression_partition_vis/common/constants.ts new file mode 100644 index 0000000000000..ffa549f7b7679 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/constants.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const PLUGIN_ID = 'expressionPartitionVis'; +export const PLUGIN_NAME = 'expressionPartitionVis'; + +export const PIE_VIS_EXPRESSION_NAME = 'pieVis'; +export const TREEMAP_VIS_EXPRESSION_NAME = 'treemapVis'; +export const MOSAIC_VIS_EXPRESSION_NAME = 'mosaicVis'; +export const WAFFLE_VIS_EXPRESSION_NAME = 'waffleVis'; +export const PARTITION_VIS_RENDERER_NAME = 'partitionVis'; +export const PARTITION_LABELS_VALUE = 'partitionLabelsValue'; +export const PARTITION_LABELS_FUNCTION = 'partitionLabels'; + +export const DEFAULT_PERCENT_DECIMALS = 2; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap new file mode 100644 index 0000000000000..de064a44058cc --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/mosaic_vis_function.test.ts.snap @@ -0,0 +1,188 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`interpreter/functions#mosaicVis logs correct datatable to inspector 1`] = ` +Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + Object { + "id": "col-0-3", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 3", + }, + Object { + "id": "col-0-4", + "meta": Object { + "dimensionName": undefined, + "type": "number", + }, + "name": "Field 4", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", +} +`; + +exports[`interpreter/functions#mosaicVis returns an object with the correct structure 1`] = ` +Object { + "as": "partitionVis", + "type": "render", + "value": Object { + "params": Object { + "listenOnChange": true, + }, + "syncColors": false, + "visConfig": Object { + "addTooltip": true, + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "dimensions": Object { + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "splitColumn": undefined, + "splitRow": undefined, + }, + "labels": Object { + "last_level": false, + "percentDecimals": 2, + "position": "default", + "show": false, + "truncate": 100, + "type": "partitionLabelsValue", + "values": true, + "valuesFormat": "percent", + }, + "legendDisplay": "show", + "legendPosition": "right", + "maxLegendLines": 2, + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "nestedLegend": true, + "palette": Object { + "name": "kibana_palette", + "type": "system_palette", + }, + "splitColumn": undefined, + "splitRow": undefined, + "truncateLegend": true, + }, + "visData": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "type": "number", + }, + "name": "Field 2", + }, + Object { + "id": "col-0-3", + "meta": Object { + "type": "number", + }, + "name": "Field 3", + }, + Object { + "id": "col-0-4", + "meta": Object { + "type": "number", + }, + "name": "Field 4", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + "visType": "mosaic", + }, +} +`; + +exports[`interpreter/functions#mosaicVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported"`; + +exports[`interpreter/functions#mosaicVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap new file mode 100644 index 0000000000000..95b8df13882d9 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap @@ -0,0 +1,288 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`interpreter/functions#pieVis logs correct datatable to inspector 1`] = ` +Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Count", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", +} +`; + +exports[`interpreter/functions#pieVis returns an object with the correct structure for donut 1`] = ` +Object { + "as": "partitionVis", + "type": "render", + "value": Object { + "params": Object { + "listenOnChange": true, + }, + "syncColors": false, + "visConfig": Object { + "addTooltip": true, + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 3, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "dimensions": Object { + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 3, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "splitColumn": undefined, + "splitRow": undefined, + }, + "distinctColors": false, + "emptySizeRatio": 0.3, + "isDonut": true, + "labels": Object { + "last_level": false, + "percentDecimals": 2, + "position": "default", + "show": false, + "truncate": 100, + "type": "partitionLabelsValue", + "values": true, + "valuesFormat": "percent", + }, + "legendDisplay": "show", + "legendPosition": "right", + "maxLegendLines": 2, + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "nestedLegend": true, + "palette": Object { + "name": "kibana_palette", + "type": "system_palette", + }, + "respectSourceOrder": true, + "splitColumn": undefined, + "splitRow": undefined, + "startFromSecondLargestSlice": true, + "truncateLegend": true, + }, + "visData": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + "visType": "donut", + }, +} +`; + +exports[`interpreter/functions#pieVis returns an object with the correct structure for pie 1`] = ` +Object { + "as": "partitionVis", + "type": "render", + "value": Object { + "params": Object { + "listenOnChange": true, + }, + "syncColors": false, + "visConfig": Object { + "addTooltip": true, + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 3, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "dimensions": Object { + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 3, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "splitColumn": undefined, + "splitRow": undefined, + }, + "distinctColors": false, + "emptySizeRatio": 0.3, + "isDonut": false, + "labels": Object { + "last_level": false, + "percentDecimals": 2, + "position": "default", + "show": false, + "truncate": 100, + "type": "partitionLabelsValue", + "values": true, + "valuesFormat": "percent", + }, + "legendDisplay": "show", + "legendPosition": "right", + "maxLegendLines": 2, + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "nestedLegend": true, + "palette": Object { + "name": "kibana_palette", + "type": "system_palette", + }, + "respectSourceOrder": true, + "splitColumn": undefined, + "splitRow": undefined, + "startFromSecondLargestSlice": true, + "truncateLegend": true, + }, + "visData": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Count", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + }, + ], + "type": "datatable", + }, + "visType": "pie", + }, +} +`; + +exports[`interpreter/functions#pieVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap new file mode 100644 index 0000000000000..d18dca573606a --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/treemap_vis_function.test.ts.snap @@ -0,0 +1,188 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`interpreter/functions#treemapVis logs correct datatable to inspector 1`] = ` +Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + Object { + "id": "col-0-3", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 3", + }, + Object { + "id": "col-0-4", + "meta": Object { + "dimensionName": undefined, + "type": "number", + }, + "name": "Field 4", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", +} +`; + +exports[`interpreter/functions#treemapVis returns an object with the correct structure 1`] = ` +Object { + "as": "partitionVis", + "type": "render", + "value": Object { + "params": Object { + "listenOnChange": true, + }, + "syncColors": false, + "visConfig": Object { + "addTooltip": true, + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "dimensions": Object { + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + Object { + "accessor": 2, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "splitColumn": undefined, + "splitRow": undefined, + }, + "labels": Object { + "last_level": false, + "percentDecimals": 2, + "position": "default", + "show": false, + "truncate": 100, + "type": "partitionLabelsValue", + "values": true, + "valuesFormat": "percent", + }, + "legendDisplay": "show", + "legendPosition": "right", + "maxLegendLines": 2, + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "nestedLegend": true, + "palette": Object { + "name": "kibana_palette", + "type": "system_palette", + }, + "splitColumn": undefined, + "splitRow": undefined, + "truncateLegend": true, + }, + "visData": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "type": "number", + }, + "name": "Field 2", + }, + Object { + "id": "col-0-3", + "meta": Object { + "type": "number", + }, + "name": "Field 3", + }, + Object { + "id": "col-0-4", + "meta": Object { + "type": "number", + }, + "name": "Field 4", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + "visType": "treemap", + }, +} +`; + +exports[`interpreter/functions#treemapVis throws error if provided more than 2 buckets 1`] = `"More than 2 buckets are not supported"`; + +exports[`interpreter/functions#treemapVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap new file mode 100644 index 0000000000000..54ead941c7548 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/__snapshots__/waffle_vis_function.test.ts.snap @@ -0,0 +1,168 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`interpreter/functions#waffleVis logs correct datatable to inspector 1`] = ` +Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "dimensionName": "Slice size", + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "dimensionName": "Slice", + "type": "number", + }, + "name": "Field 2", + }, + Object { + "id": "col-0-3", + "meta": Object { + "dimensionName": undefined, + "type": "number", + }, + "name": "Field 3", + }, + Object { + "id": "col-0-4", + "meta": Object { + "dimensionName": undefined, + "type": "number", + }, + "name": "Field 4", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", +} +`; + +exports[`interpreter/functions#waffleVis returns an object with the correct structure 1`] = ` +Object { + "as": "partitionVis", + "type": "render", + "value": Object { + "params": Object { + "listenOnChange": true, + }, + "syncColors": false, + "visConfig": Object { + "addTooltip": true, + "bucket": Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "dimensions": Object { + "buckets": Array [ + Object { + "accessor": 1, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + ], + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "splitColumn": undefined, + "splitRow": undefined, + }, + "labels": Object { + "last_level": false, + "percentDecimals": 2, + "position": "default", + "show": false, + "truncate": 100, + "type": "partitionLabelsValue", + "values": true, + "valuesFormat": "percent", + }, + "legendDisplay": "show", + "legendPosition": "right", + "maxLegendLines": 2, + "metric": Object { + "accessor": 0, + "format": Object { + "id": "number", + "params": Object {}, + }, + "type": "vis_dimension", + }, + "palette": Object { + "name": "kibana_palette", + "type": "system_palette", + }, + "showValuesInLegend": true, + "splitColumn": undefined, + "splitRow": undefined, + "truncateLegend": true, + }, + "visData": Object { + "columns": Array [ + Object { + "id": "col-0-1", + "meta": Object { + "type": "number", + }, + "name": "Field 1", + }, + Object { + "id": "col-0-2", + "meta": Object { + "type": "number", + }, + "name": "Field 2", + }, + Object { + "id": "col-0-3", + "meta": Object { + "type": "number", + }, + "name": "Field 3", + }, + Object { + "id": "col-0-4", + "meta": Object { + "type": "number", + }, + "name": "Field 4", + }, + ], + "rows": Array [ + Object { + "col-0-1": 0, + "col-0-2": 0, + "col-0-3": 0, + "col-0-4": 0, + }, + ], + "type": "datatable", + }, + "visType": "waffle", + }, +} +`; + +exports[`interpreter/functions#waffleVis throws error if provided split row and split column at once 1`] = `"A split row and column are specified. Expression is supporting only one of them at once."`; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts new file mode 100644 index 0000000000000..0be470121ecb4 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/i18n.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +export const strings = { + getPieVisFunctionName: () => + i18n.translate('expressionPartitionVis.pieVis.function.help', { + defaultMessage: 'Pie visualization', + }), + getMetricArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.metricHelpText', { + defaultMessage: 'Metric dimensions config', + }), + getBucketsArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.bucketsHelpText', { + defaultMessage: 'Buckets dimensions config', + }), + getBucketArgHelp: () => + i18n.translate('expressionPartitionVis.waffle.function.args.bucketHelpText', { + defaultMessage: 'Bucket dimensions config', + }), + getSplitColumnArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.splitColumnHelpText', { + defaultMessage: 'Split by column dimension config', + }), + getSplitRowArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.splitRowHelpText', { + defaultMessage: 'Split by row dimension config', + }), + getAddTooltipArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.addTooltipHelpText', { + defaultMessage: 'Show tooltip on slice hover', + }), + getLegendDisplayArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.legendDisplayHelpText', { + defaultMessage: 'Show legend chart legend', + }), + getLegendPositionArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.legendPositionHelpText', { + defaultMessage: 'Position the legend on top, bottom, left, right of the chart', + }), + getNestedLegendArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.nestedLegendHelpText', { + defaultMessage: 'Show a more detailed legend', + }), + getTruncateLegendArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.truncateLegendHelpText', { + defaultMessage: 'Defines if the legend items will be truncated or not', + }), + getMaxLegendLinesArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.maxLegendLinesHelpText', { + defaultMessage: 'Defines the number of lines per legend item', + }), + getDistinctColorsArgHelp: () => + i18n.translate('expressionPartitionVis.pieVis.function.args.distinctColorsHelpText', { + defaultMessage: + 'Maps different color per slice. Slices with the same value have the same color', + }), + getIsDonutArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.isDonutHelpText', { + defaultMessage: 'Displays the pie chart as donut', + }), + getRespectSourceOrderArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.respectSourceOrderHelpText', { + defaultMessage: 'Keeps an order of the elements, returned from the datasource', + }), + getStartFromSecondLargestSliceArgHelp: () => + i18n.translate( + 'expressionPartitionVis.reusable.function.args.startPlacementWithSecondLargestSliceHelpText', + { + defaultMessage: 'Starts placement with the second largest slice', + } + ), + getEmptySizeRatioArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.emptySizeRatioHelpText', { + defaultMessage: 'Defines donut inner empty area size', + }), + getPaletteArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.paletteHelpText', { + defaultMessage: 'Defines the chart palette name', + }), + getLabelsArgHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.args.labelsHelpText', { + defaultMessage: 'Pie labels config', + }), + getShowValuesInLegendArgHelp: () => + i18n.translate('expressionPartitionVis.waffle.function.args.showValuesInLegendHelpText', { + defaultMessage: 'Show values in legend', + }), + + getSliceSizeHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.dimension.metric', { + defaultMessage: 'Slice size', + }), + getSliceHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.dimension.buckets', { + defaultMessage: 'Slice', + }), + getColumnSplitHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.dimension.splitcolumn', { + defaultMessage: 'Column split', + }), + getRowSplitHelp: () => + i18n.translate('expressionPartitionVis.reusable.function.dimension.splitrow', { + defaultMessage: 'Row split', + }), +}; + +export const errors = { + moreThanNBucketsAreNotSupportedError: (maxLength: number) => + i18n.translate('expressionPartitionVis.reusable.function.errors.moreThenNumberBuckets', { + defaultMessage: 'More than {maxLength} buckets are not supported', + values: { maxLength }, + }), + splitRowAndSplitColumnAreSpecifiedError: () => + i18n.translate('expressionPartitionVis.reusable.function.errors.splitRowAndColumnSpecified', { + defaultMessage: + 'A split row and column are specified. Expression is supporting only one of them at once.', + }), +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/index.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/index.ts new file mode 100644 index 0000000000000..6117b53a8b2a8 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { pieVisFunction } from './pie_vis_function'; +export { treemapVisFunction } from './treemap_vis_function'; +export { mosaicVisFunction } from './mosaic_vis_function'; +export { waffleVisFunction } from './waffle_vis_function'; +export { partitionLabelsFunction } from './partition_labels_function'; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.test.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.test.ts new file mode 100644 index 0000000000000..58f765899f5e3 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.test.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; +import { + MosaicVisConfig, + LabelPositions, + ValueFormats, + LegendDisplay, +} from '../types/expression_renderers'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { Datatable } from '../../../../expressions/common/expression_types/specs'; +import { mosaicVisFunction } from './mosaic_vis_function'; +import { PARTITION_LABELS_VALUE } from '../constants'; + +describe('interpreter/functions#mosaicVis', () => { + const fn = functionWrapper(mosaicVisFunction()); + const context: Datatable = { + type: 'datatable', + rows: [{ 'col-0-1': 0, 'col-0-2': 0, 'col-0-3': 0, 'col-0-4': 0 }], + columns: [ + { id: 'col-0-1', name: 'Field 1', meta: { type: 'number' } }, + { id: 'col-0-2', name: 'Field 2', meta: { type: 'number' } }, + { id: 'col-0-3', name: 'Field 3', meta: { type: 'number' } }, + { id: 'col-0-4', name: 'Field 4', meta: { type: 'number' } }, + ], + }; + + const visConfig: MosaicVisConfig = { + addTooltip: true, + legendDisplay: LegendDisplay.SHOW, + legendPosition: 'right', + nestedLegend: true, + truncateLegend: true, + maxLegendLines: 2, + palette: { + type: 'system_palette', + name: 'kibana_palette', + }, + labels: { + type: PARTITION_LABELS_VALUE, + show: false, + values: true, + position: LabelPositions.DEFAULT, + valuesFormat: ValueFormats.PERCENT, + percentDecimals: 2, + truncate: 100, + last_level: false, + }, + metric: { + type: 'vis_dimension', + accessor: 0, + format: { + id: 'number', + params: {}, + }, + }, + buckets: [ + { + type: 'vis_dimension', + accessor: 1, + format: { + id: 'number', + params: {}, + }, + }, + { + type: 'vis_dimension', + accessor: 2, + format: { + id: 'number', + params: {}, + }, + }, + ], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns an object with the correct structure', async () => { + const actual = await fn(context, visConfig); + expect(actual).toMatchSnapshot(); + }); + + it('throws error if provided more than 2 buckets', async () => { + expect(() => + fn(context, { + ...visConfig, + buckets: [ + ...(visConfig.buckets ?? []), + { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }, + ], + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('throws error if provided split row and split column at once', async () => { + const splitDimension: ExpressionValueVisDimension = { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }; + + expect(() => + fn(context, { + ...visConfig, + splitColumn: [splitDimension], + splitRow: [splitDimension], + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('logs correct datatable to inspector', async () => { + let loggedTable: Datatable; + const handlers = { + inspectorAdapters: { + tables: { + logDatatable: (name: string, datatable: Datatable) => { + loggedTable = datatable; + }, + }, + }, + }; + await fn(context, visConfig, handlers as any); + + expect(loggedTable!).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts new file mode 100644 index 0000000000000..388b0741d23d3 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/mosaic_vis_function.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers'; +import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table'; +import { ChartTypes, MosaicVisExpressionFunctionDefinition } from '../types'; +import { + PARTITION_LABELS_FUNCTION, + PARTITION_LABELS_VALUE, + PARTITION_VIS_RENDERER_NAME, + MOSAIC_VIS_EXPRESSION_NAME, +} from '../constants'; +import { errors, strings } from './i18n'; + +export const mosaicVisFunction = (): MosaicVisExpressionFunctionDefinition => ({ + name: MOSAIC_VIS_EXPRESSION_NAME, + type: 'render', + inputTypes: ['datatable'], + help: strings.getPieVisFunctionName(), + args: { + metric: { + types: ['vis_dimension'], + help: strings.getMetricArgHelp(), + required: true, + }, + buckets: { + types: ['vis_dimension'], + help: strings.getBucketsArgHelp(), + multi: true, + }, + splitColumn: { + types: ['vis_dimension'], + help: strings.getSplitColumnArgHelp(), + multi: true, + }, + splitRow: { + types: ['vis_dimension'], + help: strings.getSplitRowArgHelp(), + multi: true, + }, + addTooltip: { + types: ['boolean'], + help: strings.getAddTooltipArgHelp(), + default: true, + }, + legendDisplay: { + types: ['string'], + help: strings.getLegendDisplayArgHelp(), + options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT], + default: LegendDisplay.HIDE, + }, + legendPosition: { + types: ['string'], + help: strings.getLegendPositionArgHelp(), + }, + nestedLegend: { + types: ['boolean'], + help: strings.getNestedLegendArgHelp(), + default: false, + }, + truncateLegend: { + types: ['boolean'], + help: strings.getTruncateLegendArgHelp(), + default: true, + }, + maxLegendLines: { + types: ['number'], + help: strings.getMaxLegendLinesArgHelp(), + }, + palette: { + types: ['palette', 'system_palette'], + help: strings.getPaletteArgHelp(), + default: '{palette}', + }, + labels: { + types: [PARTITION_LABELS_VALUE], + help: strings.getLabelsArgHelp(), + default: `{${PARTITION_LABELS_FUNCTION}}`, + }, + }, + fn(context, args, handlers) { + const maxSupportedBuckets = 2; + if ((args.buckets ?? []).length > maxSupportedBuckets) { + throw new Error(errors.moreThanNBucketsAreNotSupportedError(maxSupportedBuckets)); + } + + if (args.splitColumn && args.splitRow) { + throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError()); + } + + const visConfig: PartitionVisParams = { + ...args, + palette: args.palette, + dimensions: { + metric: args.metric, + buckets: args.buckets, + splitColumn: args.splitColumn, + splitRow: args.splitRow, + }, + }; + + if (handlers?.inspectorAdapters?.tables) { + const logTable = prepareLogTable(context, [ + [[args.metric], strings.getSliceSizeHelp()], + [args.buckets, strings.getSliceHelp()], + [args.splitColumn, strings.getColumnSplitHelp()], + [args.splitRow, strings.getRowSplitHelp()], + ]); + handlers.inspectorAdapters.tables.logDatatable('default', logTable); + } + + return { + type: 'render', + as: PARTITION_VIS_RENDERER_NAME, + value: { + visData: context, + visConfig, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + visType: ChartTypes.MOSAIC, + params: { + listenOnChange: true, + }, + }, + }; + }, +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/partition_labels_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/partition_labels_function.ts new file mode 100644 index 0000000000000..900927a35983c --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/partition_labels_function.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition, Datatable } from '../../../../expressions/common'; +import { PARTITION_LABELS_FUNCTION, PARTITION_LABELS_VALUE } from '../constants'; +import { + ExpressionValuePartitionLabels, + LabelPositions, + PartitionLabelsArguments, + ValueFormats, +} from '../types'; + +export const partitionLabelsFunction = (): ExpressionFunctionDefinition< + typeof PARTITION_LABELS_FUNCTION, + Datatable | null, + PartitionLabelsArguments, + ExpressionValuePartitionLabels +> => ({ + name: PARTITION_LABELS_FUNCTION, + help: i18n.translate('expressionPartitionVis.partitionLabels.function.help', { + defaultMessage: 'Generates the partition labels object', + }), + type: PARTITION_LABELS_VALUE, + args: { + show: { + types: ['boolean'], + help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.show.help', { + defaultMessage: 'Displays the partition chart labels', + }), + default: true, + }, + position: { + types: ['string'], + default: 'default', + help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.position.help', { + defaultMessage: 'Defines the label position', + }), + options: [LabelPositions.DEFAULT, LabelPositions.INSIDE], + }, + values: { + types: ['boolean'], + help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.values.help', { + defaultMessage: 'Displays the values inside the slices', + }), + default: true, + }, + percentDecimals: { + types: ['number'], + help: i18n.translate( + 'expressionPartitionVis.partitionLabels.function.args.percentDecimals.help', + { + defaultMessage: + 'Defines the number of decimals that will appear on the values as percent', + } + ), + default: 2, + }, + // Deprecated + last_level: { + types: ['boolean'], + help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.last_level.help', { + defaultMessage: 'Show top level labels only for multilayer pie/donut charts', + }), + default: false, + }, + // Deprecated + truncate: { + types: ['number', 'null'], + help: i18n.translate('expressionPartitionVis.partitionLabels.function.args.truncate.help', { + defaultMessage: + 'Defines the number of characters that the slice value will display only for multilayer pie/donut charts', + }), + default: null, + }, + valuesFormat: { + types: ['string'], + default: 'percent', + help: i18n.translate( + 'expressionPartitionVis.partitionLabels.function.args.valuesFormat.help', + { + defaultMessage: 'Defines the format of the values', + } + ), + options: [ValueFormats.PERCENT, ValueFormats.VALUE], + }, + }, + fn: (context, args) => { + return { + type: PARTITION_LABELS_VALUE, + show: args.show, + position: args.position, + percentDecimals: args.percentDecimals, + values: args.values, + truncate: args.truncate, + valuesFormat: args.valuesFormat, + last_level: args.last_level, + }; + }, +}); diff --git a/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_vis_function.test.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.test.ts similarity index 56% rename from src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_vis_function.test.ts rename to src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.test.ts index 56807b4aa6a59..366683ce80ddb 100644 --- a/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_vis_function.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.test.ts @@ -12,21 +12,24 @@ import { EmptySizeRatios, LabelPositions, ValueFormats, + LegendDisplay, } from '../types/expression_renderers'; -import { pieVisFunction } from './pie_vis_function'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; import { Datatable } from '../../../../expressions/common/expression_types/specs'; +import { pieVisFunction } from './pie_vis_function'; +import { PARTITION_LABELS_VALUE } from '../constants'; -describe('interpreter/functions#pie', () => { +describe('interpreter/functions#pieVis', () => { const fn = functionWrapper(pieVisFunction()); - const context = { + const context: Datatable = { type: 'datatable', rows: [{ 'col-0-1': 0 }], - columns: [{ id: 'col-0-1', name: 'Count' }], - } as unknown as Datatable; + columns: [{ id: 'col-0-1', name: 'Count', meta: { type: 'number' } }], + }; const visConfig: PieVisConfig = { addTooltip: true, - addLegend: true, + legendDisplay: LegendDisplay.SHOW, legendPosition: 'right', isDonut: true, emptySizeRatio: EmptySizeRatios.SMALL, @@ -39,7 +42,7 @@ describe('interpreter/functions#pie', () => { name: 'kibana_palette', }, labels: { - type: 'pie_labels_value', + type: PARTITION_LABELS_VALUE, show: false, values: true, position: LabelPositions.DEFAULT, @@ -56,17 +59,67 @@ describe('interpreter/functions#pie', () => { params: {}, }, }, + buckets: [ + { + type: 'vis_dimension', + accessor: 1, + format: { + id: 'number', + params: {}, + }, + }, + { + type: 'vis_dimension', + accessor: 2, + format: { + id: 'number', + params: {}, + }, + }, + { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }, + ], }; beforeEach(() => { jest.clearAllMocks(); }); - it('returns an object with the correct structure', async () => { + it('returns an object with the correct structure for pie', async () => { + const actual = await fn(context, { ...visConfig, isDonut: false }); + expect(actual).toMatchSnapshot(); + }); + + it('returns an object with the correct structure for donut', async () => { const actual = await fn(context, visConfig); expect(actual).toMatchSnapshot(); }); + it('throws error if provided split row and split column at once', async () => { + const splitDimension: ExpressionValueVisDimension = { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }; + + expect(() => + fn(context, { + ...visConfig, + splitColumn: [splitDimension], + splitRow: [splitDimension], + }) + ).toThrowErrorMatchingSnapshot(); + }); + it('logs correct datatable to inspector', async () => { let loggedTable: Datatable; const handlers = { diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts new file mode 100644 index 0000000000000..c054d572538ce --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/pie_vis_function.ts @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Position } from '@elastic/charts'; +import { EmptySizeRatios, LegendDisplay, PartitionVisParams } from '../types/expression_renderers'; +import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table'; +import { ChartTypes, PieVisExpressionFunctionDefinition } from '../types'; +import { + PARTITION_LABELS_FUNCTION, + PARTITION_LABELS_VALUE, + PIE_VIS_EXPRESSION_NAME, + PARTITION_VIS_RENDERER_NAME, +} from '../constants'; +import { errors, strings } from './i18n'; + +export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({ + name: PIE_VIS_EXPRESSION_NAME, + type: 'render', + inputTypes: ['datatable'], + help: strings.getPieVisFunctionName(), + args: { + metric: { + types: ['vis_dimension'], + help: strings.getMetricArgHelp(), + required: true, + }, + buckets: { + types: ['vis_dimension'], + help: strings.getBucketsArgHelp(), + multi: true, + }, + splitColumn: { + types: ['vis_dimension'], + help: strings.getSplitColumnArgHelp(), + multi: true, + }, + splitRow: { + types: ['vis_dimension'], + help: strings.getSplitRowArgHelp(), + multi: true, + }, + addTooltip: { + types: ['boolean'], + help: strings.getAddTooltipArgHelp(), + default: true, + }, + legendDisplay: { + types: ['string'], + help: strings.getLegendDisplayArgHelp(), + options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT], + default: LegendDisplay.HIDE, + }, + legendPosition: { + types: ['string'], + help: strings.getLegendPositionArgHelp(), + options: [Position.Top, Position.Right, Position.Bottom, Position.Left], + }, + nestedLegend: { + types: ['boolean'], + help: strings.getNestedLegendArgHelp(), + default: false, + }, + truncateLegend: { + types: ['boolean'], + help: strings.getTruncateLegendArgHelp(), + default: true, + }, + maxLegendLines: { + types: ['number'], + help: strings.getMaxLegendLinesArgHelp(), + }, + distinctColors: { + types: ['boolean'], + help: strings.getDistinctColorsArgHelp(), + default: false, + }, + respectSourceOrder: { + types: ['boolean'], + help: strings.getRespectSourceOrderArgHelp(), + default: true, + }, + isDonut: { + types: ['boolean'], + help: strings.getIsDonutArgHelp(), + default: false, + }, + emptySizeRatio: { + types: ['number'], + help: strings.getEmptySizeRatioArgHelp(), + default: EmptySizeRatios.SMALL, + }, + palette: { + types: ['palette', 'system_palette'], + help: strings.getPaletteArgHelp(), + default: '{palette}', + }, + labels: { + types: [PARTITION_LABELS_VALUE], + help: strings.getLabelsArgHelp(), + default: `{${PARTITION_LABELS_FUNCTION}}`, + }, + startFromSecondLargestSlice: { + types: ['boolean'], + help: strings.getStartFromSecondLargestSliceArgHelp(), + default: true, + }, + }, + fn(context, args, handlers) { + if (args.splitColumn && args.splitRow) { + throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError()); + } + + const visConfig: PartitionVisParams = { + ...args, + palette: args.palette, + dimensions: { + metric: args.metric, + buckets: args.buckets, + splitColumn: args.splitColumn, + splitRow: args.splitRow, + }, + }; + + if (handlers?.inspectorAdapters?.tables) { + const logTable = prepareLogTable(context, [ + [[args.metric], strings.getSliceSizeHelp()], + [args.buckets, strings.getSliceHelp()], + [args.splitColumn, strings.getColumnSplitHelp()], + [args.splitRow, strings.getRowSplitHelp()], + ]); + handlers.inspectorAdapters.tables.logDatatable('default', logTable); + } + + return { + type: 'render', + as: PARTITION_VIS_RENDERER_NAME, + value: { + visData: context, + visConfig, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + visType: args.isDonut ? ChartTypes.DONUT : ChartTypes.PIE, + params: { + listenOnChange: true, + }, + }, + }; + }, +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.test.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.test.ts new file mode 100644 index 0000000000000..1d65ba35a5e0c --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.test.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; +import { + TreemapVisConfig, + LabelPositions, + ValueFormats, + LegendDisplay, +} from '../types/expression_renderers'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { Datatable } from '../../../../expressions/common/expression_types/specs'; +import { treemapVisFunction } from './treemap_vis_function'; +import { PARTITION_LABELS_VALUE } from '../constants'; + +describe('interpreter/functions#treemapVis', () => { + const fn = functionWrapper(treemapVisFunction()); + const context: Datatable = { + type: 'datatable', + rows: [{ 'col-0-1': 0, 'col-0-2': 0, 'col-0-3': 0, 'col-0-4': 0 }], + columns: [ + { id: 'col-0-1', name: 'Field 1', meta: { type: 'number' } }, + { id: 'col-0-2', name: 'Field 2', meta: { type: 'number' } }, + { id: 'col-0-3', name: 'Field 3', meta: { type: 'number' } }, + { id: 'col-0-4', name: 'Field 4', meta: { type: 'number' } }, + ], + }; + + const visConfig: TreemapVisConfig = { + addTooltip: true, + legendDisplay: LegendDisplay.SHOW, + legendPosition: 'right', + nestedLegend: true, + truncateLegend: true, + maxLegendLines: 2, + palette: { + type: 'system_palette', + name: 'kibana_palette', + }, + labels: { + type: PARTITION_LABELS_VALUE, + show: false, + values: true, + position: LabelPositions.DEFAULT, + valuesFormat: ValueFormats.PERCENT, + percentDecimals: 2, + truncate: 100, + last_level: false, + }, + metric: { + type: 'vis_dimension', + accessor: 0, + format: { + id: 'number', + params: {}, + }, + }, + buckets: [ + { + type: 'vis_dimension', + accessor: 1, + format: { + id: 'number', + params: {}, + }, + }, + { + type: 'vis_dimension', + accessor: 2, + format: { + id: 'number', + params: {}, + }, + }, + ], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns an object with the correct structure', async () => { + const actual = await fn(context, visConfig); + expect(actual).toMatchSnapshot(); + }); + + it('throws error if provided more than 2 buckets', async () => { + expect(() => + fn(context, { + ...visConfig, + buckets: [ + ...(visConfig.buckets ?? []), + { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }, + ], + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('throws error if provided split row and split column at once', async () => { + const splitDimension: ExpressionValueVisDimension = { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }; + + expect(() => + fn(context, { + ...visConfig, + splitColumn: [splitDimension], + splitRow: [splitDimension], + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('logs correct datatable to inspector', async () => { + let loggedTable: Datatable; + const handlers = { + inspectorAdapters: { + tables: { + logDatatable: (name: string, datatable: Datatable) => { + loggedTable = datatable; + }, + }, + }, + }; + await fn(context, visConfig, handlers as any); + + expect(loggedTable!).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts new file mode 100644 index 0000000000000..d0ae42b4b7942 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/treemap_vis_function.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers'; +import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table'; +import { ChartTypes, TreemapVisExpressionFunctionDefinition } from '../types'; +import { + PARTITION_LABELS_FUNCTION, + PARTITION_LABELS_VALUE, + PARTITION_VIS_RENDERER_NAME, + TREEMAP_VIS_EXPRESSION_NAME, +} from '../constants'; +import { errors, strings } from './i18n'; + +export const treemapVisFunction = (): TreemapVisExpressionFunctionDefinition => ({ + name: TREEMAP_VIS_EXPRESSION_NAME, + type: 'render', + inputTypes: ['datatable'], + help: strings.getPieVisFunctionName(), + args: { + metric: { + types: ['vis_dimension'], + help: strings.getMetricArgHelp(), + required: true, + }, + buckets: { + types: ['vis_dimension'], + help: strings.getBucketsArgHelp(), + multi: true, + }, + splitColumn: { + types: ['vis_dimension'], + help: strings.getSplitColumnArgHelp(), + multi: true, + }, + splitRow: { + types: ['vis_dimension'], + help: strings.getSplitRowArgHelp(), + multi: true, + }, + addTooltip: { + types: ['boolean'], + help: strings.getAddTooltipArgHelp(), + default: true, + }, + legendDisplay: { + types: ['string'], + help: strings.getLegendDisplayArgHelp(), + options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT], + default: LegendDisplay.HIDE, + }, + legendPosition: { + types: ['string'], + help: strings.getLegendPositionArgHelp(), + }, + nestedLegend: { + types: ['boolean'], + help: strings.getNestedLegendArgHelp(), + default: false, + }, + truncateLegend: { + types: ['boolean'], + help: strings.getTruncateLegendArgHelp(), + default: true, + }, + maxLegendLines: { + types: ['number'], + help: strings.getMaxLegendLinesArgHelp(), + }, + palette: { + types: ['palette', 'system_palette'], + help: strings.getPaletteArgHelp(), + default: '{palette}', + }, + labels: { + types: [PARTITION_LABELS_VALUE], + help: strings.getLabelsArgHelp(), + default: `{${PARTITION_LABELS_FUNCTION}}`, + }, + }, + fn(context, args, handlers) { + const maxSupportedBuckets = 2; + if ((args.buckets ?? []).length > maxSupportedBuckets) { + throw new Error(errors.moreThanNBucketsAreNotSupportedError(maxSupportedBuckets)); + } + + if (args.splitColumn && args.splitRow) { + throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError()); + } + + const visConfig: PartitionVisParams = { + ...args, + palette: args.palette, + dimensions: { + metric: args.metric, + buckets: args.buckets, + splitColumn: args.splitColumn, + splitRow: args.splitRow, + }, + }; + + if (handlers?.inspectorAdapters?.tables) { + const logTable = prepareLogTable(context, [ + [[args.metric], strings.getSliceSizeHelp()], + [args.buckets, strings.getSliceHelp()], + [args.splitColumn, strings.getColumnSplitHelp()], + [args.splitRow, strings.getRowSplitHelp()], + ]); + handlers.inspectorAdapters.tables.logDatatable('default', logTable); + } + + return { + type: 'render', + as: PARTITION_VIS_RENDERER_NAME, + value: { + visData: context, + visConfig, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + visType: ChartTypes.TREEMAP, + params: { + listenOnChange: true, + }, + }, + }; + }, +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts new file mode 100644 index 0000000000000..ead4d97a8f8e0 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { functionWrapper } from '../../../../expressions/common/expression_functions/specs/tests/utils'; +import { + WaffleVisConfig, + LabelPositions, + ValueFormats, + LegendDisplay, +} from '../types/expression_renderers'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { Datatable } from '../../../../expressions/common/expression_types/specs'; +import { waffleVisFunction } from './waffle_vis_function'; +import { PARTITION_LABELS_VALUE } from '../constants'; + +describe('interpreter/functions#waffleVis', () => { + const fn = functionWrapper(waffleVisFunction()); + const context: Datatable = { + type: 'datatable', + rows: [{ 'col-0-1': 0, 'col-0-2': 0, 'col-0-3': 0, 'col-0-4': 0 }], + columns: [ + { id: 'col-0-1', name: 'Field 1', meta: { type: 'number' } }, + { id: 'col-0-2', name: 'Field 2', meta: { type: 'number' } }, + { id: 'col-0-3', name: 'Field 3', meta: { type: 'number' } }, + { id: 'col-0-4', name: 'Field 4', meta: { type: 'number' } }, + ], + }; + + const visConfig: WaffleVisConfig = { + addTooltip: true, + showValuesInLegend: true, + legendDisplay: LegendDisplay.SHOW, + legendPosition: 'right', + truncateLegend: true, + maxLegendLines: 2, + palette: { + type: 'system_palette', + name: 'kibana_palette', + }, + labels: { + type: PARTITION_LABELS_VALUE, + show: false, + values: true, + position: LabelPositions.DEFAULT, + valuesFormat: ValueFormats.PERCENT, + percentDecimals: 2, + truncate: 100, + last_level: false, + }, + metric: { + type: 'vis_dimension', + accessor: 0, + format: { + id: 'number', + params: {}, + }, + }, + bucket: { + type: 'vis_dimension', + accessor: 1, + format: { + id: 'number', + params: {}, + }, + }, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('returns an object with the correct structure', async () => { + const actual = await fn(context, visConfig); + expect(actual).toMatchSnapshot(); + }); + + it('throws error if provided split row and split column at once', async () => { + const splitDimension: ExpressionValueVisDimension = { + type: 'vis_dimension', + accessor: 3, + format: { + id: 'number', + params: {}, + }, + }; + + expect(() => + fn(context, { + ...visConfig, + splitColumn: [splitDimension], + splitRow: [splitDimension], + }) + ).toThrowErrorMatchingSnapshot(); + }); + + it('logs correct datatable to inspector', async () => { + let loggedTable: Datatable; + const handlers = { + inspectorAdapters: { + tables: { + logDatatable: (name: string, datatable: Datatable) => { + loggedTable = datatable; + }, + }, + }, + }; + await fn(context, visConfig, handlers as any); + + expect(loggedTable!).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts new file mode 100644 index 0000000000000..ade524aad59c8 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/expression_functions/waffle_vis_function.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { LegendDisplay, PartitionVisParams } from '../types/expression_renderers'; +import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table'; +import { ChartTypes, WaffleVisExpressionFunctionDefinition } from '../types'; +import { + PARTITION_LABELS_FUNCTION, + PARTITION_LABELS_VALUE, + PARTITION_VIS_RENDERER_NAME, + WAFFLE_VIS_EXPRESSION_NAME, +} from '../constants'; +import { errors, strings } from './i18n'; + +export const waffleVisFunction = (): WaffleVisExpressionFunctionDefinition => ({ + name: WAFFLE_VIS_EXPRESSION_NAME, + type: 'render', + inputTypes: ['datatable'], + help: strings.getPieVisFunctionName(), + args: { + metric: { + types: ['vis_dimension'], + help: strings.getMetricArgHelp(), + required: true, + }, + bucket: { + types: ['vis_dimension'], + help: strings.getBucketArgHelp(), + }, + splitColumn: { + types: ['vis_dimension'], + help: strings.getSplitColumnArgHelp(), + multi: true, + }, + splitRow: { + types: ['vis_dimension'], + help: strings.getSplitRowArgHelp(), + multi: true, + }, + addTooltip: { + types: ['boolean'], + help: strings.getAddTooltipArgHelp(), + default: true, + }, + legendDisplay: { + types: ['string'], + help: strings.getLegendDisplayArgHelp(), + options: [LegendDisplay.SHOW, LegendDisplay.HIDE, LegendDisplay.DEFAULT], + default: LegendDisplay.HIDE, + }, + legendPosition: { + types: ['string'], + help: strings.getLegendPositionArgHelp(), + }, + truncateLegend: { + types: ['boolean'], + help: strings.getTruncateLegendArgHelp(), + default: true, + }, + maxLegendLines: { + types: ['number'], + help: strings.getMaxLegendLinesArgHelp(), + }, + palette: { + types: ['palette', 'system_palette'], + help: strings.getPaletteArgHelp(), + default: '{palette}', + }, + labels: { + types: [PARTITION_LABELS_VALUE], + help: strings.getLabelsArgHelp(), + default: `{${PARTITION_LABELS_FUNCTION}}`, + }, + showValuesInLegend: { + types: ['boolean'], + help: strings.getShowValuesInLegendArgHelp(), + default: false, + }, + }, + fn(context, args, handlers) { + if (args.splitColumn && args.splitRow) { + throw new Error(errors.splitRowAndSplitColumnAreSpecifiedError()); + } + + const buckets = args.bucket ? [args.bucket] : []; + const visConfig: PartitionVisParams = { + ...args, + palette: args.palette, + dimensions: { + metric: args.metric, + buckets, + splitColumn: args.splitColumn, + splitRow: args.splitRow, + }, + }; + + if (handlers?.inspectorAdapters?.tables) { + const logTable = prepareLogTable(context, [ + [[args.metric], strings.getSliceSizeHelp()], + [buckets, strings.getSliceHelp()], + [args.splitColumn, strings.getColumnSplitHelp()], + [args.splitRow, strings.getRowSplitHelp()], + ]); + handlers.inspectorAdapters.tables.logDatatable('default', logTable); + } + + return { + type: 'render', + as: PARTITION_VIS_RENDERER_NAME, + value: { + visData: context, + visConfig, + syncColors: handlers?.isSyncColorsEnabled?.() ?? false, + visType: ChartTypes.WAFFLE, + params: { + listenOnChange: true, + }, + }, + }; + }, +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/index.ts b/src/plugins/chart_expressions/expression_partition_vis/common/index.ts new file mode 100755 index 0000000000000..559d597caf90c --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/index.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { + PLUGIN_ID, + PLUGIN_NAME, + PIE_VIS_EXPRESSION_NAME, + TREEMAP_VIS_EXPRESSION_NAME, + MOSAIC_VIS_EXPRESSION_NAME, + WAFFLE_VIS_EXPRESSION_NAME, + PARTITION_LABELS_VALUE, + PARTITION_LABELS_FUNCTION, +} from './constants'; + +export { + pieVisFunction, + treemapVisFunction, + waffleVisFunction, + mosaicVisFunction, + partitionLabelsFunction, +} from './expression_functions'; + +export type { + ExpressionValuePartitionLabels, + PieVisExpressionFunctionDefinition, + TreemapVisExpressionFunctionDefinition, + MosaicVisExpressionFunctionDefinition, + WaffleVisExpressionFunctionDefinition, +} from './types/expression_functions'; + +export type { + PartitionVisParams, + PieVisConfig, + TreemapVisConfig, + MosaicVisConfig, + WaffleVisConfig, + LabelsParams, + Dimension, + Dimensions, +} from './types/expression_renderers'; + +export { + ValueFormats, + LabelPositions, + EmptySizeRatios, + LegendDisplay, +} from './types/expression_renderers'; diff --git a/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts new file mode 100644 index 0000000000000..bc623fd621345 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_functions.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + PARTITION_LABELS_VALUE, + PIE_VIS_EXPRESSION_NAME, + TREEMAP_VIS_EXPRESSION_NAME, + MOSAIC_VIS_EXPRESSION_NAME, + WAFFLE_VIS_EXPRESSION_NAME, +} from '../constants'; +import { + ExpressionFunctionDefinition, + Datatable, + ExpressionValueRender, + ExpressionValueBoxed, +} from '../../../../expressions/common'; +import { + RenderValue, + PieVisConfig, + LabelPositions, + ValueFormats, + TreemapVisConfig, + MosaicVisConfig, + WaffleVisConfig, +} from './expression_renderers'; + +export interface PartitionLabelsArguments { + show: boolean; + position: LabelPositions; + values: boolean; + valuesFormat: ValueFormats; + percentDecimals: number; + /** @deprecated This field is deprecated and going to be removed in the futher release versions. */ + truncate?: number | null; + /** @deprecated This field is deprecated and going to be removed in the futher release versions. */ + last_level?: boolean; +} + +export type ExpressionValuePartitionLabels = ExpressionValueBoxed< + typeof PARTITION_LABELS_VALUE, + { + show: boolean; + position: LabelPositions; + values: boolean; + valuesFormat: ValueFormats; + percentDecimals: number; + /** @deprecated This field is deprecated and going to be removed in the futher release versions. */ + truncate?: number | null; + /** @deprecated This field is deprecated and going to be removed in the futher release versions. */ + last_level?: boolean; + } +>; + +export type PieVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof PIE_VIS_EXPRESSION_NAME, + Datatable, + PieVisConfig, + ExpressionValueRender +>; + +export type TreemapVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof TREEMAP_VIS_EXPRESSION_NAME, + Datatable, + TreemapVisConfig, + ExpressionValueRender +>; + +export type MosaicVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof MOSAIC_VIS_EXPRESSION_NAME, + Datatable, + MosaicVisConfig, + ExpressionValueRender +>; + +export type WaffleVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + typeof WAFFLE_VIS_EXPRESSION_NAME, + Datatable, + WaffleVisConfig, + ExpressionValueRender +>; + +export enum ChartTypes { + PIE = 'pie', + DONUT = 'donut', + TREEMAP = 'treemap', + MOSAIC = 'mosaic', + WAFFLE = 'waffle', +} diff --git a/src/plugins/chart_expressions/expression_pie/common/types/expression_renderers.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts similarity index 59% rename from src/plugins/chart_expressions/expression_pie/common/types/expression_renderers.ts rename to src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts index dd7bfacc0b9c1..87358d5dbe659 100644 --- a/src/plugins/chart_expressions/expression_pie/common/types/expression_renderers.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/common/types/expression_renderers.ts @@ -11,7 +11,7 @@ import { Datatable, DatatableColumn } from '../../../../expressions/common'; import { SerializedFieldFormat } from '../../../../field_formats/common'; import { ExpressionValueVisDimension } from '../../../../visualizations/common'; import { PaletteOutput } from '../../../../charts/common'; -import { ExpressionValuePieLabels } from './expression_functions'; +import { ChartTypes, ExpressionValuePartitionLabels } from './expression_functions'; export enum EmptySizeRatios { SMALL = 0.3, @@ -28,7 +28,7 @@ export interface Dimension { } export interface Dimensions { - metric: ExpressionValueVisDimension; + metric?: ExpressionValueVisDimension; buckets?: ExpressionValueVisDimension[]; splitRow?: ExpressionValueVisDimension[]; splitColumn?: ExpressionValueVisDimension[]; @@ -36,45 +36,74 @@ export interface Dimensions { export interface LabelsParams { show: boolean; - last_level: boolean; position: LabelPositions; values: boolean; - truncate: number | null; valuesFormat: ValueFormats; percentDecimals: number; + /** @deprecated This field is deprecated and going to be removed in the futher release versions. */ + truncate?: number | null; + /** @deprecated This field is deprecated and going to be removed in the futher release versions. */ + last_level?: boolean; } -interface PieCommonParams { +interface VisCommonParams { addTooltip: boolean; - addLegend: boolean; + legendDisplay: LegendDisplay; legendPosition: Position; - nestedLegend: boolean; truncateLegend: boolean; maxLegendLines: number; - distinctColors: boolean; - isDonut: boolean; - emptySizeRatio?: EmptySizeRatios; } -export interface PieVisParams extends PieCommonParams { +interface VisCommonConfig extends VisCommonParams { + metric: ExpressionValueVisDimension; + splitColumn?: ExpressionValueVisDimension[]; + splitRow?: ExpressionValueVisDimension[]; + labels: ExpressionValuePartitionLabels; + palette: PaletteOutput; +} + +export interface PartitionVisParams extends VisCommonParams { dimensions: Dimensions; labels: LabelsParams; palette: PaletteOutput; + isDonut?: boolean; + showValuesInLegend?: boolean; + respectSourceOrder?: boolean; + emptySizeRatio?: EmptySizeRatios; + startFromSecondLargestSlice?: boolean; + distinctColors?: boolean; + nestedLegend?: boolean; } -export interface PieVisConfig extends PieCommonParams { +export interface PieVisConfig extends VisCommonConfig { buckets?: ExpressionValueVisDimension[]; - metric: ExpressionValueVisDimension; - splitColumn?: ExpressionValueVisDimension[]; - splitRow?: ExpressionValueVisDimension[]; - labels: ExpressionValuePieLabels; - palette: PaletteOutput; + isDonut: boolean; + emptySizeRatio?: EmptySizeRatios; + respectSourceOrder?: boolean; + startFromSecondLargestSlice?: boolean; + distinctColors?: boolean; + nestedLegend: boolean; +} + +export interface TreemapVisConfig extends VisCommonConfig { + buckets?: ExpressionValueVisDimension[]; + nestedLegend: boolean; +} + +export interface MosaicVisConfig extends VisCommonConfig { + buckets?: ExpressionValueVisDimension[]; + nestedLegend: boolean; +} + +export interface WaffleVisConfig extends VisCommonConfig { + bucket?: ExpressionValueVisDimension; + showValuesInLegend: boolean; } export interface RenderValue { visData: Datatable; - visType: string; - visConfig: PieVisParams; + visType: ChartTypes; + visConfig: PartitionVisParams; syncColors: boolean; } @@ -88,6 +117,12 @@ export enum ValueFormats { VALUE = 'value', } +export enum LegendDisplay { + SHOW = 'show', + HIDE = 'hide', + DEFAULT = 'default', +} + export interface BucketColumns extends DatatableColumn { format?: { id?: string; diff --git a/src/plugins/chart_expressions/expression_pie/common/types/index.ts b/src/plugins/chart_expressions/expression_partition_vis/common/types/index.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/common/types/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/common/types/index.ts diff --git a/src/plugins/chart_expressions/expression_pie/jest.config.js b/src/plugins/chart_expressions/expression_partition_vis/jest.config.js similarity index 72% rename from src/plugins/chart_expressions/expression_pie/jest.config.js rename to src/plugins/chart_expressions/expression_partition_vis/jest.config.js index d8dd288fab086..c449f1e1f2453 100644 --- a/src/plugins/chart_expressions/expression_pie/jest.config.js +++ b/src/plugins/chart_expressions/expression_partition_vis/jest.config.js @@ -9,11 +9,11 @@ module.exports = { preset: '@kbn/test', rootDir: '../../../../', - roots: ['/src/plugins/chart_expressions/expression_pie'], + roots: ['/src/plugins/chart_expressions/expression_partition_vis'], coverageDirectory: - '/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_pie', + '/target/kibana-coverage/jest/src/plugins/chart_expressions/expression_partition_vis', coverageReporters: ['text', 'html'], collectCoverageFrom: [ - '/src/plugins/chart_expressions/expression_pie/{common,public,server}/**/*.{ts,tsx}', + '/src/plugins/chart_expressions/expression_partition_vis/{common,public,server}/**/*.{ts,tsx}', ], }; diff --git a/src/plugins/chart_expressions/expression_pie/kibana.json b/src/plugins/chart_expressions/expression_partition_vis/kibana.json similarity index 55% rename from src/plugins/chart_expressions/expression_pie/kibana.json rename to src/plugins/chart_expressions/expression_partition_vis/kibana.json index a681ca1ed00ac..226d1681cd3fc 100755 --- a/src/plugins/chart_expressions/expression_pie/kibana.json +++ b/src/plugins/chart_expressions/expression_partition_vis/kibana.json @@ -1,12 +1,12 @@ { - "id": "expressionPie", + "id": "expressionPartitionVis", "version": "1.0.0", "kibanaVersion": "kibana", "owner": { "name": "Vis Editors", "githubTeam": "kibana-vis-editors" }, - "description": "Expression Pie plugin adds a `pie` renderer and function to the expression plugin. The renderer will display the `pie` chart.", + "description": "Expression Partition Visualization plugin adds a `partitionVis` renderer and `pieVis`, `mosaicVis`, `treemapVis`, `waffleVis` functions to the expression plugin. The renderer will display the `pie`, `waffle`, `treemap` and `mosaic` charts.", "server": true, "ui": true, "extraPublicDirs": [ diff --git a/src/plugins/chart_expressions/expression_pie/public/__mocks__/format_service.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/format_service.ts similarity index 88% rename from src/plugins/chart_expressions/expression_pie/public/__mocks__/format_service.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/format_service.ts index 77f6d8eb0bf37..84ad7f4f0f5bd 100644 --- a/src/plugins/chart_expressions/expression_pie/public/__mocks__/format_service.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/format_service.ts @@ -8,6 +8,6 @@ export const getFormatService = () => ({ deserialize: (target: any) => ({ - convert: (text: string, format: string) => text, + convert: (text: string, format: string) => `${text}`, }), }); diff --git a/src/plugins/chart_expressions/expression_pie/public/__mocks__/index.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/index.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/__mocks__/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/index.ts diff --git a/src/plugins/chart_expressions/expression_pie/public/__mocks__/palettes.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/palettes.ts similarity index 87% rename from src/plugins/chart_expressions/expression_pie/public/__mocks__/palettes.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/palettes.ts index f418a7e561606..5637acfdbee10 100644 --- a/src/plugins/chart_expressions/expression_pie/public/__mocks__/palettes.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/palettes.ts @@ -21,10 +21,15 @@ export const getPaletteRegistry = () => { '#AA6556', '#E7664C', ]; + let counter = 0; const mockPalette: PaletteDefinition = { id: 'default', title: 'My Palette', - getCategoricalColor: (_: SeriesLayer[]) => colors[0], + getCategoricalColor: (_: SeriesLayer[]) => { + counter++; + if (counter > colors.length - 1) counter = 0; + return colors[counter]; + }, getCategoricalColors: (num: number) => colors, toExpression: () => ({ type: 'expression', diff --git a/src/plugins/chart_expressions/expression_pie/public/__mocks__/start_deps.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/start_deps.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/__mocks__/start_deps.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/start_deps.ts diff --git a/src/plugins/chart_expressions/expression_pie/public/__mocks__/theme.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/theme.ts similarity index 79% rename from src/plugins/chart_expressions/expression_pie/public/__mocks__/theme.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/theme.ts index b5af8dc496608..178b8db605e1a 100644 --- a/src/plugins/chart_expressions/expression_pie/public/__mocks__/theme.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/theme.ts @@ -8,5 +8,9 @@ // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ThemeService } from '../../../../charts/public/services'; +import { uiSettings } from './ui_settings'; -export const theme = new ThemeService(); +const theme = new ThemeService(); +theme.init(uiSettings); + +export { theme }; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/ui_settings.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/ui_settings.ts new file mode 100644 index 0000000000000..c5838d82867fb --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__mocks__/ui_settings.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IUiSettingsClient, PublicUiSettingsParams, UserProvidedValues } from 'kibana/public'; +import { Observable } from 'rxjs'; + +export const uiSettings: IUiSettingsClient = { + set: (key: string, value: any) => Promise.resolve(true), + remove: (key: string) => Promise.resolve(true), + isCustom: (key: string) => false, + isOverridden: (key: string) => Boolean(uiSettings.getAll()[key].isOverridden), + getUpdate$: () => + new Observable<{ + key: string; + newValue: any; + oldValue: any; + }>(), + isDeclared: (key: string) => true, + isDefault: (key: string) => true, + getUpdateErrors$: () => new Observable(), + get: (key: string, defaultOverride?: any): any => uiSettings.getAll()[key] || defaultOverride, + get$: (key: string) => new Observable(uiSettings.get(key)), + getAll: (): Readonly> => { + return {}; + }, +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/mosaic_vis_renderer.stories.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/mosaic_vis_renderer.stories.tsx new file mode 100644 index 0000000000000..bba644f721038 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/mosaic_vis_renderer.stories.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { ComponentStory } from '@storybook/react'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { getPartitionVisRenderer } from '../expression_renderers'; +import { ChartTypes, RenderValue } from '../../common/types'; +import { palettes, theme, getStartDeps } from '../__mocks__'; +import { mosaicArgTypes, treemapMosaicConfig, data } from './shared'; + +const containerSize = { + width: '700px', + height: '700px', +}; + +const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps }); + +type Props = { + visType: RenderValue['visType']; + syncColors: RenderValue['syncColors']; +} & RenderValue['visConfig']; + +const PartitionVis: ComponentStory> = ({ + visType, + syncColors, + children, + ...visConfig +}) => ( + +); + +export default { + title: 'renderers/mosaicVis', + component: PartitionVis, + argTypes: mosaicArgTypes, +}; + +const Default = PartitionVis.bind({}); +Default.args = { ...treemapMosaicConfig, visType: ChartTypes.MOSAIC, syncColors: false }; + +export { Default }; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/pie_vis_renderer.stories.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/pie_vis_renderer.stories.tsx new file mode 100644 index 0000000000000..b6d6e9055e692 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/pie_vis_renderer.stories.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { ComponentStory } from '@storybook/react'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { getPartitionVisRenderer } from '../expression_renderers'; +import { ChartTypes, RenderValue } from '../../common/types'; +import { palettes, theme, getStartDeps } from '../__mocks__'; +import { pieDonutArgTypes, pieConfig, data } from './shared'; + +const containerSize = { + width: '700px', + height: '700px', +}; + +const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps }); + +type Props = { + visType: RenderValue['visType']; + syncColors: RenderValue['syncColors']; +} & RenderValue['visConfig']; + +const PartitionVis: ComponentStory> = ({ + visType, + syncColors, + children, + ...visConfig +}) => ( + +); + +export default { + title: 'renderers/pieVis', + component: PartitionVis, + argTypes: pieDonutArgTypes, +}; + +const Default = PartitionVis.bind({}); +Default.args = { ...pieConfig, visType: ChartTypes.PIE, syncColors: false }; + +export { Default }; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts new file mode 100644 index 0000000000000..1a18c905548d4 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/arg_types.ts @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Position } from '@elastic/charts'; +import { ArgTypes } from '@storybook/addons'; +import { EmptySizeRatios, LegendDisplay } from '../../../common'; +import { ChartTypes } from '../../../common/types'; + +const visConfigName = 'visConfig'; + +export const argTypes: ArgTypes = { + addTooltip: { + name: `${visConfigName}.addTooltip`, + description: 'Add tooltip on hover', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: true } }, + control: { type: 'boolean' }, + }, + legendDisplay: { + name: `${visConfigName}.legendDisplay`, + description: 'Legend mode of displaying', + type: { name: 'string', required: false }, + table: { type: { summary: 'string' }, defaultValue: { summary: LegendDisplay.HIDE } }, + options: Object.values(LegendDisplay), + control: { type: 'select' }, + }, + legendPosition: { + name: `${visConfigName}.legendPosition`, + description: 'Legend position', + type: { name: 'string', required: false }, + table: { type: { summary: 'string' }, defaultValue: { summary: Position.Bottom } }, + options: Object.values(Position), + control: { type: 'select' }, + }, + truncateLegend: { + name: `${visConfigName}.truncateLegend`, + description: 'Truncate too long legend', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: true } }, + control: { type: 'boolean' }, + }, + maxLegendLines: { + name: `${visConfigName}.maxLegendLines`, + description: 'Legend maximum number of lines', + type: { name: 'number', required: false }, + table: { type: { summary: 'number' } }, + control: { type: 'number' }, + }, + palette: { + name: `${visConfigName}.palette`, + description: 'Palette', + type: { name: 'palette', required: false }, + table: { type: { summary: 'object' } }, + control: { type: 'object' }, + }, + labels: { + name: `${visConfigName}.labels`, + description: 'Labels configuration', + type: { name: 'object', required: false }, + table: { + type: { + summary: 'object', + detail: `Labels configuration consists of further fields: + - show: boolean. Default: true. + - position: string. Options: 'default', 'inside'. Default: 'default'. + - values: boolean. Default: true. + - percentDecimals: number. Default: 2. + - last_level: boolean. Default: false. DEPRECATED. + - truncate: number. Default: null. + - valuesFormat: string. Options: 'percent', 'value'. Default: percent. + `, + }, + }, + control: { type: 'object' }, + }, + dimensions: { + name: `${visConfigName}.dimensions`, + description: 'dimensions configuration', + type: { name: 'object', required: false }, + table: { + type: { + summary: 'object', + detail: `Dimensions configuration consists of two fields: + - metric: visdimension. + - buckets: visdimension[]. + `, + }, + }, + control: { type: 'object' }, + }, +}; + +export const pieDonutArgTypes: ArgTypes = { + ...argTypes, + visType: { + name: `visType`, + description: 'Type of the chart', + type: { name: 'string', required: false }, + table: { + type: { summary: 'string' }, + defaultValue: { summary: `${ChartTypes.PIE} | ${ChartTypes.DONUT}` }, + }, + control: { type: 'text', disable: true }, + }, + isDonut: { + name: `${visConfigName}.isDonut`, + description: 'Render a donut chart', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: false } }, + control: { type: 'boolean' }, + }, + emptySizeRatio: { + name: `${visConfigName}.emptySizeRatio`, + description: 'The hole size of the donut chart', + type: { name: 'number', required: false }, + table: { type: { summary: 'number' }, defaultValue: { summary: EmptySizeRatios.SMALL } }, + options: [EmptySizeRatios.SMALL, EmptySizeRatios.MEDIUM, EmptySizeRatios.LARGE], + control: { type: 'select' }, + }, + distinctColors: { + name: `${visConfigName}.distinctColors`, + description: 'Enable distinct colors', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: false } }, + control: { type: 'boolean' }, + }, + respectSourceOrder: { + name: `${visConfigName}.respectSourceOrder`, + description: 'Save default order of the incomming data', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: true } }, + control: { type: 'boolean' }, + }, + startFromSecondLargestSlice: { + name: `${visConfigName}.startFromSecondLargestSlice`, + description: 'Start placement of slices from the second largest slice', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: true } }, + control: { type: 'boolean' }, + }, + nestedLegend: { + name: `${visConfigName}.nestedLegend`, + description: 'Enable nested legend', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: false } }, + control: { type: 'boolean' }, + }, +}; + +export const treemapArgTypes: ArgTypes = { + visType: { + name: `visType`, + description: 'Type of the chart', + type: { name: 'string', required: false }, + table: { + type: { summary: 'string' }, + defaultValue: { summary: `${ChartTypes.TREEMAP}` }, + }, + control: { type: 'text', disable: true }, + }, + ...argTypes, + nestedLegend: { + name: `${visConfigName}.nestedLegend`, + description: 'Enable nested legend', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: false } }, + control: { type: 'boolean' }, + }, +}; + +export const mosaicArgTypes: ArgTypes = { + visType: { + name: `visType`, + description: 'Type of the chart', + type: { name: 'string', required: false }, + table: { + type: { summary: 'string' }, + defaultValue: { summary: `${ChartTypes.MOSAIC}` }, + }, + control: { type: 'text', disable: true }, + }, + ...argTypes, + nestedLegend: { + name: `${visConfigName}.nestedLegend`, + description: 'Enable nested legend', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: false } }, + control: { type: 'boolean' }, + }, +}; + +export const waffleArgTypes: ArgTypes = { + visType: { + name: `visType`, + description: 'Type of the chart', + type: { name: 'string', required: false }, + table: { + type: { summary: 'string' }, + defaultValue: { summary: `${ChartTypes.WAFFLE}` }, + }, + control: { type: 'text', disable: true }, + }, + ...argTypes, + showValuesInLegend: { + name: `${visConfigName}.nestedLegend`, + description: 'Enable displaying values in the legend', + type: { name: 'boolean', required: false }, + table: { type: { summary: 'boolean' }, defaultValue: { summary: false } }, + control: { type: 'boolean' }, + }, +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/config.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/config.ts new file mode 100644 index 0000000000000..d16802518cce4 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/config.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { Position } from '@elastic/charts'; +import { + LabelPositions, + LegendDisplay, + RenderValue, + PartitionVisParams, + ValueFormats, +} from '../../../common/types'; + +export const config: RenderValue['visConfig'] = { + addTooltip: true, + legendDisplay: LegendDisplay.HIDE, + truncateLegend: true, + respectSourceOrder: true, + legendPosition: Position.Bottom, + maxLegendLines: 1, + palette: { + type: 'palette', + name: 'system_palette', + }, + labels: { + show: true, + position: LabelPositions.DEFAULT, + percentDecimals: 2, + values: true, + truncate: 0, + valuesFormat: ValueFormats.PERCENT, + last_level: false, + }, + dimensions: { + metric: { + type: 'vis_dimension', + accessor: { + id: 'percent_uptime', + name: 'percent_uptime', + meta: { + type: 'number', + }, + }, + format: { + id: 'string', + params: {}, + }, + }, + }, +}; + +export const pieConfig: PartitionVisParams = { + ...config, + isDonut: false, + emptySizeRatio: 0, + distinctColors: false, + nestedLegend: false, + dimensions: { + ...config.dimensions, + buckets: [ + { + type: 'vis_dimension', + accessor: { + id: 'project', + name: 'project', + meta: { + type: 'string', + }, + }, + format: { + id: 'string', + params: {}, + }, + }, + ], + }, + startFromSecondLargestSlice: true, +}; + +export const treemapMosaicConfig: PartitionVisParams = { + ...config, + nestedLegend: false, + dimensions: { + ...config.dimensions, + buckets: [ + { + type: 'vis_dimension', + accessor: { + id: 'project', + name: 'project', + meta: { + type: 'string', + }, + }, + format: { + id: 'string', + params: {}, + }, + }, + ], + }, +}; + +export const waffleConfig: PartitionVisParams = { + ...config, + dimensions: { + ...config.dimensions, + buckets: [ + { + type: 'vis_dimension', + accessor: { + id: 'project', + name: 'project', + meta: { + type: 'string', + }, + }, + format: { + id: 'string', + params: {}, + }, + }, + ], + }, + showValuesInLegend: false, +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/data.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/data.ts new file mode 100644 index 0000000000000..e02f090b5f7fa --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/data.ts @@ -0,0 +1,207 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RenderValue } from '../../../common/types'; + +export const data: RenderValue['visData'] = { + type: 'datatable', + columns: [ + { + id: '@timestamp', + name: '@timestamp', + meta: { + type: 'date', + }, + }, + { + id: 'time', + name: 'time', + meta: { + type: 'date', + }, + }, + { + id: 'cost', + name: 'cost', + meta: { + type: 'number', + }, + }, + { + id: 'username', + name: 'username', + meta: { + type: 'string', + }, + }, + { + id: 'price', + name: 'price', + meta: { + type: 'number', + }, + }, + { + id: 'age', + name: 'age', + meta: { + type: 'number', + }, + }, + { + id: 'country', + name: 'country', + meta: { + type: 'string', + }, + }, + { + id: 'state', + name: 'state', + meta: { + type: 'string', + }, + }, + { + id: 'project', + name: 'project', + meta: { + type: 'string', + }, + }, + { + id: 'percent_uptime', + name: 'percent_uptime', + meta: { + type: 'number', + }, + }, + ], + rows: [ + { + age: 63, + cost: 32.15, + country: 'US', + price: 53, + project: 'elasticsearch', + state: 'running', + time: 1546334211208, + '@timestamp': 1546334211208, + username: 'aevans2e', + percent_uptime: 0.83, + }, + { + age: 68, + cost: 20.52, + country: 'JP', + price: 33, + project: 'beats', + state: 'done', + time: 1546351551031, + '@timestamp': 1546351551031, + username: 'aking2c', + percent_uptime: 0.9, + }, + { + age: 57, + cost: 21.15, + country: 'UK', + price: 59, + project: 'apm', + state: 'running', + time: 1546352631083, + '@timestamp': 1546352631083, + username: 'mmoore2o', + percent_uptime: 0.96, + }, + { + age: 73, + cost: 35.64, + country: 'CN', + price: 71, + project: 'machine-learning', + state: 'start', + time: 1546402490956, + '@timestamp': 1546402490956, + username: 'wrodriguez1r', + percent_uptime: 0.61, + }, + { + age: 38, + cost: 27.19, + country: 'TZ', + price: 36, + project: 'kibana', + state: 'done', + time: 1546467111351, + '@timestamp': 1546467111351, + username: 'wrodriguez1r', + percent_uptime: 0.72, + }, + { + age: 61, + cost: 49.95, + country: 'NL', + price: 65, + project: 'machine-learning', + state: 'start', + time: 1546473771019, + '@timestamp': 1546473771019, + username: 'mmoore2o', + percent_uptime: 0.72, + }, + { + age: 53, + cost: 27.36, + country: 'JP', + price: 60, + project: 'x-pack', + state: 'running', + time: 1546482171310, + '@timestamp': 1546482171310, + username: 'hcrawford2h', + percent_uptime: 0.65, + }, + { + age: 31, + cost: 33.77, + country: 'AZ', + price: 77, + project: 'kibana', + state: 'start', + time: 1546493451206, + '@timestamp': 1546493451206, + username: 'aking2c', + percent_uptime: 0.92, + }, + { + age: 71, + cost: 20.2, + country: 'TZ', + price: 57, + project: 'swiftype', + state: 'running', + time: 1546494651235, + '@timestamp': 1546494651235, + username: 'jlawson2p', + percent_uptime: 0.59, + }, + { + age: 54, + cost: 36.65, + country: 'TZ', + price: 72, + project: 'apm', + state: 'done', + time: 1546498431195, + '@timestamp': 1546498431195, + username: 'aking2c', + percent_uptime: 1, + }, + ], +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/index.ts b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/index.ts new file mode 100644 index 0000000000000..10d31b77d6973 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/shared/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { data } from './data'; +export { config, pieConfig, treemapMosaicConfig, waffleConfig } from './config'; +export { + argTypes, + pieDonutArgTypes, + treemapArgTypes, + mosaicArgTypes, + waffleArgTypes, +} from './arg_types'; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/treemap_vis_renderer.stories.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/treemap_vis_renderer.stories.tsx new file mode 100644 index 0000000000000..a8f9010ade4ab --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/treemap_vis_renderer.stories.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { ComponentStory } from '@storybook/react'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { getPartitionVisRenderer } from '../expression_renderers'; +import { ChartTypes, RenderValue } from '../../common/types'; +import { palettes, theme, getStartDeps } from '../__mocks__'; +import { treemapArgTypes, treemapMosaicConfig, data } from './shared'; + +const containerSize = { + width: '700px', + height: '700px', +}; + +const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps }); + +type Props = { + visType: RenderValue['visType']; + syncColors: RenderValue['syncColors']; +} & RenderValue['visConfig']; + +const PartitionVis: ComponentStory> = ({ + visType, + syncColors, + children, + ...visConfig +}) => ( + +); + +export default { + title: 'renderers/treemapVis', + component: PartitionVis, + argTypes: treemapArgTypes, +}; + +const Default = PartitionVis.bind({}); +Default.args = { ...treemapMosaicConfig, visType: ChartTypes.TREEMAP, syncColors: false }; + +export { Default }; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/waffle_vis_renderer.stories.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/waffle_vis_renderer.stories.tsx new file mode 100644 index 0000000000000..a97efdabef892 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/__stories__/waffle_vis_renderer.stories.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { FC } from 'react'; +import { ComponentStory } from '@storybook/react'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { getPartitionVisRenderer } from '../expression_renderers'; +import { ChartTypes, RenderValue } from '../../common/types'; +import { palettes, theme, getStartDeps } from '../__mocks__'; +import { waffleArgTypes, waffleConfig, data } from './shared'; + +const containerSize = { + width: '700px', + height: '700px', +}; + +const PartitionVisRenderer = () => getPartitionVisRenderer({ palettes, theme, getStartDeps }); + +type Props = { + visType: RenderValue['visType']; + syncColors: RenderValue['syncColors']; +} & RenderValue['visConfig']; + +const PartitionVis: ComponentStory> = ({ + visType, + syncColors, + children, + ...visConfig +}) => ( + +); + +export default { + title: 'renderers/waffleVis', + component: PartitionVis, + argTypes: waffleArgTypes, +}; + +const Default = PartitionVis.bind({}); +Default.args = { ...waffleConfig, visType: ChartTypes.WAFFLE, syncColors: false }; + +export { Default }; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap new file mode 100644 index 0000000000000..4e56d2c5efa4c --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/__snapshots__/partition_vis_component.test.tsx.snap @@ -0,0 +1,1993 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PartitionVisComponent should render correct structure for donut 1`] = ` +
+
+ + + + + + + + +
+
+`; + +exports[`PartitionVisComponent should render correct structure for mosaic 1`] = ` +
+
+ + + + + + + + +
+
+`; + +exports[`PartitionVisComponent should render correct structure for pie 1`] = ` +
+
+ + + + + + + + +
+
+`; + +exports[`PartitionVisComponent should render correct structure for treemap 1`] = ` +
+
+ + + + + + + + +
+
+`; + +exports[`PartitionVisComponent should render correct structure for waffle 1`] = ` +
+
+ + + + + + + + +
+
+`; diff --git a/src/plugins/chart_expressions/expression_pie/public/components/chart_split.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/chart_split.tsx similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/components/chart_split.tsx rename to src/plugins/chart_expressions/expression_partition_vis/public/components/chart_split.tsx diff --git a/packages/kbn-test/src/jest/index.ts b/src/plugins/chart_expressions/expression_partition_vis/public/components/index.ts similarity index 89% rename from packages/kbn-test/src/jest/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/components/index.ts index a50ff5c59e798..14a49bafb689c 100644 --- a/packages/kbn-test/src/jest/index.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export * from './utils'; +export * from './partition_vis_component'; diff --git a/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.styles.ts b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.styles.ts similarity index 78% rename from src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.styles.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.styles.ts index 678bdd1a73c8e..b713b3b22964a 100644 --- a/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.styles.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.styles.ts @@ -9,15 +9,15 @@ import { EuiThemeComputed } from '@elastic/eui'; import { css } from '@emotion/react'; -export const pieChartWrapperStyle = css({ +export const partitionVisWrapperStyle = css({ display: 'flex', flex: '1 1 auto', minHeight: 0, minWidth: 0, }); -export const pieChartContainerStyleFactory = (theme: EuiThemeComputed) => css` - ${pieChartWrapperStyle}; +export const partitionVisContainerStyleFactory = (theme: EuiThemeComputed) => css` + ${partitionVisWrapperStyle}; position: absolute; top: 0; @@ -27,4 +27,5 @@ export const pieChartContainerStyleFactory = (theme: EuiThemeComputed) => css` padding: ${theme.size.s}; margin-left: auto; margin-right: auto; + overflow: hidden; `; diff --git a/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.test.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx similarity index 65% rename from src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.test.tsx rename to src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx index 0e0a26483ef6d..ddade06c2c7e0 100644 --- a/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.test.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.test.tsx @@ -15,8 +15,15 @@ import type { Datatable } from '../../../../expressions/public'; import { shallow, mount } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { act } from 'react-dom/test-utils'; -import PieComponent, { PieComponentProps } from './pie_vis_component'; -import { createMockPieParams, createMockVisData } from '../mocks'; +import PartitionVisComponent, { PartitionVisComponentProps } from './partition_vis_component'; +import { + createMockDonutParams, + createMockPieParams, + createMockTreemapMosaicParams, + createMockVisData, + createMockWaffleParams, +} from '../mocks'; +import { ChartTypes } from '../../common/types'; jest.mock('@elastic/charts', () => { const original = jest.requireActual('@elastic/charts'); @@ -42,8 +49,8 @@ const uiState = { setSilent: jest.fn(), } as any; -describe('PieComponent', function () { - let wrapperProps: PieComponentProps; +describe('PartitionVisComponent', function () { + let wrapperProps: PartitionVisComponentProps; beforeAll(() => { wrapperProps = { @@ -51,6 +58,7 @@ describe('PieComponent', function () { palettesRegistry, visParams, visData, + visType: ChartTypes.PIE, uiState, syncColors: false, fireEvent: jest.fn(), @@ -62,20 +70,81 @@ describe('PieComponent', function () { }; }); + it('should render correct structure for pie', function () { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should render correct structure for donut', function () { + const donutVisParams = createMockDonutParams(); + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + + it('should render correct structure for treemap', function () { + const treemapVisParams = createMockTreemapMosaicParams(); + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + + it('should render correct structure for mosaic', function () { + const mosaicVisParams = createMockTreemapMosaicParams(); + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + + it('should render correct structure for waffle', function () { + const waffleVisParams = createMockWaffleParams(); + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + }); + it('renders the legend on the correct position', () => { - const component = shallow(); + const component = shallow(); expect(component.find(Settings).prop('legendPosition')).toEqual('right'); }); it('renders the legend toggle component', async () => { - const component = mount(); + const component = mount(); await act(async () => { expect(findTestSubject(component, 'vislibToggleLegend').length).toBe(1); }); }); it('hides the legend if the legend toggle is clicked', async () => { - const component = mount(); + const component = mount(); findTestSubject(component, 'vislibToggleLegend').simulate('click'); await act(async () => { expect(component.find(Settings).prop('showLegend')).toEqual(false); @@ -83,31 +152,31 @@ describe('PieComponent', function () { }); it('defaults on showing the legend for the inner cicle', () => { - const component = shallow(); + const component = shallow(); expect(component.find(Settings).prop('legendMaxDepth')).toBe(1); }); it('shows the nested legend when the user requests it', () => { const newParams = { ...visParams, nestedLegend: true }; const newProps = { ...wrapperProps, visParams: newParams }; - const component = shallow(); + const component = shallow(); expect(component.find(Settings).prop('legendMaxDepth')).toBeUndefined(); }); it('defaults on displaying the tooltip', () => { - const component = shallow(); + const component = shallow(); expect(component.find(Settings).prop('tooltip')).toStrictEqual({ type: TooltipType.Follow }); }); it('doesnt show the tooltip when the user requests it', () => { const newParams = { ...visParams, addTooltip: false }; const newProps = { ...wrapperProps, visParams: newParams }; - const component = shallow(); + const component = shallow(); expect(component.find(Settings).prop('tooltip')).toStrictEqual({ type: TooltipType.None }); }); it('calls filter callback', () => { - const component = shallow(); + const component = shallow(); component.find(Settings).first().prop('onElementClick')!([ [ [ @@ -130,14 +199,14 @@ describe('PieComponent', function () { const newVisData = { type: 'datatable', columns: [ - { - id: 'col-1-1', - name: 'Count', - }, { id: 'col-0-2', name: 'filters', }, + { + id: 'col-1-1', + name: 'Count', + }, ], rows: [ { @@ -151,7 +220,7 @@ describe('PieComponent', function () { ], } as unknown as Datatable; const newProps = { ...wrapperProps, visData: newVisData }; - const component = mount(); + const component = mount(); expect(findTestSubject(component, 'pieVisualizationError').text()).toEqual('No results found'); }); @@ -159,14 +228,14 @@ describe('PieComponent', function () { const newVisData = { type: 'datatable', columns: [ - { - id: 'col-1-1', - name: 'Count', - }, { id: 'col-0-2', name: 'filters', }, + { + id: 'col-1-1', + name: 'Count', + }, ], rows: [ { @@ -180,7 +249,7 @@ describe('PieComponent', function () { ], } as unknown as Datatable; const newProps = { ...wrapperProps, visData: newVisData }; - const component = mount(); + const component = mount(); expect(findTestSubject(component, 'pieVisualizationError').text()).toEqual( "Pie/donut charts can't render with negative values." ); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx new file mode 100644 index 0000000000000..834a0c9c9547b --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/partition_vis_component.tsx @@ -0,0 +1,438 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { memo, useCallback, useMemo, useState, useEffect, useRef } from 'react'; +import { + Chart, + Datum, + LayerValue, + Partition, + Position, + Settings, + RenderChangeListener, + TooltipProps, + TooltipType, + SeriesIdentifier, +} from '@elastic/charts'; +import { useEuiTheme } from '@elastic/eui'; +import { + LegendToggle, + ClickTriggerEvent, + ChartsPluginSetup, + PaletteRegistry, +} from '../../../../charts/public'; +import type { PersistedState } from '../../../../visualizations/public'; +import { + Datatable, + DatatableColumn, + IInterpreterRenderHandlers, +} from '../../../../expressions/public'; +import type { FieldFormat } from '../../../../field_formats/common'; +import { DEFAULT_PERCENT_DECIMALS } from '../../common/constants'; +import { + PartitionVisParams, + BucketColumns, + ValueFormats, + PieContainerDimensions, +} from '../../common/types/expression_renderers'; +import { + LegendColorPickerWrapper, + LegendColorPickerWrapperContext, + getLayers, + getLegendActions, + canFilter, + getFilterClickData, + getFilterEventData, + getPartitionTheme, + getColumns, + getSplitDimensionAccessor, + getColumnByAccessor, + isLegendFlat, + shouldShowLegend, + generateFormatters, + getFormatter, + getPartitionType, +} from '../utils'; +import { ChartSplit, SMALL_MULTIPLES_ID } from './chart_split'; +import { VisualizationNoResults } from './visualization_noresults'; +import { VisTypePiePluginStartDependencies } from '../plugin'; +import { + partitionVisWrapperStyle, + partitionVisContainerStyleFactory, +} from './partition_vis_component.styles'; +import { ChartTypes } from '../../common/types'; +import { filterOutConfig } from '../utils/filter_out_config'; + +declare global { + interface Window { + /** + * Flag used to enable debugState on elastic charts + */ + _echDebugStateFlag?: boolean; + } +} +export interface PartitionVisComponentProps { + visParams: PartitionVisParams; + visData: Datatable; + visType: ChartTypes; + uiState: PersistedState; + fireEvent: IInterpreterRenderHandlers['event']; + renderComplete: IInterpreterRenderHandlers['done']; + chartsThemeService: ChartsPluginSetup['theme']; + palettesRegistry: PaletteRegistry; + services: VisTypePiePluginStartDependencies; + syncColors: boolean; +} + +const PartitionVisComponent = (props: PartitionVisComponentProps) => { + const { visData, visParams: preVisParams, visType, services, syncColors } = props; + const visParams = useMemo(() => filterOutConfig(visType, preVisParams), [preVisParams, visType]); + + const theme = useEuiTheme(); + const chartTheme = props.chartsThemeService.useChartsTheme(); + const chartBaseTheme = props.chartsThemeService.useChartsBaseTheme(); + + const { bucketColumns, metricColumn } = useMemo( + () => getColumns(props.visParams, props.visData), + [props.visData, props.visParams] + ); + + const formatters = useMemo( + () => generateFormatters(visParams, visData, services.fieldFormats.deserialize), + [services.fieldFormats.deserialize, visData, visParams] + ); + + const showLegendDefault = useCallback(() => { + const showLegendDef = shouldShowLegend(visType, visParams.legendDisplay, bucketColumns); + return props.uiState?.get('vis.legendOpen', showLegendDef) ?? showLegendDef; + }, [bucketColumns, props.uiState, visParams.legendDisplay, visType]); + + const [showLegend, setShowLegend] = useState(() => showLegendDefault()); + + const [dimensions, setDimensions] = useState(); + + const parentRef = useRef(null); + + useEffect(() => { + if (parentRef && parentRef.current) { + const parentHeight = parentRef.current!.getBoundingClientRect().height; + const parentWidth = parentRef.current!.getBoundingClientRect().width; + setDimensions({ width: parentWidth, height: parentHeight }); + } + }, [parentRef]); + + useEffect(() => { + const legendShow = showLegendDefault(); + setShowLegend(legendShow); + props.uiState?.set('vis.legendOpen', legendShow); + }, [showLegendDefault, props.uiState]); + + const onRenderChange = useCallback( + (isRendered) => { + if (isRendered) { + props.renderComplete(); + } + }, + [props] + ); + + // handles slice click event + const handleSliceClick = useCallback( + ( + clickedLayers: LayerValue[], + buckets: Array>, + vData: Datatable, + splitChartDimension?: DatatableColumn, + splitChartFormatter?: FieldFormat + ): void => { + const data = getFilterClickData( + clickedLayers, + buckets, + vData, + splitChartDimension, + splitChartFormatter + ); + const event = { + name: 'filterBucket', + data: { data }, + }; + props.fireEvent(event); + }, + [props] + ); + + // handles legend action event data + const getLegendActionEventData = useCallback( + (vData: Datatable) => + (series: SeriesIdentifier): ClickTriggerEvent | null => { + const data = getFilterEventData(vData, series); + + return { + name: 'filterBucket', + data: { + negate: false, + data, + }, + }; + }, + [] + ); + + const handleLegendAction = useCallback( + (event: ClickTriggerEvent, negate = false) => { + props.fireEvent({ + ...event, + data: { + ...event.data, + negate, + }, + }); + }, + [props] + ); + + const toggleLegend = useCallback(() => { + setShowLegend((value) => { + const newValue = !value; + props.uiState?.set('vis.legendOpen', newValue); + return newValue; + }); + }, [props.uiState]); + + const setColor = useCallback( + (newColor: string | null, seriesLabel: string | number) => { + const colors = props.uiState?.get('vis.colors') || {}; + if (colors[seriesLabel] === newColor || !newColor) { + delete colors[seriesLabel]; + } else { + colors[seriesLabel] = newColor; + } + props.uiState?.setSilent('vis.colors', null); + props.uiState?.set('vis.colors', colors); + props.uiState?.emit('reload'); + }, + [props.uiState] + ); + + const getSliceValue = useCallback((d: Datum, metric: DatatableColumn) => { + const value = d[metric.id]; + return Number.isFinite(value) && value >= 0 ? value : 0; + }, []); + + const defaultFormatter = services.fieldFormats.deserialize; + // formatters + const metricFieldFormatter = getFormatter(metricColumn, formatters, defaultFormatter); + const { splitColumn, splitRow } = visParams.dimensions; + + const splitChartFormatter = splitColumn + ? getFormatter(splitColumn[0], formatters, defaultFormatter) + : splitRow + ? getFormatter(splitRow[0], formatters, defaultFormatter) + : undefined; + + const percentFormatter = services.fieldFormats.deserialize({ + id: 'percent', + params: { + pattern: `0,0.[${'0'.repeat(visParams.labels.percentDecimals ?? DEFAULT_PERCENT_DECIMALS)}]%`, + }, + }); + + const isDarkMode = props.chartsThemeService.useDarkMode(); + const layers = useMemo( + () => + getLayers( + visType, + bucketColumns, + visParams, + visData, + props.uiState?.get('vis.colors', {}), + visData.rows, + props.palettesRegistry, + formatters, + services.fieldFormats, + syncColors, + isDarkMode + ), + [ + visType, + bucketColumns, + visParams, + visData, + props.uiState, + props.palettesRegistry, + formatters, + services.fieldFormats, + syncColors, + isDarkMode, + ] + ); + + const rescaleFactor = useMemo(() => { + const overallSum = visData.rows.reduce((sum, row) => sum + row[metricColumn.id], 0); + const slices = visData.rows.map((row) => row[metricColumn.id] / overallSum); + const smallSlices = slices.filter((value) => value < 0.02) ?? []; + if (smallSlices.length) { + // shrink up to 20% to give some room for the linked values + return 1 / (1 + Math.min(smallSlices.length * 0.05, 0.2)); + } + return 1; + }, [visData.rows, metricColumn]); + + const themeOverrides = useMemo( + () => getPartitionTheme(visType, visParams, chartTheme, dimensions, rescaleFactor), + [visType, visParams, chartTheme, dimensions, rescaleFactor] + ); + + const fixedViewPort = document.getElementById('app-fixed-viewport'); + const tooltip: TooltipProps = { + ...(fixedViewPort ? { boundary: fixedViewPort } : {}), + type: visParams.addTooltip ? TooltipType.Follow : TooltipType.None, + }; + const legendPosition = visParams.legendPosition ?? Position.Right; + + const splitChartColumnAccessor = splitColumn + ? getSplitDimensionAccessor(visData.columns, splitColumn[0], formatters, defaultFormatter) + : undefined; + + const splitChartRowAccessor = splitRow + ? getSplitDimensionAccessor(visData.columns, splitRow[0], formatters, defaultFormatter) + : undefined; + + const splitChartDimension = splitColumn + ? getColumnByAccessor(splitColumn[0].accessor, visData.columns) + : splitRow + ? getColumnByAccessor(splitRow[0].accessor, visData.columns) + : undefined; + + /** + * Checks whether data have all zero values. + * If so, the no data container is loaded. + */ + const isAllZeros = useMemo( + () => visData.rows.every((row) => row[metricColumn.id] === 0), + [visData.rows, metricColumn] + ); + + /** + * Checks whether data have negative values. + * If so, the no data container is loaded. + */ + const hasNegative = useMemo( + () => + visData.rows.some((row) => { + const value = row[metricColumn.id]; + return typeof value === 'number' && value < 0; + }), + [visData.rows, metricColumn] + ); + const flatLegend = isLegendFlat(visType, splitChartDimension); + const canShowPieChart = !isAllZeros && !hasNegative; + const partitionType = getPartitionType(visType); + + return ( +
+ {!canShowPieChart ? ( + + ) : ( +
+ + + + + { + handleSliceClick( + args[0][0] as LayerValue[], + bucketColumns, + visData, + splitChartDimension, + splitChartFormatter + ); + }} + legendAction={getLegendActions( + canFilter, + getLegendActionEventData(visData), + handleLegendAction, + visParams, + services.data.actions, + services.fieldFormats + )} + theme={[ + // Chart background should be transparent for the usage at Canvas. + { background: { color: 'transparent' } }, + themeOverrides, + chartTheme, + { + legend: { + labelOptions: { + maxLines: visParams.truncateLegend ? visParams.maxLegendLines ?? 1 : 0, + }, + }, + }, + ]} + baseTheme={chartBaseTheme} + onRenderChange={onRenderChange} + /> + getSliceValue(d, metricColumn)} + percentFormatter={(d: number) => percentFormatter.convert(d / 100)} + valueGetter={ + !visParams.labels.show || + visParams.labels.valuesFormat === ValueFormats.VALUE || + !visParams.labels.values + ? undefined + : ValueFormats.PERCENT + } + valueFormatter={(d: number) => + !visParams.labels.show || !visParams.labels.values + ? '' + : metricFieldFormatter.convert(d) + } + layers={layers} + topGroove={!visParams.labels.show ? 0 : undefined} + /> + + +
+ )} +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export default memo(PartitionVisComponent); diff --git a/src/plugins/chart_expressions/expression_pie/public/components/visualization_noresults.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/components/visualization_noresults.tsx similarity index 86% rename from src/plugins/chart_expressions/expression_pie/public/components/visualization_noresults.tsx rename to src/plugins/chart_expressions/expression_partition_vis/public/components/visualization_noresults.tsx index 46478556f5f9b..8362d17920bdd 100644 --- a/src/plugins/chart_expressions/expression_pie/public/components/visualization_noresults.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/components/visualization_noresults.tsx @@ -19,10 +19,10 @@ export const VisualizationNoResults = ({ hasNegativeValues = false }) => { body={ {hasNegativeValues - ? i18n.translate('expressionPie.negativeValuesFound', { + ? i18n.translate('expressionPartitionVis.negativeValuesFound', { defaultMessage: "Pie/donut charts can't render with negative values.", }) - : i18n.translate('expressionPie.noResultsFoundTitle', { + : i18n.translate('expressionPartitionVis.noResultsFoundTitle', { defaultMessage: 'No results found', })} diff --git a/src/plugins/chart_expressions/expression_pie/public/expression_renderers/index.ts b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/index.ts similarity index 83% rename from src/plugins/chart_expressions/expression_pie/public/expression_renderers/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/index.ts index 3f370b63b4579..17a103370e9f4 100644 --- a/src/plugins/chart_expressions/expression_pie/public/expression_renderers/index.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { getPieVisRenderer } from './pie_vis_renderer'; +export { getPartitionVisRenderer } from './partition_vis_renderer'; diff --git a/src/plugins/chart_expressions/expression_pie/public/expression_renderers/pie_vis_renderer.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx similarity index 79% rename from src/plugins/chart_expressions/expression_pie/public/expression_renderers/pie_vis_renderer.tsx rename to src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx index b65e8696f7a57..c3521c7346a81 100644 --- a/src/plugins/chart_expressions/expression_pie/public/expression_renderers/pie_vis_renderer.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/expression_renderers/partition_vis_renderer.tsx @@ -15,23 +15,23 @@ import { VisualizationContainer } from '../../../../visualizations/public'; import type { PersistedState } from '../../../../visualizations/public'; import { KibanaThemeProvider } from '../../../../kibana_react/public'; -import { PIE_VIS_EXPRESSION_NAME } from '../../common/constants'; -import { RenderValue } from '../../common/types'; +import { PARTITION_VIS_RENDERER_NAME } from '../../common/constants'; +import { ChartTypes, RenderValue } from '../../common/types'; import { VisTypePieDependencies } from '../plugin'; export const strings = { getDisplayName: () => - i18n.translate('expressionPie.renderer.pieVis.displayName', { + i18n.translate('expressionPartitionVis.renderer.pieVis.displayName', { defaultMessage: 'Pie visualization', }), getHelpDescription: () => - i18n.translate('expressionPie.renderer.pieVis.helpDescription', { + i18n.translate('expressionPartitionVis.renderer.pieVis.helpDescription', { defaultMessage: 'Render a pie', }), }; -const PieComponent = lazy(() => import('../components/pie_vis_component')); +const PartitionVisComponent = lazy(() => import('../components/partition_vis_component')); function shouldShowNoResultsMessage(visData: Datatable | undefined): boolean { const rows: object[] | undefined = visData?.rows; @@ -40,14 +40,14 @@ function shouldShowNoResultsMessage(visData: Datatable | undefined): boolean { return Boolean(isZeroHits); } -export const getPieVisRenderer: ( +export const getPartitionVisRenderer: ( deps: VisTypePieDependencies ) => ExpressionRenderDefinition = ({ theme, palettes, getStartDeps }) => ({ - name: PIE_VIS_EXPRESSION_NAME, + name: PARTITION_VIS_RENDERER_NAME, displayName: strings.getDisplayName(), help: strings.getHelpDescription(), reuseDomNode: true, - render: async (domNode, { visConfig, visData, syncColors }, handlers) => { + render: async (domNode, { visConfig, visData, visType, syncColors }, handlers) => { const showNoResult = shouldShowNoResultsMessage(visData); handlers.onDestroy(() => { @@ -61,11 +61,12 @@ export const getPieVisRenderer: ( - { return [ @@ -107,50 +108,50 @@ export const createMockVisData = (): Datatable => { rows: [ { 'col-0-2': 'Logstash Airways', - 'col-2-3': 0, 'col-1-1': 797, + 'col-2-3': 0, 'col-3-1': 689, }, { 'col-0-2': 'Logstash Airways', - 'col-2-3': 1, 'col-1-1': 797, + 'col-2-3': 1, 'col-3-1': 108, }, { 'col-0-2': 'JetBeats', - 'col-2-3': 0, 'col-1-1': 766, + 'col-2-3': 0, 'col-3-1': 654, }, { 'col-0-2': 'JetBeats', - 'col-2-3': 1, 'col-1-1': 766, + 'col-2-3': 1, 'col-3-1': 112, }, { 'col-0-2': 'ES-Air', - 'col-2-3': 0, 'col-1-1': 744, + 'col-2-3': 0, 'col-3-1': 665, }, { 'col-0-2': 'ES-Air', - 'col-2-3': 1, 'col-1-1': 744, + 'col-2-3': 1, 'col-3-1': 79, }, { 'col-0-2': 'Kibana Airlines', - 'col-2-3': 0, 'col-1-1': 731, + 'col-2-3': 0, 'col-3-1': 655, }, { 'col-0-2': 'Kibana Airlines', - 'col-2-3': 1, 'col-1-1': 731, + 'col-2-3': 1, 'col-3-1': 76, }, ], @@ -269,9 +270,9 @@ export const createMockVisData = (): Datatable => { }; }; -export const createMockPieParams = (): PieVisParams => { +export const createMockPartitionVisParams = (): PartitionVisParams => { return { - addLegend: true, + legendDisplay: LegendDisplay.SHOW, addTooltip: true, isDonut: true, labels: { @@ -291,19 +292,20 @@ export const createMockPieParams = (): PieVisParams => { name: 'default', type: 'palette', }, - type: 'pie', dimensions: { metric: { + type: 'vis_dimension', accessor: 1, format: { id: 'number', + params: { + id: 'number', + }, }, - params: {}, - label: 'Count', - aggType: 'count', }, buckets: [ { + type: 'vis_dimension', accessor: 0, format: { id: 'terms', @@ -313,10 +315,9 @@ export const createMockPieParams = (): PieVisParams => { missingBucketLabel: 'Missing', }, }, - label: 'Carrier: Descending', - aggType: 'terms', }, { + type: 'vis_dimension', accessor: 2, format: { id: 'terms', @@ -326,10 +327,56 @@ export const createMockPieParams = (): PieVisParams => { missingBucketLabel: 'Missing', }, }, - label: 'Cancelled: Descending', - aggType: 'terms', }, ], }, - } as unknown as PieVisParams; + }; +}; + +export const createMockPieParams = (): PartitionVisParams => { + return { + ...createMockPartitionVisParams(), + isDonut: false, + distinctColors: false, + }; +}; + +export const createMockDonutParams = (): PartitionVisParams => { + return { + ...createMockPartitionVisParams(), + isDonut: true, + emptySizeRatio: 0.3, + }; +}; + +export const createMockTreemapMosaicParams = (): PartitionVisParams => { + return { + ...createMockPartitionVisParams(), + nestedLegend: true, + }; +}; + +export const createMockWaffleParams = (): PartitionVisParams => { + const visParams = createMockPartitionVisParams(); + return { + ...visParams, + dimensions: { + ...visParams.dimensions, + buckets: [ + { + type: 'vis_dimension', + accessor: 0, + format: { + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: 'Missing', + }, + }, + }, + ], + }, + showValuesInLegend: true, + }; }; diff --git a/src/plugins/chart_expressions/expression_pie/public/plugin.ts b/src/plugins/chart_expressions/expression_partition_vis/public/plugin.ts similarity index 64% rename from src/plugins/chart_expressions/expression_pie/public/plugin.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/plugin.ts index 2c141027c65fb..3bc3cdb31f9b1 100755 --- a/src/plugins/chart_expressions/expression_pie/public/plugin.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/plugin.ts @@ -10,9 +10,20 @@ import { FieldFormatsStart } from '../../../field_formats/public'; import { CoreSetup, CoreStart, ThemeServiceStart } from '../../../../core/public'; import { ChartsPluginSetup } from '../../../charts/public'; import { DataPublicPluginStart } from '../../../data/public'; -import { pieLabelsFunction, pieVisFunction } from '../common'; -import { getPieVisRenderer } from './expression_renderers'; -import { ExpressionPiePluginSetup, ExpressionPiePluginStart, SetupDeps, StartDeps } from './types'; +import { + partitionLabelsFunction, + pieVisFunction, + treemapVisFunction, + mosaicVisFunction, + waffleVisFunction, +} from '../common'; +import { getPartitionVisRenderer } from './expression_renderers'; +import { + ExpressionPartitionVisPluginSetup, + ExpressionPartitionVisPluginStart, + SetupDeps, + StartDeps, +} from './types'; /** @internal */ export interface VisTypePieDependencies { @@ -30,13 +41,16 @@ export interface VisTypePiePluginStartDependencies { fieldFormats: FieldFormatsStart; } -export class ExpressionPiePlugin { +export class ExpressionPartitionVisPlugin { public setup( core: CoreSetup, { expressions, charts }: SetupDeps - ): ExpressionPiePluginSetup { - expressions.registerFunction(pieLabelsFunction); + ): ExpressionPartitionVisPluginSetup { + expressions.registerFunction(partitionLabelsFunction); expressions.registerFunction(pieVisFunction); + expressions.registerFunction(treemapVisFunction); + expressions.registerFunction(mosaicVisFunction); + expressions.registerFunction(waffleVisFunction); const getStartDeps = async () => { const [coreStart, deps] = await core.getStartServices(); @@ -46,11 +60,11 @@ export class ExpressionPiePlugin { }; expressions.registerRenderer( - getPieVisRenderer({ theme: charts.theme, palettes: charts.palettes, getStartDeps }) + getPartitionVisRenderer({ theme: charts.theme, palettes: charts.palettes, getStartDeps }) ); } - public start(core: CoreStart, deps: StartDeps): ExpressionPiePluginStart {} + public start(core: CoreStart, deps: StartDeps): ExpressionPartitionVisPluginStart {} public stop() {} } diff --git a/src/plugins/chart_expressions/expression_pie/public/types.ts b/src/plugins/chart_expressions/expression_partition_vis/public/types.ts similarity index 86% rename from src/plugins/chart_expressions/expression_pie/public/types.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/types.ts index 32f2e83bad512..64e132d2ddadb 100755 --- a/src/plugins/chart_expressions/expression_pie/public/types.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/types.ts @@ -8,8 +8,8 @@ import { ChartsPluginSetup } from '../../../charts/public'; import { ExpressionsPublicPlugin, ExpressionsServiceStart } from '../../../expressions/public'; -export type ExpressionPiePluginSetup = void; -export type ExpressionPiePluginStart = void; +export type ExpressionPartitionVisPluginSetup = void; +export type ExpressionPartitionVisPluginStart = void; export interface SetupDeps { expressions: ReturnType; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/accessor.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/accessor.test.ts new file mode 100644 index 0000000000000..f1023d478d40e --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/accessor.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { createMockVisData } from '../mocks'; +import { getColumnByAccessor } from './accessor'; + +const visData = createMockVisData(); + +describe('getColumnByAccessor', () => { + it('returns column by the index', () => { + const index = 1; + const column = getColumnByAccessor(index, visData.columns); + expect(column).toEqual(visData.columns[index]); + }); + + it('returns undefiend if the index is higher then amount of columns', () => { + const index = visData.columns.length; + const column = getColumnByAccessor(index, visData.columns); + expect(column).toBeUndefined(); + }); + + it('returns column by id', () => { + const column = visData.columns[1]; + const accessor: ExpressionValueVisDimension['accessor'] = { + id: column.id, + name: '', + meta: { type: column.meta.type }, + }; + + const foundColumn = getColumnByAccessor(accessor, visData.columns); + expect(foundColumn).toEqual(column); + }); + + it('returns undefined for the accessor to non-existent column', () => { + const accessor: ExpressionValueVisDimension['accessor'] = { + id: 'non-existent-column', + name: '', + meta: { type: 'number' }, + }; + + const column = getColumnByAccessor(accessor, visData.columns); + expect(column).toBeUndefined(); + }); +}); diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/accessor.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/accessor.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/utils/accessor.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/accessor.ts diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/filter_helpers.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_helpers.test.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/utils/filter_helpers.test.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_helpers.test.ts diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/filter_helpers.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_helpers.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/utils/filter_helpers.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_helpers.ts diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_out_config.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_out_config.test.ts new file mode 100644 index 0000000000000..eec33cac05e3a --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_out_config.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ChartTypes } from '../../common/types'; +import { createMockDonutParams, createMockPieParams } from '../mocks'; +import { filterOutConfig } from './filter_out_config'; + +describe('filterOutConfig', () => { + const config = createMockPieParams(); + const { last_level: lastLevel, truncate, ...restLabels } = config.labels; + const configWithoutTruncateAndLastLevel = { ...config, labels: restLabels }; + + it('returns full configuration for pie visualization', () => { + const fullConfig = filterOutConfig(ChartTypes.PIE, config); + + expect(fullConfig).toEqual(config); + }); + + it('returns full configuration for donut visualization', () => { + const donutConfig = createMockDonutParams(); + const fullDonutConfig = filterOutConfig(ChartTypes.DONUT, donutConfig); + + expect(fullDonutConfig).toEqual(donutConfig); + }); + + it('excludes truncate and last_level from labels for treemap', () => { + const filteredOutConfig = filterOutConfig(ChartTypes.TREEMAP, config); + + expect(filteredOutConfig).toEqual(configWithoutTruncateAndLastLevel); + }); + + it('excludes truncate and last_level from labels for mosaic', () => { + const filteredOutConfig = filterOutConfig(ChartTypes.MOSAIC, config); + + expect(filteredOutConfig).toEqual(configWithoutTruncateAndLastLevel); + }); + + it('excludes truncate and last_level from labels for waffle', () => { + const filteredOutConfig = filterOutConfig(ChartTypes.WAFFLE, config); + + expect(filteredOutConfig).toEqual(configWithoutTruncateAndLastLevel); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_out_config.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_out_config.ts new file mode 100644 index 0000000000000..2b118cd0903c1 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/filter_out_config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PartitionVisParams, ChartTypes } from '../../common/types'; + +export const filterOutConfig = (visType: ChartTypes, visConfig: PartitionVisParams) => { + if ([ChartTypes.PIE, ChartTypes.DONUT].includes(visType)) { + return visConfig; + } + + const { last_level: lastLevel, truncate, ...restLabelsConfig } = visConfig.labels; + + return { + ...visConfig, + labels: restLabelsConfig, + }; +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/formatters.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/formatters.test.ts new file mode 100644 index 0000000000000..69443dcfea5fb --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/formatters.test.ts @@ -0,0 +1,186 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; +import { Datatable } from '../../../../expressions'; +import { createMockPieParams, createMockVisData } from '../mocks'; +import { generateFormatters, getAvailableFormatter, getFormatter } from './formatters'; +import { BucketColumns } from '../../common/types'; + +describe('generateFormatters', () => { + const visParams = createMockPieParams(); + const visData = createMockVisData(); + const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + beforeEach(() => { + defaultFormatter.mockClear(); + }); + + it('returns empty object, if labels should not be should ', () => { + const formatters = generateFormatters( + { ...visParams, labels: { ...visParams.labels, show: false } }, + visData, + defaultFormatter + ); + + expect(formatters).toEqual({}); + expect(defaultFormatter).toHaveBeenCalledTimes(0); + }); + + it('returns formatters, if columns have meta parameters', () => { + const formatters = generateFormatters(visParams, visData, defaultFormatter); + const formattingResult = fieldFormatsMock.deserialize(); + + const serializedFormatters = Object.keys(formatters).reduce( + (serialized, formatterId) => ({ + ...serialized, + [formatterId]: formatters[formatterId]?.toJSON(), + }), + {} + ); + + expect(serializedFormatters).toEqual({ + 'col-0-2': formattingResult.toJSON(), + 'col-1-1': formattingResult.toJSON(), + 'col-2-3': formattingResult.toJSON(), + 'col-3-1': formattingResult.toJSON(), + }); + + expect(defaultFormatter).toHaveBeenCalledTimes(visData.columns.length); + visData.columns.forEach((col) => { + expect(defaultFormatter).toHaveBeenCalledWith(col.meta.params); + }); + }); + + it('returns undefined formatters for columns without meta parameters', () => { + const newVisData: Datatable = { + ...visData, + columns: visData.columns.map(({ meta, ...col }) => ({ ...col, meta: { type: 'string' } })), + }; + + const formatters = generateFormatters(visParams, newVisData, defaultFormatter); + + expect(formatters).toEqual({ + 'col-0-2': undefined, + 'col-1-1': undefined, + 'col-2-3': undefined, + 'col-3-1': undefined, + }); + expect(defaultFormatter).toHaveBeenCalledTimes(0); + }); +}); + +describe('getAvailableFormatter', () => { + const visData = createMockVisData(); + + const preparedFormatter1 = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const preparedFormatter2 = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + + beforeEach(() => { + defaultFormatter.mockClear(); + preparedFormatter1.mockClear(); + preparedFormatter2.mockClear(); + }); + + const formatters: Record = { + [visData.columns[0].id]: preparedFormatter1(), + [visData.columns[1].id]: preparedFormatter2(), + }; + + it('returns formatter from formatters, if meta.params are present ', () => { + const formatter = getAvailableFormatter(visData.columns[1], formatters, defaultFormatter); + + expect(formatter).toEqual(formatters[visData.columns[1].id]); + expect(defaultFormatter).toHaveBeenCalledTimes(0); + }); + + it('returns formatter from defaultFormatter factory, if meta.params are not present and format is present at column', () => { + const column: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + format: { + id: 'string', + params: {}, + }, + }; + const formatter = getAvailableFormatter(column, formatters, defaultFormatter); + + expect(formatter).not.toBeNull(); + expect(typeof formatter).toBe('object'); + expect(defaultFormatter).toHaveBeenCalledTimes(1); + expect(defaultFormatter).toHaveBeenCalledWith(column.format); + }); + + it('returns undefined, if meta.params and format are not present', () => { + const column: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + }; + const formatter = getAvailableFormatter(column, formatters, defaultFormatter); + + expect(formatter).toBeUndefined(); + expect(defaultFormatter).toHaveBeenCalledTimes(0); + }); +}); + +describe('getFormatter', () => { + const visData = createMockVisData(); + + const preparedFormatter1 = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const preparedFormatter2 = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + + beforeEach(() => { + defaultFormatter.mockClear(); + preparedFormatter1.mockClear(); + preparedFormatter2.mockClear(); + }); + + const formatters: Record = { + [visData.columns[0].id]: preparedFormatter1(), + [visData.columns[1].id]: preparedFormatter2(), + }; + + it('returns formatter from formatters, if meta.params are present ', () => { + const formatter = getFormatter(visData.columns[1], formatters, defaultFormatter); + + expect(formatter).toEqual(formatters[visData.columns[1].id]); + expect(defaultFormatter).toHaveBeenCalledTimes(0); + }); + + it('returns formatter from defaultFormatter factory, if meta.params are not present and format is present at column', () => { + const column: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + format: { + id: 'string', + params: {}, + }, + }; + const formatter = getFormatter(column, formatters, defaultFormatter); + + expect(formatter).not.toBeNull(); + expect(typeof formatter).toBe('object'); + expect(defaultFormatter).toHaveBeenCalledTimes(1); + expect(defaultFormatter).toHaveBeenCalledWith(column.format); + }); + + it('returns defaultFormatter, if meta.params and format are not present', () => { + const column: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + }; + + const formatter = getFormatter(column, formatters, defaultFormatter); + + expect(formatter).not.toBeNull(); + expect(typeof formatter).toBe('object'); + expect(defaultFormatter).toHaveBeenCalledTimes(1); + expect(defaultFormatter).toHaveBeenCalledWith(); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/formatters.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/formatters.ts new file mode 100644 index 0000000000000..59574dd248518 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/formatters.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { FieldFormat, FormatFactory } from '../../../../field_formats/common'; +import type { Datatable } from '../../../../expressions/public'; +import { BucketColumns, PartitionVisParams } from '../../common/types'; + +export const generateFormatters = ( + visParams: PartitionVisParams, + visData: Datatable, + formatFactory: FormatFactory +) => { + if (!visParams.labels.show) { + return {}; + } + + return visData.columns.reduce | undefined>>( + (newFormatters, column) => ({ + ...newFormatters, + [column.id]: column?.meta?.params ? formatFactory(column.meta.params) : undefined, + }), + {} + ); +}; + +export const getAvailableFormatter = ( + column: Partial, + formatters: Record, + defaultFormatFactory: FormatFactory +) => { + if (column?.meta?.params) { + const formatter = column?.id ? formatters[column?.id] : undefined; + if (formatter) { + return formatter; + } + } + + if (column?.format) { + return defaultFormatFactory(column.format); + } +}; + +export const getFormatter = ( + column: Partial, + formatters: Record, + defaultFormatFactory: FormatFactory +) => getAvailableFormatter(column, formatters, defaultFormatFactory) ?? defaultFormatFactory(); diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_color_picker.test.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx similarity index 69% rename from src/plugins/chart_expressions/expression_pie/public/utils/get_color_picker.test.tsx rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx index c129fdd356a66..1a9c785ed6dae 100644 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_color_picker.test.tsx +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.test.tsx @@ -9,9 +9,13 @@ import React from 'react'; import { LegendColorPickerProps } from '@elastic/charts'; import { EuiPopover } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; -import { ComponentType, ReactWrapper } from 'enzyme'; -import { getColorPicker } from './get_color_picker'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { ReactWrapper } from 'enzyme'; +import { + LegendColorPickerWrapper, + LegendColorPickerWrapperContext, + LegendColorPickerWrapperContextType, +} from './get_color_picker'; import { ColorPicker } from '../../../../charts/public'; import type { PersistedState } from '../../../../visualizations/public'; import { createMockBucketColumns, createMockVisData } from '../mocks'; @@ -28,7 +32,7 @@ jest.mock('@elastic/charts', () => { }; }); -describe('getColorPicker', function () { +describe('LegendColorPickerWrapper', () => { const mockState = new Map(); const uiState = { get: jest @@ -40,15 +44,6 @@ describe('getColorPicker', function () { } as unknown as PersistedState; let wrapperProps: LegendColorPickerProps; - const Component: ComponentType = getColorPicker( - 'left', - jest.fn(), - bucketColumns, - 'default', - visData.rows, - uiState, - false - ); let wrapper: ReactWrapper; beforeAll(() => { @@ -66,50 +61,73 @@ describe('getColorPicker', function () { }; }); + const mount = ( + props: LegendColorPickerProps = wrapperProps, + context: LegendColorPickerWrapperContextType = { + legendPosition: 'left', + setColor: jest.fn(), + bucketColumns, + palette: 'default', + data: visData.rows, + uiState, + distinctColors: false, + } + ) => + mountWithIntl( + + + + ); + it('renders the color picker for default palette and inner layer', () => { - wrapper = mountWithIntl(); + wrapper = mount(); expect(wrapper.find(ColorPicker).length).toBe(1); }); it('renders the picker on the correct position', () => { - wrapper = mountWithIntl(); + wrapper = mount(); expect(wrapper.find(EuiPopover).prop('anchorPosition')).toEqual('rightCenter'); }); it('converts the color to the right hex and passes it to the color picker', () => { - wrapper = mountWithIntl(); + wrapper = mount(); expect(wrapper.find(ColorPicker).prop('color')).toEqual('#6dccb1'); }); it('doesnt render the picker for default palette and not inner layer', () => { - const newProps = { ...wrapperProps, seriesIdentifier: { key: '1', specId: 'pie' } }; - wrapper = mountWithIntl(); + wrapper = mount({ + ...wrapperProps, + seriesIdentifier: { key: '1', specId: 'pie' }, + } as LegendColorPickerProps); + expect(wrapper).toEqual({}); }); it('renders the color picker with the colorIsOverwritten prop set to false if color is not overwritten for the specific series', () => { - wrapper = mountWithIntl(); + wrapper = mount(); expect(wrapper.find(ColorPicker).prop('colorIsOverwritten')).toBe(false); }); it('renders the color picker with the colorIsOverwritten prop set to true if color is overwritten for the specific series', () => { uiState.set('vis.colors', { 'Logstash Airways': '#6092c0' }); - wrapper = mountWithIntl(); + wrapper = mount(); expect(wrapper.find(ColorPicker).prop('colorIsOverwritten')).toBe(true); }); it('renders the picker for kibana palette and not distinctColors', () => { - const LegacyPaletteComponent: ComponentType = getColorPicker( - 'left', - jest.fn(), - bucketColumns, - 'kibana_palette', - visData.rows, - uiState, - true + wrapper = mount( + { ...wrapperProps, seriesIdentifier: { key: '1', specId: 'pie' } } as LegendColorPickerProps, + { + legendPosition: 'left', + setColor: jest.fn(), + bucketColumns, + palette: 'kibana_palette', + data: visData.rows, + uiState, + distinctColors: true, + } ); - const newProps = { ...wrapperProps, seriesIdentifier: { key: '1', specId: 'pie' } }; - wrapper = mountWithIntl(); + expect(wrapper.find(ColorPicker).length).toBe(1); expect(wrapper.find(ColorPicker).prop('useLegacyColors')).toBe(true); }); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.tsx b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.tsx new file mode 100644 index 0000000000000..3f95f77c7ec6c --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_color_picker.tsx @@ -0,0 +1,137 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback, createContext, useContext } from 'react'; +import Color from 'color'; +import { LegendColorPicker, Position } from '@elastic/charts'; +import { PopoverAnchorPosition, EuiWrappingPopover, EuiOutsideClickDetector } from '@elastic/eui'; +import type { DatatableRow } from '../../../../expressions/public'; +import type { PersistedState } from '../../../../visualizations/public'; +import { ColorPicker } from '../../../../charts/public'; +import { BucketColumns } from '../../common/types'; + +const KEY_CODE_ENTER = 13; + +function getAnchorPosition(legendPosition: Position): PopoverAnchorPosition { + switch (legendPosition) { + case Position.Bottom: + return 'upCenter'; + case Position.Top: + return 'downCenter'; + case Position.Left: + return 'rightCenter'; + default: + return 'leftCenter'; + } +} + +function getLayerIndex( + seriesKey: string, + data: DatatableRow[], + layers: Array> +): number { + const row = data.find((d) => Object.keys(d).find((key) => d[key] === seriesKey)); + const bucketId = row && Object.keys(row).find((key) => row[key] === seriesKey); + return layers.findIndex((layer) => layer.id === bucketId) + 1; +} + +function isOnInnerLayer( + firstBucket: Partial, + data: DatatableRow[], + seriesKey: string +): DatatableRow | undefined { + return data.find((d) => firstBucket.id && d[firstBucket.id] === seriesKey); +} + +export interface LegendColorPickerWrapperContextType { + legendPosition: Position; + setColor: (newColor: string | null, seriesKey: string | number) => void; + bucketColumns: Array>; + palette: string; + data: DatatableRow[]; + uiState: PersistedState; + distinctColors: boolean; +} + +export const LegendColorPickerWrapperContext = createContext< + LegendColorPickerWrapperContextType | undefined +>(undefined); + +export const LegendColorPickerWrapper: LegendColorPicker = ({ + anchor, + color, + onClose, + onChange, + seriesIdentifiers: [seriesIdentifier], +}) => { + const seriesName = seriesIdentifier.key; + const colorPickerWrappingContext = useContext(LegendColorPickerWrapperContext); + + const handleOutsideClick = useCallback(() => { + onClose?.(); + }, [onClose]); + + if (!colorPickerWrappingContext) { + return null; + } + + const { legendPosition, setColor, bucketColumns, palette, data, uiState, distinctColors } = + colorPickerWrappingContext; + + const overwriteColors: Record = uiState?.get('vis.colors', {}) ?? {}; + const colorIsOverwritten = Object.keys(overwriteColors).includes(seriesName.toString()); + let keyDownEventOn = false; + + const handleChange = (newColor: string | null) => { + if (newColor) { + onChange(newColor); + } + setColor(newColor, seriesName); + // close the popover if no color is applied or the user has clicked a color + if (!newColor || !keyDownEventOn) { + onClose(); + } + }; + + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.keyCode === KEY_CODE_ENTER) { + onClose?.(); + } + keyDownEventOn = true; + }; + + if (!distinctColors) { + const enablePicker = isOnInnerLayer(bucketColumns[0], data, seriesName) || !bucketColumns[0].id; + if (!enablePicker) return null; + } + const hexColor = new Color(color).hex(); + return ( + + + + + + ); +}; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_columns.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_columns.test.ts similarity index 85% rename from src/plugins/chart_expressions/expression_pie/public/utils/get_columns.test.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/get_columns.test.ts index 57dc4367c7e1e..157336599a26e 100644 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_columns.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_columns.test.ts @@ -7,7 +7,12 @@ */ import { getColumns } from './get_columns'; -import { PieVisParams } from '../../common/types'; +import { + LabelPositions, + LegendDisplay, + PartitionVisParams, + ValueFormats, +} from '../../common/types'; import { createMockPieParams, createMockVisData } from '../mocks'; const visParams = createMockPieParams(); @@ -108,7 +113,16 @@ describe('getColumns', () => { }); it('should return the correct metric column if visParams returns dimensions', () => { - const { metricColumn } = getColumns(visParams, visData); + const { metricColumn } = getColumns( + { + ...visParams, + dimensions: { + ...visParams.dimensions, + metric: undefined, + }, + }, + visData + ); expect(metricColumn).toEqual({ id: 'col-3-1', meta: { @@ -130,39 +144,38 @@ describe('getColumns', () => { }); it('should return the first data column if no buckets specified', () => { - const visParamsOnlyMetric = { - addLegend: true, + const visParamsOnlyMetric: PartitionVisParams = { + legendDisplay: LegendDisplay.SHOW, addTooltip: true, - isDonut: true, labels: { - position: 'default', + position: LabelPositions.DEFAULT, show: true, truncate: 100, values: true, - valuesFormat: 'percent', + valuesFormat: ValueFormats.PERCENT, percentDecimals: 2, + last_level: false, }, legendPosition: 'right', nestedLegend: false, maxLegendLines: 1, truncateLegend: false, + distinctColors: false, palette: { name: 'default', type: 'palette', }, - type: 'pie', dimensions: { metric: { + type: 'vis_dimension', accessor: 1, format: { id: 'number', + params: {}, }, - params: {}, - label: 'Count', - aggType: 'count', }, }, - } as unknown as PieVisParams; + }; const { metricColumn } = getColumns(visParamsOnlyMetric, visData); expect(metricColumn).toEqual({ id: 'col-1-1', @@ -187,37 +200,39 @@ describe('getColumns', () => { }); it('should return an object with the name of the metric if no buckets specified', () => { - const visParamsOnlyMetric = { - addLegend: true, + const visParamsOnlyMetric: PartitionVisParams = { + legendDisplay: LegendDisplay.SHOW, addTooltip: true, isDonut: true, labels: { - position: 'default', + position: LabelPositions.DEFAULT, show: true, truncate: 100, values: true, - valuesFormat: 'percent', + valuesFormat: ValueFormats.PERCENT, percentDecimals: 2, + last_level: false, }, + truncateLegend: false, + maxLegendLines: 100, + distinctColors: false, legendPosition: 'right', nestedLegend: false, palette: { name: 'default', type: 'palette', }, - type: 'pie', dimensions: { metric: { + type: 'vis_dimension', accessor: 1, format: { id: 'number', + params: {}, }, - params: {}, - label: 'Count', - aggType: 'count', }, }, - } as unknown as PieVisParams; + }; const { bucketColumns, metricColumn } = getColumns(visParamsOnlyMetric, visData); expect(bucketColumns).toEqual([{ name: metricColumn.name }]); }); diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_columns.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_columns.ts similarity index 50% rename from src/plugins/chart_expressions/expression_pie/public/utils/get_columns.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/get_columns.ts index 2fce86f365ffa..063315e3aab94 100644 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_columns.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_columns.ts @@ -6,39 +6,43 @@ * Side Public License, v 1. */ -import { getColumnByAccessor } from './accessor'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; import { DatatableColumn, Datatable } from '../../../../expressions/public'; -import { BucketColumns, PieVisParams } from '../../common/types'; +import { BucketColumns, PartitionVisParams } from '../../common/types'; +import { getColumnByAccessor } from './accessor'; + +const getMetricColumn = ( + metricAccessor: ExpressionValueVisDimension['accessor'], + visData: Datatable +) => { + return getColumnByAccessor(metricAccessor, visData.columns); +}; export const getColumns = ( - visParams: PieVisParams, + visParams: PartitionVisParams, visData: Datatable ): { metricColumn: DatatableColumn; bucketColumns: Array>; } => { - if (visParams.dimensions.buckets && visParams.dimensions.buckets.length > 0) { - const bucketColumns: Array> = visParams.dimensions.buckets.map( - ({ accessor, format }) => ({ - ...getColumnByAccessor(accessor, visData.columns), - format, - }) - ); + const { metric, buckets } = visParams.dimensions; + if (buckets && buckets.length > 0) { + const bucketColumns: Array> = buckets.map(({ accessor, format }) => ({ + ...getColumnByAccessor(accessor, visData.columns), + format, + })); + const lastBucketId = bucketColumns[bucketColumns.length - 1].id; const matchingIndex = visData.columns.findIndex((col) => col.id === lastBucketId); + return { bucketColumns, - metricColumn: visData.columns[matchingIndex + 1], + metricColumn: getMetricColumn(metric?.accessor ?? matchingIndex + 1, visData), }; } - const metricAccessor = visParams?.dimensions?.metric.accessor ?? 0; - const metricColumn = getColumnByAccessor(metricAccessor, visData.columns); + const metricColumn = getMetricColumn(metric?.accessor ?? 0, visData); return { metricColumn, - bucketColumns: [ - { - name: metricColumn.name, - }, - ], + bucketColumns: [{ name: metricColumn.name }], }; }; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_distinct_series.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_distinct_series.test.ts similarity index 100% rename from src/plugins/chart_expressions/expression_pie/public/utils/get_distinct_series.test.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/get_distinct_series.test.ts diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_distinct_series.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_distinct_series.ts similarity index 78% rename from src/plugins/chart_expressions/expression_pie/public/utils/get_distinct_series.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/get_distinct_series.ts index d5014689f331f..cb432bf7b2580 100644 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_distinct_series.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_distinct_series.ts @@ -8,7 +8,15 @@ import { DatatableRow } from '../../../../expressions/public'; import { BucketColumns } from '../../common/types'; -export const getDistinctSeries = (rows: DatatableRow[], buckets: Array>) => { +export interface DistinctSeries { + allSeries: string[]; + parentSeries: string[]; +} + +export const getDistinctSeries = ( + rows: DatatableRow[], + buckets: Array> +): DistinctSeries => { const parentBucketId = buckets[0].id; const parentSeries: string[] = []; const allSeries: string[] = []; @@ -24,8 +32,5 @@ export const getDistinctSeries = (rows: DatatableRow[], buckets: Array Promise, getFilterEventData: (series: SeriesIdentifier) => ClickTriggerEvent | null, onFilter: (data: ClickTriggerEvent, negate?: any) => void, - visParams: PieVisParams, + visParams: PartitionVisParams, actions: DataPublicPluginStart['actions'], formatter: FieldFormatsStart ): LegendAction => { @@ -56,7 +56,7 @@ export const getLegendActions = ( title: `${title}`, items: [ { - name: i18n.translate('expressionPie.legend.filterForValueButtonAriaLabel', { + name: i18n.translate('expressionPartitionVis.legend.filterForValueButtonAriaLabel', { defaultMessage: 'Filter for value', }), 'data-test-subj': `legend-${title}-filterIn`, @@ -67,7 +67,7 @@ export const getLegendActions = ( }, }, { - name: i18n.translate('expressionPie.legend.filterOutValueButtonAriaLabel', { + name: i18n.translate('expressionPartitionVis.legend.filterOutValueButtonAriaLabel', { defaultMessage: 'Filter out value', }), 'data-test-subj': `legend-${title}-filterOut`, @@ -114,7 +114,7 @@ export const getLegendActions = ( }} panelPaddingSize="none" anchorPosition="upLeft" - title={i18n.translate('expressionPie.legend.filterOptionsLegend', { + title={i18n.translate('expressionPartitionVis.legend.filterOptionsLegend', { defaultMessage: '{legendDataLabel}, filter options', values: { legendDataLabel: title }, })} diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts new file mode 100644 index 0000000000000..11838c7ce0140 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.test.ts @@ -0,0 +1,496 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { getPartitionTheme } from './get_partition_theme'; +import { createMockPieParams, createMockDonutParams, createMockPartitionVisParams } from '../mocks'; +import { ChartTypes, LabelPositions, PartitionVisParams } from '../../common/types'; +import { RecursivePartial } from '@elastic/eui'; +import { Theme } from '@elastic/charts'; + +const column: ExpressionValueVisDimension = { + type: 'vis_dimension', + accessor: { id: 'col-1-1', name: 'Count', meta: { type: 'number' } }, + format: { + id: 'terms', + params: { + id: 'string', + otherBucketLabel: 'Other', + missingBucketLabel: 'Missing', + }, + }, +}; + +const splitRows = [column]; +const splitColumns = [column]; +const chartTheme: RecursivePartial = { + barSeriesStyle: { displayValue: { fontFamily: 'Arial' } }, + lineSeriesStyle: { point: { fill: '#fff' } }, + axes: { axisTitle: { fill: '#000' } }, +}; + +const linkLabelWithEnoughSpace = (visParams: PartitionVisParams) => ({ + maxCount: Number.POSITIVE_INFINITY, + maximumSection: Number.POSITIVE_INFINITY, + maxTextLength: visParams.labels.truncate ?? undefined, +}); + +const linkLabelsWithoutSpaceForOuterLabels = { maxCount: 0 }; + +const linkLabelsWithoutSpaceForLabels = { + maxCount: 0, + maximumSection: Number.POSITIVE_INFINITY, +}; + +const getStaticThemePartition = ( + theme: RecursivePartial, + visParams: PartitionVisParams +) => ({ + fontFamily: theme.barSeriesStyle?.displayValue?.fontFamily, + outerSizeRatio: 1, + minFontSize: 10, + maxFontSize: 16, + emptySizeRatio: visParams.emptySizeRatio ?? 0, + sectorLineStroke: theme.lineSeriesStyle?.point?.fill, + sectorLineWidth: 1.5, + circlePadding: 4, +}); + +const getStaticThemeOptions = (theme: RecursivePartial, visParams: PartitionVisParams) => ({ + partition: getStaticThemePartition(theme, visParams), + chartMargins: { top: 0, left: 0, bottom: 0, right: 0 }, +}); + +const getDefaultLinkLabel = (visParams: PartitionVisParams, theme: RecursivePartial) => ({ + maxCount: 5, + fontSize: 11, + textColor: theme.axes?.axisTitle?.fill, + maxTextLength: visParams.labels.truncate ?? undefined, +}); + +const dimensions = undefined; + +const runPieDonutWaffleTestSuites = (chartType: ChartTypes, visParams: PartitionVisParams) => { + const vParamsSplitRows = { + ...visParams, + dimensions: { ...visParams.dimensions, splitRow: splitRows }, + }; + const vParamsSplitColumns = { + ...visParams, + dimensions: { ...visParams.dimensions, splitColumn: splitColumns }, + }; + + it('should return correct default theme options', () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + outerSizeRatio: undefined, + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should not return padding settings if dimensions are not specified', () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + outerSizeRatio: undefined, + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should not return padding settings if split column or row are specified', () => { + const themeForSplitColumns = getPartitionTheme( + chartType, + vParamsSplitColumns, + chartTheme, + dimensions + ); + + expect(themeForSplitColumns).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitColumns), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitColumns), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + + const themeForSplitRows = getPartitionTheme( + chartType, + vParamsSplitRows, + chartTheme, + dimensions + ); + + expect(themeForSplitRows).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitRows), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitRows), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + }); + + it('should return adjusted padding settings if dimensions are specified', () => { + const specifiedDimensions = { width: 2000, height: 2000 }; + const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, + partition: { + ...getStaticThemePartition(chartTheme, visParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should return right settings for the theme related fields', () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + outerSizeRatio: undefined, + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should return undefined outerSizeRatio for split chart and show labels', () => { + const specifiedDimensions = { width: 2000, height: 2000 }; + const theme = getPartitionTheme(chartType, vParamsSplitRows, chartTheme, specifiedDimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitRows), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitRows), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + + const themeForSplitColumns = getPartitionTheme( + chartType, + vParamsSplitColumns, + chartTheme, + specifiedDimensions + ); + + expect(themeForSplitColumns).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitColumns), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitColumns), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + }); + + it( + 'should return undefined outerSizeRatio for not specified dimensions, visible labels,' + + 'and default labels position and not split chart', + () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + outerSizeRatio: undefined, + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + } + ); + + it( + 'should return rescaleFactor value for outerSizeRatio if dimensions are specified,' + + ' is not split chart, labels are shown and labels position is not `inside`', + () => { + const specifiedDimensions = { width: 2000, height: 2000 }; + const rescaleFactor = 2; + const theme = getPartitionTheme( + chartType, + visParams, + chartTheme, + specifiedDimensions, + rescaleFactor + ); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, + partition: { + ...getStaticThemePartition(chartTheme, visParams), + outerSizeRatio: rescaleFactor, + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + } + ); + it( + 'should return adjusted rescaleFactor for outerSizeRatio if dimensions are specified,' + + ' is not split chart, labels position is `inside` and labels are shown', + () => { + const specifiedDimensions = { width: 2000, height: 2000 }; + const rescaleFactor = 1; + const vParams = { + ...visParams, + labels: { ...visParams.labels, position: LabelPositions.INSIDE }, + }; + + const theme = getPartitionTheme( + chartType, + vParams, + chartTheme, + specifiedDimensions, + rescaleFactor + ); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, vParams), + chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, + partition: { + ...getStaticThemePartition(chartTheme, vParams), + outerSizeRatio: 0.5, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + } + ); + it( + 'should return linkLabel with enough space if labels are shown,' + + ' labels position is `default` and need to show the last level only.', + () => { + const specifiedDimensions = { width: 2000, height: 2000 }; + const vParams = { + ...visParams, + labels: { ...visParams.labels, last_level: true }, + }; + const theme = getPartitionTheme(chartType, vParams, chartTheme, specifiedDimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, vParams), + chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, + partition: { + ...getStaticThemePartition(chartTheme, vParams), + linkLabel: linkLabelWithEnoughSpace(vParams), + }, + }); + } + ); + + it('should hide links if position is `inside` or is split chart, and labels are shown', () => { + const vParams = { + ...visParams, + labels: { ...visParams.labels, position: LabelPositions.INSIDE }, + }; + const theme = getPartitionTheme(chartType, vParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, vParams), + partition: { + ...getStaticThemePartition(chartTheme, vParams), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + + const themeSplitColumns = getPartitionTheme( + chartType, + vParamsSplitColumns, + chartTheme, + dimensions + ); + + expect(themeSplitColumns).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitColumns), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitColumns), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + + const themeSplitRows = getPartitionTheme(chartType, vParamsSplitRows, chartTheme, dimensions); + + expect(themeSplitRows).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitRows), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitRows), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForOuterLabels, + }, + }); + }); + + it('should hide links if labels are not shown', () => { + const vParams = { ...visParams, labels: { ...visParams.labels, show: false } }; + const theme = getPartitionTheme(chartType, vParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, vParams), + partition: { + ...getStaticThemePartition(chartTheme, vParams), + outerSizeRatio: undefined, + linkLabel: linkLabelsWithoutSpaceForLabels, + }, + }); + }); +}; + +const runTreemapMosaicTestSuites = (chartType: ChartTypes, visParams: PartitionVisParams) => { + const vParamsSplitRows = { + ...visParams, + dimensions: { ...visParams.dimensions, splitRow: splitRows }, + }; + const vParamsSplitColumns = { + ...visParams, + dimensions: { ...visParams.dimensions, splitColumn: splitColumns }, + }; + + it('should return correct theme options', () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should return empty padding settings if dimensions are not specified', () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should return padding settings if split column or row are specified', () => { + const themeForSplitColumns = getPartitionTheme( + chartType, + vParamsSplitColumns, + chartTheme, + dimensions + ); + + expect(themeForSplitColumns).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitColumns), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitColumns), + linkLabel: getDefaultLinkLabel(vParamsSplitColumns, chartTheme), + }, + }); + + const themeForSplitRows = getPartitionTheme( + chartType, + vParamsSplitRows, + chartTheme, + dimensions + ); + + expect(themeForSplitRows).toEqual({ + ...getStaticThemeOptions(chartTheme, vParamsSplitRows), + partition: { + ...getStaticThemePartition(chartTheme, vParamsSplitRows), + linkLabel: getDefaultLinkLabel(vParamsSplitRows, chartTheme), + }, + }); + }); + + it('should return fullfilled padding settings if dimensions are specified', () => { + const specifiedDimensions = { width: 2000, height: 2000 }; + const theme = getPartitionTheme(chartType, visParams, chartTheme, specifiedDimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + chartPaddings: { top: 500, bottom: 500, left: 500, right: 500 }, + partition: { + ...getStaticThemePartition(chartTheme, visParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should return settings for the theme related fields', () => { + const theme = getPartitionTheme(chartType, visParams, chartTheme, dimensions); + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + partition: { + ...getStaticThemePartition(chartTheme, visParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + }, + }); + }); + + it('should make color transparent if labels are hidden', () => { + const vParams = { ...visParams, labels: { ...visParams.labels, show: false } }; + const theme = getPartitionTheme(chartType, vParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, vParams), + partition: { + ...getStaticThemePartition(chartTheme, vParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + fillLabel: { textColor: 'rgba(0,0,0,0)' }, + }, + }); + }); +}; + +describe('Pie getPartitionTheme', () => { + runPieDonutWaffleTestSuites(ChartTypes.PIE, createMockPieParams()); +}); + +describe('Donut getPartitionTheme', () => { + const visParams = createMockDonutParams(); + const chartType = ChartTypes.DONUT; + + runPieDonutWaffleTestSuites(chartType, visParams); + + it('should return correct empty size ratio and partitionLayout', () => { + const theme = getPartitionTheme(ChartTypes.DONUT, visParams, chartTheme, dimensions); + + expect(theme).toEqual({ + ...getStaticThemeOptions(chartTheme, visParams), + outerSizeRatio: undefined, + partition: { + ...getStaticThemePartition(chartTheme, visParams), + linkLabel: getDefaultLinkLabel(visParams, chartTheme), + outerSizeRatio: undefined, + }, + }); + }); +}); + +describe('Waffle getPartitionTheme', () => { + runPieDonutWaffleTestSuites(ChartTypes.WAFFLE, createMockPartitionVisParams()); +}); + +describe('Mosaic getPartitionTheme', () => { + runTreemapMosaicTestSuites(ChartTypes.MOSAIC, createMockPartitionVisParams()); +}); + +describe('Treemap getPartitionTheme', () => { + runTreemapMosaicTestSuites(ChartTypes.TREEMAP, createMockPartitionVisParams()); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts new file mode 100644 index 0000000000000..edb1aaea64aad --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_theme.ts @@ -0,0 +1,165 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { RecursivePartial, Theme, PartialTheme } from '@elastic/charts'; +import { + ChartTypes, + LabelPositions, + PartitionVisParams, + PieContainerDimensions, +} from '../../common/types'; + +type GetThemeByTypeFn = ( + chartType: ChartTypes, + visParams: PartitionVisParams, + dimensions?: PieContainerDimensions, + rescaleFactor?: number +) => PartialTheme; + +type GetThemeFn = ( + chartType: ChartTypes, + visParams: PartitionVisParams, + chartTheme: RecursivePartial, + dimensions?: PieContainerDimensions, + rescaleFactor?: number +) => PartialTheme; + +type GetPieDonutWaffleThemeFn = ( + visParams: PartitionVisParams, + dimensions?: PieContainerDimensions, + rescaleFactor?: number +) => PartialTheme; + +type GetTreemapMosaicThemeFn = (visParams: PartitionVisParams) => PartialTheme; + +const MAX_SIZE = 1000; + +const getPieDonutWaffleCommonTheme: GetPieDonutWaffleThemeFn = ( + visParams, + dimensions, + rescaleFactor = 1 +) => { + const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow); + const preventLinksFromShowing = + (visParams.labels.position === LabelPositions.INSIDE || isSplitChart) && visParams.labels.show; + + const usingOuterSizeRatio = + dimensions && !isSplitChart + ? { + outerSizeRatio: + // Cap the ratio to 1 and then rescale + rescaleFactor * Math.min(MAX_SIZE / Math.min(dimensions?.width, dimensions?.height), 1), + } + : { outerSizeRatio: undefined }; + + const theme: PartialTheme = {}; + theme.partition = { ...(usingOuterSizeRatio ?? {}) }; + + if ( + visParams.labels.show && + visParams.labels.position === LabelPositions.DEFAULT && + visParams.labels.last_level + ) { + theme.partition.linkLabel = { + maxCount: Number.POSITIVE_INFINITY, + maximumSection: Number.POSITIVE_INFINITY, + maxTextLength: visParams.labels.truncate ?? undefined, + }; + } + + if (preventLinksFromShowing || !visParams.labels.show) { + // Prevent links from showing + theme.partition.linkLabel = { + maxCount: 0, + ...(!visParams.labels.show ? { maximumSection: Number.POSITIVE_INFINITY } : {}), + }; + } + + if (!preventLinksFromShowing && dimensions && !isSplitChart) { + // shrink up to 20% to give some room for the linked values + theme.partition.outerSizeRatio = rescaleFactor; + } + + return theme; +}; + +const getDonutSpecificTheme: GetPieDonutWaffleThemeFn = (visParams, ...args) => { + const { partition, ...restTheme } = getPieDonutWaffleCommonTheme(visParams, ...args); + return { ...restTheme, partition: { ...partition, emptySizeRatio: visParams.emptySizeRatio } }; +}; + +const getTreemapMosaicCommonTheme: GetTreemapMosaicThemeFn = (visParams) => { + if (!visParams.labels.show) { + return { + partition: { + fillLabel: { textColor: 'rgba(0,0,0,0)' }, + }, + }; + } + return {}; +}; + +const getSpecificTheme: GetThemeByTypeFn = (chartType, visParams, dimensions, rescaleFactor) => + ({ + [ChartTypes.PIE]: () => getPieDonutWaffleCommonTheme(visParams, dimensions, rescaleFactor), + [ChartTypes.DONUT]: () => getDonutSpecificTheme(visParams, dimensions, rescaleFactor), + [ChartTypes.TREEMAP]: () => getTreemapMosaicCommonTheme(visParams), + [ChartTypes.MOSAIC]: () => getTreemapMosaicCommonTheme(visParams), + [ChartTypes.WAFFLE]: () => getPieDonutWaffleCommonTheme(visParams, dimensions, rescaleFactor), + }[chartType]()); + +export const getPartitionTheme: GetThemeFn = ( + chartType, + visParams, + chartTheme, + dimensions, + rescaleFactor = 1 +) => { + // On small multiples we want the labels to only appear inside + const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow); + const paddingProps: PartialTheme | null = + dimensions && !isSplitChart + ? { + chartPaddings: { + top: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height, + bottom: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height, + left: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height, + right: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height, + }, + } + : null; + const partition = { + fontFamily: chartTheme.barSeriesStyle?.displayValue?.fontFamily, + outerSizeRatio: 1, + minFontSize: 10, + maxFontSize: 16, + emptySizeRatio: 0, + sectorLineStroke: chartTheme.lineSeriesStyle?.point?.fill, + sectorLineWidth: 1.5, + circlePadding: 4, + linkLabel: { + maxCount: 5, + fontSize: 11, + textColor: chartTheme.axes?.axisTitle?.fill, + maxTextLength: visParams.labels.truncate ?? undefined, + }, + }; + const { partition: specificPartition = {}, ...restSpecificTheme } = getSpecificTheme( + chartType, + visParams, + dimensions, + rescaleFactor + ); + + return { + partition: { ...partition, ...specificPartition }, + chartMargins: { top: 0, bottom: 0, left: 0, right: 0 }, + ...(paddingProps ?? {}), + ...restSpecificTheme, + }; +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_type.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_type.ts new file mode 100644 index 0000000000000..842c4f49b42c9 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_partition_type.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PartitionLayout } from '@elastic/charts'; +import { ChartTypes } from '../../common/types'; + +export const getPartitionType = (chartType: ChartTypes) => + ({ + [ChartTypes.PIE]: PartitionLayout.sunburst, + [ChartTypes.DONUT]: PartitionLayout.sunburst, + [ChartTypes.TREEMAP]: PartitionLayout.treemap, + [ChartTypes.MOSAIC]: PartitionLayout.mosaic, + [ChartTypes.WAFFLE]: PartitionLayout.waffle, + }[chartType]); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_split_dimension_accessor.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_split_dimension_accessor.test.ts new file mode 100644 index 0000000000000..5e0f58a384785 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_split_dimension_accessor.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { fieldFormatsMock } from '../../../../field_formats/common/mocks'; +import { DatatableColumn } from '../../../../expressions'; +import { createMockVisData } from '../mocks'; +import { getSplitDimensionAccessor } from './get_split_dimension_accessor'; +import { BucketColumns } from '../../common/types'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; + +describe('getSplitDimensionAccessor', () => { + const visData = createMockVisData(); + + const preparedFormatter1 = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const preparedFormatter2 = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + const defaultFormatter = jest.fn((...args) => fieldFormatsMock.deserialize(...args)); + + beforeEach(() => { + defaultFormatter.mockClear(); + preparedFormatter1.mockClear(); + preparedFormatter2.mockClear(); + }); + + const formatters: Record = { + [visData.columns[0].id]: preparedFormatter1(), + [visData.columns[1].id]: preparedFormatter2(), + }; + + const splitDimension: ExpressionValueVisDimension = { + type: 'vis_dimension', + accessor: { + id: visData.columns[1].id, + name: visData.columns[1].name, + meta: visData.columns[1].meta, + }, + format: { + params: {}, + }, + }; + + it('returns accessor which is using formatter from formatters, if meta.params are present at accessing column', () => { + const accessor = getSplitDimensionAccessor( + visData.columns, + splitDimension, + formatters, + defaultFormatter + ); + const formatter = formatters[visData.columns[1].id]; + const spyOnFormatterConvert = jest.spyOn(formatter, 'convert'); + + expect(defaultFormatter).toHaveBeenCalledTimes(0); + expect(typeof accessor).toBe('function'); + accessor(visData.rows[0]); + expect(spyOnFormatterConvert).toHaveBeenCalledTimes(1); + }); + + it('returns accessor which is using default formatter, if meta.params are not present and format is present at accessing column', () => { + const column: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + format: { + id: 'string', + params: {}, + }, + }; + const columns = [visData.columns[0], column, visData.columns[2]] as DatatableColumn[]; + const defaultFormatterReturnedVal = fieldFormatsMock.deserialize(); + const spyOnDefaultFormatterConvert = jest.spyOn(defaultFormatterReturnedVal, 'convert'); + + defaultFormatter.mockReturnValueOnce(defaultFormatterReturnedVal); + + const accessor = getSplitDimensionAccessor( + columns, + splitDimension, + formatters, + defaultFormatter + ); + + expect(defaultFormatter).toHaveBeenCalledTimes(1); + expect(defaultFormatter).toHaveBeenCalledWith(column.format); + + expect(typeof accessor).toBe('function'); + accessor(visData.rows[0]); + expect(spyOnDefaultFormatterConvert).toHaveBeenCalledTimes(1); + }); + + it('returns accessor which is using default formatter, if meta.params and format are not present', () => { + const column: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + }; + const columns = [visData.columns[0], column, visData.columns[2]] as DatatableColumn[]; + const defaultFormatterReturnedVal = fieldFormatsMock.deserialize(); + const spyOnDefaultFormatterConvert = jest.spyOn(defaultFormatterReturnedVal, 'convert'); + + defaultFormatter.mockReturnValueOnce(defaultFormatterReturnedVal); + + const accessor = getSplitDimensionAccessor( + columns, + splitDimension, + formatters, + defaultFormatter + ); + + expect(defaultFormatter).toHaveBeenCalledTimes(1); + expect(defaultFormatter).toHaveBeenCalledWith(); + + expect(typeof accessor).toBe('function'); + accessor(visData.rows[0]); + expect(spyOnDefaultFormatterConvert).toHaveBeenCalledTimes(1); + }); + + it('returns accessor which returns undefined, if such column is not present', () => { + const accessor1 = getSplitDimensionAccessor( + visData.columns, + splitDimension, + formatters, + defaultFormatter + ); + + expect(typeof accessor1).toBe('function'); + const result1 = accessor1({}); + expect(result1).toBeUndefined(); + + const column2: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + }; + const columns2 = [visData.columns[0], column2, visData.columns[2]] as DatatableColumn[]; + const accessor2 = getSplitDimensionAccessor( + columns2, + splitDimension, + formatters, + defaultFormatter + ); + + expect(typeof accessor2).toBe('function'); + const result2 = accessor1({}); + expect(result2).toBeUndefined(); + + const column3: Partial = { + ...visData.columns[1], + meta: { type: 'string' }, + format: { + id: 'string', + params: {}, + }, + }; + const columns3 = [visData.columns[0], column3, visData.columns[2]] as DatatableColumn[]; + + const accessor3 = getSplitDimensionAccessor( + columns3, + splitDimension, + formatters, + defaultFormatter + ); + expect(typeof accessor3).toBe('function'); + const result3 = accessor3({}); + expect(result3).toBeUndefined(); + }); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_split_dimension_accessor.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_split_dimension_accessor.ts new file mode 100644 index 0000000000000..1a18a1134bafe --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/get_split_dimension_accessor.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { AccessorFn } from '@elastic/charts'; +import { getColumnByAccessor } from './accessor'; +import { DatatableColumn } from '../../../../expressions/public'; +import { FieldFormat, FormatFactory } from '../../../../field_formats/common'; +import { ExpressionValueVisDimension } from '../../../../visualizations/common'; +import { getFormatter } from './formatters'; + +export const getSplitDimensionAccessor = ( + columns: DatatableColumn[], + splitDimension: ExpressionValueVisDimension, + formatters: Record, + defaultFormatFactory: FormatFactory +): AccessorFn => { + const splitChartColumn = getColumnByAccessor(splitDimension.accessor, columns); + const accessor = splitChartColumn.id; + const formatter = getFormatter(splitChartColumn, formatters, defaultFormatFactory); + + const fn: AccessorFn = (d) => { + const v = d[accessor]; + if (v === undefined) { + return; + } + + const f = formatter.convert(v); + return f; + }; + + return fn; +}; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/index.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/index.ts similarity index 69% rename from src/plugins/chart_expressions/expression_pie/public/utils/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/index.ts index e1b779c511bfc..afa0b82a87eb1 100644 --- a/src/plugins/chart_expressions/expression_pie/public/utils/index.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/index.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -export { getLayers } from './get_layers'; -export { getColorPicker } from './get_color_picker'; +export { getLayers } from './layers'; +export { LegendColorPickerWrapper, LegendColorPickerWrapperContext } from './get_color_picker'; export { getLegendActions } from './get_legend_actions'; export { canFilter, getFilterClickData, getFilterEventData } from './filter_helpers'; export { getPartitionTheme } from './get_partition_theme'; @@ -15,3 +15,6 @@ export { getColumns } from './get_columns'; export { getSplitDimensionAccessor } from './get_split_dimension_accessor'; export { getDistinctSeries } from './get_distinct_series'; export { getColumnByAccessor } from './accessor'; +export { isLegendFlat, shouldShowLegend } from './legend'; +export { generateFormatters, getAvailableFormatter, getFormatter } from './formatters'; +export { getPartitionType } from './get_partition_type'; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.ts new file mode 100644 index 0000000000000..d381c9cd3f0be --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_color.ts @@ -0,0 +1,237 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ShapeTreeNode } from '@elastic/charts'; +import { isEqual } from 'lodash'; +import type { FieldFormatsStart } from '../../../../../field_formats/public'; +import { + SeriesLayer, + PaletteRegistry, + lightenColor, + PaletteDefinition, + PaletteOutput, +} from '../../../../../charts/public'; +import type { Datatable, DatatableRow } from '../../../../../expressions/public'; +import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types'; +import { DistinctSeries, getDistinctSeries } from '../get_distinct_series'; + +const isTreemapOrMosaicChart = (shape: ChartTypes) => + [ChartTypes.MOSAIC, ChartTypes.TREEMAP].includes(shape); + +export const byDataColorPaletteMap = ( + rows: Datatable['rows'], + columnId: string, + paletteDefinition: PaletteDefinition, + { params }: PaletteOutput +) => { + const colorMap = new Map( + rows.map((item) => [String(item[columnId]), undefined]) + ); + let rankAtDepth = 0; + + return { + getColor: (item: unknown) => { + const key = String(item); + if (!colorMap.has(key)) return; + + let color = colorMap.get(key); + if (color) { + return color; + } + color = + paletteDefinition.getCategoricalColor( + [ + { + name: key, + totalSeriesAtDepth: colorMap.size, + rankAtDepth: rankAtDepth++, + }, + ], + { behindText: false }, + params + ) || undefined; + + colorMap.set(key, color); + return color; + }, + }; +}; + +const getDistinctColor = ( + d: ShapeTreeNode, + isSplitChart: boolean, + overwriteColors: { [key: string]: string } = {}, + visParams: PartitionVisParams, + palettes: PaletteRegistry | null, + syncColors: boolean, + { parentSeries, allSeries }: DistinctSeries, + name: string +) => { + let overwriteColor; + // this is for supporting old visualizations (created by vislib plugin) + // it seems that there for some aggs, the uiState saved from vislib is + // different than the es-charts handle it + if (overwriteColors.hasOwnProperty(name)) { + overwriteColor = overwriteColors[name]; + } + + if (Object.keys(overwriteColors).includes(d.dataName.toString())) { + overwriteColor = overwriteColors[d.dataName]; + } + + if (overwriteColor) { + return overwriteColor; + } + + const index = allSeries.findIndex((dataName) => isEqual(dataName, d.dataName)); + const isSplitParentLayer = isSplitChart && parentSeries.includes(d.dataName); + return palettes?.get(visParams.palette.name).getCategoricalColor( + [ + { + name: d.dataName, + rankAtDepth: isSplitParentLayer + ? parentSeries.findIndex((dataName) => dataName === d.dataName) + : index > -1 + ? index + : 0, + totalSeriesAtDepth: isSplitParentLayer ? parentSeries.length : allSeries.length || 1, + }, + ], + { + maxDepth: 1, + totalSeries: allSeries.length || 1, + behindText: visParams.labels.show, + syncColors, + }, + visParams.palette?.params ?? { colors: [] } + ); +}; + +const createSeriesLayers = ( + d: ShapeTreeNode, + parentSeries: DistinctSeries['parentSeries'], + isSplitChart: boolean +) => { + const seriesLayers: SeriesLayer[] = []; + let tempParent: typeof d | typeof d['parent'] = d; + while (tempParent.parent && tempParent.depth > 0) { + const seriesName = String(tempParent.parent.children[tempParent.sortIndex][0]); + const isSplitParentLayer = isSplitChart && parentSeries.includes(seriesName); + seriesLayers.unshift({ + name: seriesName, + rankAtDepth: isSplitParentLayer + ? parentSeries.findIndex((name) => name === seriesName) + : tempParent.sortIndex, + totalSeriesAtDepth: isSplitParentLayer + ? parentSeries.length + : tempParent.parent.children.length, + }); + tempParent = tempParent.parent; + } + return seriesLayers; +}; + +const overrideColorForOldVisualization = ( + seriesLayers: SeriesLayer[], + overwriteColors: { [key: string]: string }, + name: string +) => { + let overwriteColor; + // this is for supporting old visualizations (created by vislib plugin) + // it seems that there for some aggs, the uiState saved from vislib is + // different than the es-charts handle it + if (overwriteColors.hasOwnProperty(name)) { + overwriteColor = overwriteColors[name]; + } + + seriesLayers.forEach((layer) => { + if (Object.keys(overwriteColors).includes(layer.name)) { + overwriteColor = overwriteColors[layer.name]; + } + }); + + return overwriteColor; +}; + +export const getColor = ( + chartType: ChartTypes, + d: ShapeTreeNode, + layerIndex: number, + isSplitChart: boolean, + overwriteColors: { [key: string]: string } = {}, + columns: Array>, + rows: DatatableRow[], + visParams: PartitionVisParams, + palettes: PaletteRegistry | null, + byDataPalette: ReturnType, + syncColors: boolean, + isDarkMode: boolean, + formatter: FieldFormatsStart, + format?: BucketColumns['format'] +) => { + const distinctSeries = getDistinctSeries(rows, columns); + const { parentSeries } = distinctSeries; + const dataName = d.dataName; + + // Mind the difference here: the contrast computation for the text ignores the alpha/opacity + // therefore change it for dask mode + const defaultColor = isDarkMode ? 'rgba(0,0,0,0)' : 'rgba(255,255,255,0)'; + + let name = ''; + if (format) { + name = formatter.deserialize(format).convert(dataName) ?? ''; + } + + if (visParams.distinctColors) { + return ( + getDistinctColor( + d, + isSplitChart, + overwriteColors, + visParams, + palettes, + syncColors, + distinctSeries, + name + ) || defaultColor + ); + } + + const seriesLayers = createSeriesLayers(d, parentSeries, isSplitChart); + + const overwriteColor = overrideColorForOldVisualization(seriesLayers, overwriteColors, name); + if (overwriteColor) { + return lightenColor(overwriteColor, seriesLayers.length, columns.length); + } + + if (chartType === ChartTypes.MOSAIC && byDataPalette && seriesLayers[1]) { + return byDataPalette.getColor(seriesLayers[1].name) || defaultColor; + } + + if (isTreemapOrMosaicChart(chartType)) { + if (layerIndex < columns.length - 1) { + return defaultColor; + } + // only use the top level series layer for coloring + if (seriesLayers.length > 1) { + seriesLayers.pop(); + } + } + + const outputColor = palettes?.get(visParams.palette.name).getCategoricalColor( + seriesLayers, + { + behindText: visParams.labels.show || isTreemapOrMosaicChart(chartType), + maxDepth: columns.length, + totalSeries: rows.length, + syncColors, + }, + visParams.palette?.params ?? { colors: [] } + ); + + return outputColor || defaultColor; +}; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_layers.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts similarity index 84% rename from src/plugins/chart_expressions/expression_pie/public/utils/get_layers.test.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts index 39c3ccfc45a93..34daed61f67cf 100644 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_layers.test.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.test.ts @@ -6,11 +6,12 @@ * Side Public License, v 1. */ import { ShapeTreeNode } from '@elastic/charts'; -import { PaletteDefinition, SeriesLayer } from '../../../../charts/public'; -import { dataPluginMock } from '../../../../data/public/mocks'; -import type { DataPublicPluginStart } from '../../../../data/public'; -import { computeColor } from './get_layers'; -import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../mocks'; +import { PaletteDefinition, SeriesLayer } from '../../../../../charts/public'; +import { dataPluginMock } from '../../../../../data/public/mocks'; +import type { DataPublicPluginStart } from '../../../../../data/public'; +import { getColor } from './get_color'; +import { createMockVisData, createMockBucketColumns, createMockPieParams } from '../../mocks'; +import { ChartTypes } from '../../../common/types'; const visData = createMockVisData(); const buckets = createMockBucketColumns(); @@ -68,14 +69,18 @@ describe('computeColor', () => { sortIndex: 0, }, } as unknown as ShapeTreeNode; - const color = computeColor( + const color = getColor( + ChartTypes.PIE, d, + 0, false, {}, buckets, visData.rows, visParams, getPaletteRegistry(), + { getColor: () => undefined }, + false, false, dataMock.fieldFormats ); @@ -93,14 +98,18 @@ describe('computeColor', () => { sortIndex: 0, }, } as unknown as ShapeTreeNode; - const color = computeColor( + const color = getColor( + ChartTypes.PIE, d, + 0, true, {}, buckets, visData.rows, visParams, getPaletteRegistry(), + { getColor: () => undefined }, + false, false, dataMock.fieldFormats ); @@ -117,14 +126,18 @@ describe('computeColor', () => { sortIndex: 0, }, } as unknown as ShapeTreeNode; - const color = computeColor( + const color = getColor( + ChartTypes.PIE, d, + 0, true, { 'ES-Air': '#000028' }, buckets, visData.rows, visParams, getPaletteRegistry(), + { getColor: () => undefined }, + false, false, dataMock.fieldFormats ); @@ -162,14 +175,18 @@ describe('computeColor', () => { ...visParams, distinctColors: true, }; - const color = computeColor( + const color = getColor( + ChartTypes.PIE, d, + 0, true, { '≥ 1000 and < 2000': '#3F6833' }, buckets, visData.rows, visParamsNew, getPaletteRegistry(), + { getColor: () => undefined }, + false, false, dataMock.fieldFormats, { diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts new file mode 100644 index 0000000000000..9f27ff628cf97 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_layers.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Datum, PartitionLayer } from '@elastic/charts'; +import { FieldFormat } from '../../../../../field_formats/common'; +import type { FieldFormatsStart } from '../../../../../field_formats/public'; +import { PaletteRegistry } from '../../../../../charts/public'; +import type { Datatable, DatatableRow } from '../../../../../expressions/public'; +import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types'; +import { sortPredicateByType } from './sort_predicate'; +import { byDataColorPaletteMap, getColor } from './get_color'; +import { getNodeLabel } from './get_node_labels'; + +const EMPTY_SLICE = Symbol('empty_slice'); + +export const getLayers = ( + chartType: ChartTypes, + columns: Array>, + visParams: PartitionVisParams, + visData: Datatable, + overwriteColors: { [key: string]: string } = {}, + rows: DatatableRow[], + palettes: PaletteRegistry | null, + formatters: Record, + formatter: FieldFormatsStart, + syncColors: boolean, + isDarkMode: boolean +): PartitionLayer[] => { + const fillLabel: PartitionLayer['fillLabel'] = { + valueFont: { + fontWeight: 700, + }, + }; + + if (!visParams.labels.values) { + fillLabel.valueFormatter = () => ''; + } + + const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow); + let byDataPalette: ReturnType; + if (!syncColors && columns[1]?.id && palettes && visParams.palette) { + byDataPalette = byDataColorPaletteMap( + rows, + columns[1].id, + palettes?.get(visParams.palette.name), + visParams.palette + ); + } + + const sortPredicate = sortPredicateByType(chartType, visParams, visData, columns); + return columns.map((col, layerIndex) => { + return { + groupByRollup: (d: Datum) => (col.id ? d[col.id] ?? EMPTY_SLICE : col.name), + showAccessor: (d: Datum) => d !== EMPTY_SLICE, + nodeLabel: (d: unknown) => getNodeLabel(d, col, formatters, formatter.deserialize), + fillLabel, + sortPredicate, + shape: { + fillColor: (d) => + getColor( + chartType, + d, + layerIndex, + isSplitChart, + overwriteColors, + columns, + rows, + visParams, + palettes, + byDataPalette, + syncColors, + isDarkMode, + formatter, + col.format + ), + }, + }; + }); +}; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_node_labels.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_node_labels.ts new file mode 100644 index 0000000000000..90c271daef6a4 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/get_node_labels.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { FieldFormat, FormatFactory } from '../../../../../field_formats/common'; +import { BucketColumns } from '../../../common/types'; +import { getAvailableFormatter } from '../formatters'; + +export const getNodeLabel = ( + nodeName: unknown, + column: Partial, + formatters: Record, + defaultFormatFactory: FormatFactory +) => { + const formatter = getAvailableFormatter(column, formatters, defaultFormatFactory); + if (formatter) { + return formatter.convert(nodeName) ?? ''; + } + + return String(nodeName); +}; diff --git a/src/plugins/chart_expressions/expression_pie/public/components/index.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/index.ts similarity index 89% rename from src/plugins/chart_expressions/expression_pie/public/components/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/index.ts index ef4589dac271e..84dad45cb3999 100644 --- a/src/plugins/chart_expressions/expression_pie/public/components/index.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export * from './pie_vis_component'; +export { getLayers } from './get_layers'; diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts new file mode 100644 index 0000000000000..c7eaa2c494492 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/layers/sort_predicate.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ArrayEntry } from '@elastic/charts'; +import { Datatable } from '../../../../../../../src/plugins/expressions'; +import { BucketColumns, ChartTypes, PartitionVisParams } from '../../../common/types'; + +type SortFn = (([name1, node1]: ArrayEntry, [name2, node2]: ArrayEntry) => number) | undefined; + +type SortPredicateDefaultFn = ( + visData: Datatable, + columns: Array> +) => SortFn; + +type SortPredicatePieDonutFn = (visParams: PartitionVisParams) => SortFn; + +type SortPredicatePureFn = () => SortFn; + +export const extractUniqTermsMap = (dataTable: Datatable, columnId: string) => + [...new Set(dataTable.rows.map((item) => item[columnId]))].reduce( + (acc, item, index) => ({ + ...acc, + [item]: index, + }), + {} + ); + +const sortPredicateSaveSourceOrder: SortPredicatePureFn = + () => + ([, node1], [, node2]) => { + const [index1] = node1.inputIndex ?? []; + if (index1 !== undefined) { + return index1; + } + return node2.value - node1.value; + }; + +const sortPredicatePieDonut: SortPredicatePieDonutFn = (visParams) => + visParams.respectSourceOrder ? sortPredicateSaveSourceOrder() : undefined; + +const sortPredicateMosaic: SortPredicateDefaultFn = (visData, columns) => { + const sortingMap = columns[0]?.id ? extractUniqTermsMap(visData, columns[0].id) : {}; + + return ([name1, node1], [, node2]) => { + // Sorting for first group + if (columns.length === 1 || (node1.children.length && name1 in sortingMap)) { + return sortingMap[name1]; + } + + // Sorting for second group + return node2.value - node1.value; + }; +}; + +const sortPredicateWaffle: SortPredicatePureFn = + () => + ([, node1], [, node2]) => + node2.value - node1.value; + +export const sortPredicateByType = ( + chartType: ChartTypes, + visParams: PartitionVisParams, + visData: Datatable, + columns: Array> +) => + ({ + [ChartTypes.PIE]: () => sortPredicatePieDonut(visParams), + [ChartTypes.DONUT]: () => sortPredicatePieDonut(visParams), + [ChartTypes.WAFFLE]: () => sortPredicateWaffle(), + [ChartTypes.TREEMAP]: () => undefined, + [ChartTypes.MOSAIC]: () => sortPredicateMosaic(visData, columns), + }[chartType]()); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/legend.test.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/legend.test.ts new file mode 100644 index 0000000000000..9d5e512de9956 --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/legend.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ChartTypes, LegendDisplay } from '../../common/types'; +import { createMockVisData } from '../mocks'; +import { isLegendFlat, shouldShowLegend } from './legend'; + +describe('isLegendFlat', () => { + const visData = createMockVisData(); + const splitChartDimension = visData.columns[0]; + + const runIsFlatCommonScenario = (chartType: ChartTypes) => { + it(`legend should be flat for ${chartType} if split dimension is specified`, () => { + const flat = isLegendFlat(chartType, splitChartDimension); + expect(flat).toBeTruthy(); + }); + + it(`legend should be not flat for ${chartType} if split dimension is not specified`, () => { + const flat = isLegendFlat(chartType, undefined); + expect(flat).toBeFalsy(); + }); + }; + + runIsFlatCommonScenario(ChartTypes.PIE); + runIsFlatCommonScenario(ChartTypes.DONUT); + runIsFlatCommonScenario(ChartTypes.TREEMAP); + runIsFlatCommonScenario(ChartTypes.MOSAIC); + + it('legend should be flat for Waffle if split dimension is specified', () => { + const flat = isLegendFlat(ChartTypes.WAFFLE, splitChartDimension); + expect(flat).toBeTruthy(); + }); + + it('legend should be flat for Waffle if split dimension is not specified', () => { + const flat = isLegendFlat(ChartTypes.WAFFLE, undefined); + expect(flat).toBeTruthy(); + }); +}); + +describe('shouldShowLegend', () => { + const visData = createMockVisData(); + + const runCommonShouldShowLegendScenario = (chartType: ChartTypes) => { + it(`should hide legend if legendDisplay = hide for ${chartType}`, () => { + const show = shouldShowLegend(chartType, LegendDisplay.HIDE); + expect(show).toBeFalsy(); + }); + + it(`should show legend if legendDisplay = show for ${chartType}`, () => { + const show = shouldShowLegend(chartType, LegendDisplay.SHOW); + expect(show).toBeTruthy(); + }); + }; + + const runShouldShowLegendDefaultBucketsScenario = (chartType: ChartTypes) => { + it(`should show legend if legendDisplay = default and multiple buckets for ${chartType}`, () => { + const show = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [ + visData.columns[0], + visData.columns[1], + ]); + + expect(show).toBeTruthy(); + }); + + it(`should hide legend if legendDisplay = default and one bucket or less for ${chartType}`, () => { + const show1 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [visData.columns[0]]); + expect(show1).toBeFalsy(); + + const show2 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, []); + expect(show2).toBeFalsy(); + + const show3 = shouldShowLegend(chartType, LegendDisplay.DEFAULT); + expect(show3).toBeFalsy(); + }); + }; + + const runShouldShowLegendDefaultAlwaysFalsyScenario = (chartType: ChartTypes) => { + it(`should hide legend if legendDisplay = default and multiple buckets for ${chartType}`, () => { + const show = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [ + visData.columns[0], + visData.columns[1], + ]); + + expect(show).toBeFalsy(); + }); + + it(`should hide legend if legendDisplay = default and one bucket or less for ${chartType}`, () => { + const show1 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [visData.columns[0]]); + expect(show1).toBeFalsy(); + + const show2 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, []); + expect(show2).toBeFalsy(); + + const show3 = shouldShowLegend(chartType, LegendDisplay.DEFAULT); + expect(show3).toBeFalsy(); + }); + }; + + const runShouldShowLegendDefaultAlwaysTruthyScenario = (chartType: ChartTypes) => { + it(`should show legend if legendDisplay = default and multiple buckets for ${chartType}`, () => { + const show = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [ + visData.columns[0], + visData.columns[1], + ]); + + expect(show).toBeTruthy(); + }); + + it(`should show legend if legendDisplay = default and one bucket or less for ${chartType}`, () => { + const show1 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, [visData.columns[0]]); + expect(show1).toBeTruthy(); + + const show2 = shouldShowLegend(chartType, LegendDisplay.DEFAULT, []); + expect(show2).toBeTruthy(); + + const show3 = shouldShowLegend(chartType, LegendDisplay.DEFAULT); + expect(show3).toBeTruthy(); + }); + }; + + runCommonShouldShowLegendScenario(ChartTypes.PIE); + runShouldShowLegendDefaultBucketsScenario(ChartTypes.PIE); + + runCommonShouldShowLegendScenario(ChartTypes.DONUT); + runShouldShowLegendDefaultBucketsScenario(ChartTypes.DONUT); + + runCommonShouldShowLegendScenario(ChartTypes.TREEMAP); + runShouldShowLegendDefaultAlwaysFalsyScenario(ChartTypes.TREEMAP); + + runCommonShouldShowLegendScenario(ChartTypes.MOSAIC); + runShouldShowLegendDefaultAlwaysFalsyScenario(ChartTypes.MOSAIC); + + runCommonShouldShowLegendScenario(ChartTypes.WAFFLE); + runShouldShowLegendDefaultAlwaysTruthyScenario(ChartTypes.WAFFLE); +}); diff --git a/src/plugins/chart_expressions/expression_partition_vis/public/utils/legend.ts b/src/plugins/chart_expressions/expression_partition_vis/public/utils/legend.ts new file mode 100644 index 0000000000000..9990c1a8e65ea --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/public/utils/legend.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DatatableColumn } from '../../../../expressions'; +import { BucketColumns, ChartTypes, LegendDisplay } from '../../common/types'; + +type GetLegendIsFlatFn = (splitChartDimension: DatatableColumn | undefined) => boolean; + +const isLegendFlatCommon: GetLegendIsFlatFn = (splitChartDimension) => Boolean(splitChartDimension); + +export const isLegendFlat = ( + visType: ChartTypes, + splitChartDimension: DatatableColumn | undefined +) => + ({ + [ChartTypes.PIE]: () => isLegendFlatCommon(splitChartDimension), + [ChartTypes.DONUT]: () => isLegendFlatCommon(splitChartDimension), + [ChartTypes.TREEMAP]: () => isLegendFlatCommon(splitChartDimension), + [ChartTypes.MOSAIC]: () => isLegendFlatCommon(splitChartDimension), + [ChartTypes.WAFFLE]: () => true, + }[visType]()); + +const showIfBuckets = (bucketColumns: Array>) => bucketColumns.length > 1; + +const showLegendDefault = (visType: ChartTypes, bucketColumns: Array>) => + ({ + [ChartTypes.PIE]: () => showIfBuckets(bucketColumns), + [ChartTypes.DONUT]: () => showIfBuckets(bucketColumns), + [ChartTypes.TREEMAP]: () => false, + [ChartTypes.MOSAIC]: () => false, + [ChartTypes.WAFFLE]: () => true, + }[visType]()); + +export const shouldShowLegend = ( + visType: ChartTypes, + legendDisplay: LegendDisplay, + bucketColumns: Array> = [] +) => + legendDisplay === LegendDisplay.SHOW || + (legendDisplay === LegendDisplay.DEFAULT && showLegendDefault(visType, bucketColumns)); diff --git a/src/plugins/chart_expressions/expression_pie/server/index.ts b/src/plugins/chart_expressions/expression_partition_vis/server/index.ts similarity index 65% rename from src/plugins/chart_expressions/expression_pie/server/index.ts rename to src/plugins/chart_expressions/expression_partition_vis/server/index.ts index 8123bc03c8e1b..98395d521e238 100755 --- a/src/plugins/chart_expressions/expression_pie/server/index.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/server/index.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { ExpressionPiePlugin } from './plugin'; +import { ExpressionPartitionVisPlugin } from './plugin'; export function plugin() { - return new ExpressionPiePlugin(); + return new ExpressionPartitionVisPlugin(); } -export type { ExpressionPiePluginSetup, ExpressionPiePluginStart } from './types'; +export type { ExpressionPartitionVisPluginSetup, ExpressionPartitionVisPluginStart } from './types'; diff --git a/src/plugins/chart_expressions/expression_partition_vis/server/plugin.ts b/src/plugins/chart_expressions/expression_partition_vis/server/plugin.ts new file mode 100755 index 0000000000000..190e43e4c7dfa --- /dev/null +++ b/src/plugins/chart_expressions/expression_partition_vis/server/plugin.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { CoreSetup, CoreStart, Plugin } from '../../../../core/server'; +import { + partitionLabelsFunction, + pieVisFunction, + treemapVisFunction, + mosaicVisFunction, + waffleVisFunction, +} from '../common'; +import { + ExpressionPartitionVisPluginSetup, + ExpressionPartitionVisPluginStart, + SetupDeps, + StartDeps, +} from './types'; + +export class ExpressionPartitionVisPlugin + implements + Plugin< + ExpressionPartitionVisPluginSetup, + ExpressionPartitionVisPluginStart, + SetupDeps, + StartDeps + > +{ + public setup(core: CoreSetup, { expressions }: SetupDeps) { + expressions.registerFunction(partitionLabelsFunction); + expressions.registerFunction(pieVisFunction); + expressions.registerFunction(treemapVisFunction); + expressions.registerFunction(mosaicVisFunction); + expressions.registerFunction(waffleVisFunction); + } + + public start(core: CoreStart, deps: StartDeps) {} + + public stop() {} +} diff --git a/src/plugins/chart_expressions/expression_pie/server/types.ts b/src/plugins/chart_expressions/expression_partition_vis/server/types.ts similarity index 84% rename from src/plugins/chart_expressions/expression_pie/server/types.ts rename to src/plugins/chart_expressions/expression_partition_vis/server/types.ts index ff3e00bdf6dab..0fdca6e6b319e 100755 --- a/src/plugins/chart_expressions/expression_pie/server/types.ts +++ b/src/plugins/chart_expressions/expression_partition_vis/server/types.ts @@ -7,8 +7,8 @@ */ import { ExpressionsServerStart, ExpressionsServerSetup } from '../../../expressions/server'; -export type ExpressionPiePluginSetup = void; -export type ExpressionPiePluginStart = void; +export type ExpressionPartitionVisPluginSetup = void; +export type ExpressionPartitionVisPluginStart = void; export interface SetupDeps { expressions: ExpressionsServerSetup; diff --git a/src/plugins/chart_expressions/expression_pie/tsconfig.json b/src/plugins/chart_expressions/expression_partition_vis/tsconfig.json similarity index 100% rename from src/plugins/chart_expressions/expression_pie/tsconfig.json rename to src/plugins/chart_expressions/expression_partition_vis/tsconfig.json diff --git a/src/plugins/chart_expressions/expression_pie/README.md b/src/plugins/chart_expressions/expression_pie/README.md deleted file mode 100755 index 95f4298aa293d..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# expressionPie - -Expression Pie plugin adds a `pie` renderer and function to the expression plugin. The renderer will display the `Pie` chart. - ---- - -## Development - -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/chart_expressions/expression_pie/common/constants.ts b/src/plugins/chart_expressions/expression_pie/common/constants.ts deleted file mode 100644 index c666692c3ea7f..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/common/constants.ts +++ /dev/null @@ -1,16 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export const PLUGIN_ID = 'expressionPie'; -export const PLUGIN_NAME = 'expressionPie'; - -export const PIE_VIS_EXPRESSION_NAME = 'pie_vis'; -export const PIE_LABELS_VALUE = 'pie_labels_value'; -export const PIE_LABELS_FUNCTION = 'pie_labels'; - -export const DEFAULT_PERCENT_DECIMALS = 2; diff --git a/src/plugins/chart_expressions/expression_pie/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap b/src/plugins/chart_expressions/expression_pie/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap deleted file mode 100644 index e4d994c058f0e..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/common/expression_functions/__snapshots__/pie_vis_function.test.ts.snap +++ /dev/null @@ -1,98 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`interpreter/functions#pie logs correct datatable to inspector 1`] = ` -Object { - "columns": Array [ - Object { - "id": "col-0-1", - "meta": Object { - "dimensionName": "Slice size", - }, - "name": "Count", - }, - ], - "rows": Array [ - Object { - "col-0-1": 0, - }, - ], - "type": "datatable", -} -`; - -exports[`interpreter/functions#pie returns an object with the correct structure 1`] = ` -Object { - "as": "pie_vis", - "type": "render", - "value": Object { - "params": Object { - "listenOnChange": true, - }, - "syncColors": false, - "visConfig": Object { - "addLegend": true, - "addTooltip": true, - "buckets": undefined, - "dimensions": Object { - "buckets": undefined, - "metric": Object { - "accessor": 0, - "format": Object { - "id": "number", - "params": Object {}, - }, - "type": "vis_dimension", - }, - "splitColumn": undefined, - "splitRow": undefined, - }, - "distinctColors": false, - "emptySizeRatio": 0.3, - "isDonut": true, - "labels": Object { - "last_level": false, - "percentDecimals": 2, - "position": "default", - "show": false, - "truncate": 100, - "type": "pie_labels_value", - "values": true, - "valuesFormat": "percent", - }, - "legendPosition": "right", - "maxLegendLines": 2, - "metric": Object { - "accessor": 0, - "format": Object { - "id": "number", - "params": Object {}, - }, - "type": "vis_dimension", - }, - "nestedLegend": true, - "palette": Object { - "name": "kibana_palette", - "type": "system_palette", - }, - "splitColumn": undefined, - "splitRow": undefined, - "truncateLegend": true, - }, - "visData": Object { - "columns": Array [ - Object { - "id": "col-0-1", - "name": "Count", - }, - ], - "rows": Array [ - Object { - "col-0-1": 0, - }, - ], - "type": "datatable", - }, - "visType": "pie", - }, -} -`; diff --git a/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_labels_function.ts b/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_labels_function.ts deleted file mode 100644 index 6bdb4f6b0408d..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_labels_function.ts +++ /dev/null @@ -1,88 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition, Datatable } from '../../../../expressions/common'; -import { PIE_LABELS_FUNCTION, PIE_LABELS_VALUE } from '../constants'; -import { ExpressionValuePieLabels, PieLabelsArguments } from '../types/expression_functions'; - -export const pieLabelsFunction = (): ExpressionFunctionDefinition< - typeof PIE_LABELS_FUNCTION, - Datatable | null, - PieLabelsArguments, - ExpressionValuePieLabels -> => ({ - name: PIE_LABELS_FUNCTION, - help: i18n.translate('expressionPie.pieLabels.function.help', { - defaultMessage: 'Generates the pie labels object', - }), - type: PIE_LABELS_VALUE, - args: { - show: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieLabels.function.args.show.help', { - defaultMessage: 'Displays the pie labels', - }), - default: true, - }, - position: { - types: ['string'], - default: 'default', - help: i18n.translate('expressionPie.pieLabels.function.args.position.help', { - defaultMessage: 'Defines the label position', - }), - }, - values: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieLabels.function.args.values.help', { - defaultMessage: 'Displays the values inside the slices', - }), - default: true, - }, - percentDecimals: { - types: ['number'], - help: i18n.translate('expressionPie.pieLabels.function.args.percentDecimals.help', { - defaultMessage: 'Defines the number of decimals that will appear on the values as percent', - }), - default: 2, - }, - lastLevel: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieLabels.function.args.lastLevel.help', { - defaultMessage: 'Show top level labels only', - }), - default: true, - }, - truncate: { - types: ['number'], - help: i18n.translate('expressionPie.pieLabels.function.args.truncate.help', { - defaultMessage: 'Defines the number of characters that the slice value will display', - }), - default: null, - }, - valuesFormat: { - types: ['string'], - default: 'percent', - help: i18n.translate('expressionPie.pieLabels.function.args.valuesFormat.help', { - defaultMessage: 'Defines the format of the values', - }), - }, - }, - fn: (context, args) => { - return { - type: PIE_LABELS_VALUE, - show: args.show, - position: args.position, - percentDecimals: args.percentDecimals, - values: args.values, - truncate: args.truncate, - valuesFormat: args.valuesFormat, - last_level: args.lastLevel, - }; - }, -}); diff --git a/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_vis_function.ts b/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_vis_function.ts deleted file mode 100644 index 1e5507c818449..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/common/expression_functions/pie_vis_function.ts +++ /dev/null @@ -1,183 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { EmptySizeRatios, PieVisParams } from '../types/expression_renderers'; -import { prepareLogTable } from '../../../../visualizations/common/prepare_log_table'; -import { PieVisExpressionFunctionDefinition } from '../types/expression_functions'; -import { PIE_LABELS_FUNCTION, PIE_LABELS_VALUE, PIE_VIS_EXPRESSION_NAME } from '../constants'; - -export const pieVisFunction = (): PieVisExpressionFunctionDefinition => ({ - name: PIE_VIS_EXPRESSION_NAME, - type: 'render', - inputTypes: ['datatable'], - help: i18n.translate('expressionPie.pieVis.function.help', { - defaultMessage: 'Pie visualization', - }), - args: { - metric: { - types: ['vis_dimension'], - help: i18n.translate('expressionPie.pieVis.function.args.metricHelpText', { - defaultMessage: 'Metric dimensions config', - }), - required: true, - }, - buckets: { - types: ['vis_dimension'], - help: i18n.translate('expressionPie.pieVis.function.args.bucketsHelpText', { - defaultMessage: 'Buckets dimensions config', - }), - multi: true, - }, - splitColumn: { - types: ['vis_dimension'], - help: i18n.translate('expressionPie.pieVis.function.args.splitColumnHelpText', { - defaultMessage: 'Split by column dimension config', - }), - multi: true, - }, - splitRow: { - types: ['vis_dimension'], - help: i18n.translate('expressionPie.pieVis.function.args.splitRowHelpText', { - defaultMessage: 'Split by row dimension config', - }), - multi: true, - }, - addTooltip: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieVis.function.args.addTooltipHelpText', { - defaultMessage: 'Show tooltip on slice hover', - }), - default: true, - }, - addLegend: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieVis.function.args.addLegendHelpText', { - defaultMessage: 'Show legend chart legend', - }), - }, - legendPosition: { - types: ['string'], - help: i18n.translate('expressionPie.pieVis.function.args.legendPositionHelpText', { - defaultMessage: 'Position the legend on top, bottom, left, right of the chart', - }), - }, - nestedLegend: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieVis.function.args.nestedLegendHelpText', { - defaultMessage: 'Show a more detailed legend', - }), - default: false, - }, - truncateLegend: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieVis.function.args.truncateLegendHelpText', { - defaultMessage: 'Defines if the legend items will be truncated or not', - }), - default: true, - }, - maxLegendLines: { - types: ['number'], - help: i18n.translate('expressionPie.pieVis.function.args.maxLegendLinesHelpText', { - defaultMessage: 'Defines the number of lines per legend item', - }), - }, - distinctColors: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieVis.function.args.distinctColorsHelpText', { - defaultMessage: - 'Maps different color per slice. Slices with the same value have the same color', - }), - default: false, - }, - isDonut: { - types: ['boolean'], - help: i18n.translate('expressionPie.pieVis.function.args.isDonutHelpText', { - defaultMessage: 'Displays the pie chart as donut', - }), - default: false, - }, - emptySizeRatio: { - types: ['number'], - help: i18n.translate('expressionPie.pieVis.function.args.emptySizeRatioHelpText', { - defaultMessage: 'Defines donut inner empty area size', - }), - default: EmptySizeRatios.SMALL, - }, - palette: { - types: ['palette', 'system_palette'], - help: i18n.translate('expressionPie.pieVis.function.args.paletteHelpText', { - defaultMessage: 'Defines the chart palette name', - }), - default: '{palette}', - }, - labels: { - types: [PIE_LABELS_VALUE], - help: i18n.translate('expressionPie.pieVis.function.args.labelsHelpText', { - defaultMessage: 'Pie labels config', - }), - default: `{${PIE_LABELS_FUNCTION}}`, - }, - }, - fn(context, args, handlers) { - const visConfig: PieVisParams = { - ...args, - palette: args.palette, - dimensions: { - metric: args.metric, - buckets: args.buckets, - splitColumn: args.splitColumn, - splitRow: args.splitRow, - }, - }; - - if (handlers?.inspectorAdapters?.tables) { - const logTable = prepareLogTable(context, [ - [ - [args.metric], - i18n.translate('expressionPie.pieVis.function.dimension.metric', { - defaultMessage: 'Slice size', - }), - ], - [ - args.buckets, - i18n.translate('expressionPie.pieVis.function.dimension.buckets', { - defaultMessage: 'Slice', - }), - ], - [ - args.splitColumn, - i18n.translate('expressionPie.pieVis.function.dimension.splitcolumn', { - defaultMessage: 'Column split', - }), - ], - [ - args.splitRow, - i18n.translate('expressionPie.pieVis.function.dimension.splitrow', { - defaultMessage: 'Row split', - }), - ], - ]); - handlers.inspectorAdapters.tables.logDatatable('default', logTable); - } - - return { - type: 'render', - as: PIE_VIS_EXPRESSION_NAME, - value: { - visData: context, - visConfig, - syncColors: handlers?.isSyncColorsEnabled?.() ?? false, - visType: 'pie', - params: { - listenOnChange: true, - }, - }, - }; - }, -}); diff --git a/src/plugins/chart_expressions/expression_pie/common/index.ts b/src/plugins/chart_expressions/expression_pie/common/index.ts deleted file mode 100755 index c5943c54c0c65..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/common/index.ts +++ /dev/null @@ -1,32 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { - PLUGIN_ID, - PLUGIN_NAME, - PIE_VIS_EXPRESSION_NAME, - PIE_LABELS_VALUE, - PIE_LABELS_FUNCTION, -} from './constants'; - -export { pieVisFunction, pieLabelsFunction } from './expression_functions'; - -export type { - ExpressionValuePieLabels, - PieVisExpressionFunctionDefinition, -} from './types/expression_functions'; - -export type { - PieVisParams, - PieVisConfig, - LabelsParams, - Dimension, - Dimensions, -} from './types/expression_renderers'; - -export { ValueFormats, LabelPositions, EmptySizeRatios } from './types/expression_renderers'; diff --git a/src/plugins/chart_expressions/expression_pie/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_pie/common/types/expression_functions.ts deleted file mode 100644 index 39d5392c65ed5..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/common/types/expression_functions.ts +++ /dev/null @@ -1,46 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PIE_LABELS_VALUE, PIE_VIS_EXPRESSION_NAME } from '../constants'; -import { - ExpressionFunctionDefinition, - Datatable, - ExpressionValueRender, - ExpressionValueBoxed, -} from '../../../../expressions/common'; -import { RenderValue, PieVisConfig, LabelPositions, ValueFormats } from './expression_renderers'; - -export interface PieLabelsArguments { - show: boolean; - position: LabelPositions; - values: boolean; - truncate: number | null; - valuesFormat: ValueFormats; - lastLevel: boolean; - percentDecimals: number; -} - -export type ExpressionValuePieLabels = ExpressionValueBoxed< - typeof PIE_LABELS_VALUE, - { - show: boolean; - position: LabelPositions; - values: boolean; - truncate: number | null; - valuesFormat: ValueFormats; - last_level: boolean; - percentDecimals: number; - } ->; - -export type PieVisExpressionFunctionDefinition = ExpressionFunctionDefinition< - typeof PIE_VIS_EXPRESSION_NAME, - Datatable, - PieVisConfig, - ExpressionValueRender ->; diff --git a/src/plugins/chart_expressions/expression_pie/public/__stories__/pie_renderer.stories.tsx b/src/plugins/chart_expressions/expression_pie/public/__stories__/pie_renderer.stories.tsx deleted file mode 100644 index 8afca1f9912f7..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/__stories__/pie_renderer.stories.tsx +++ /dev/null @@ -1,115 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { Datatable } from '../../../../expressions'; -import { Render } from '../../../../presentation_util/public/__stories__'; -import { getPieVisRenderer } from '../expression_renderers'; -import { LabelPositions, RenderValue, ValueFormats } from '../../common/types'; -import { palettes, theme, getStartDeps } from '../__mocks__'; - -const visData: Datatable = { - type: 'datatable', - columns: [ - { id: 'cost', name: 'cost', meta: { type: 'number' } }, - { id: 'age', name: 'age', meta: { type: 'number' } }, - { id: 'price', name: 'price', meta: { type: 'number' } }, - { id: 'project', name: 'project', meta: { type: 'string' } }, - { id: '@timestamp', name: '@timestamp', meta: { type: 'date' } }, - ], - rows: [ - { - cost: 32.15, - age: 63, - price: 53, - project: 'elasticsearch', - '@timestamp': 1546334211208, - }, - { - cost: 20.52, - age: 68, - price: 33, - project: 'beats', - '@timestamp': 1546351551031, - }, - { - cost: 21.15, - age: 57, - price: 59, - project: 'apm', - '@timestamp': 1546352631083, - }, - { - cost: 35.64, - age: 73, - price: 71, - project: 'machine-learning', - '@timestamp': 1546402490956, - }, - { - cost: 27.19, - age: 38, - price: 36, - project: 'kibana', - '@timestamp': 1546467111351, - }, - ], -}; - -const config: RenderValue = { - visType: 'pie_vis', - visData, - visConfig: { - dimensions: { - metric: { - type: 'vis_dimension', - accessor: { id: 'cost', name: 'cost', meta: { type: 'number' } }, - format: { id: 'number', params: {} }, - }, - buckets: [ - { - type: 'vis_dimension', - accessor: { id: 'age', name: 'age', meta: { type: 'number' } }, - format: { id: 'number', params: {} }, - }, - ], - }, - palette: { type: 'system_palette', name: 'default' }, - addTooltip: false, - addLegend: false, - legendPosition: 'right', - nestedLegend: false, - truncateLegend: false, - distinctColors: false, - isDonut: false, - emptySizeRatio: 0.37, - maxLegendLines: 1, - labels: { - show: false, - last_level: false, - position: LabelPositions.DEFAULT, - values: false, - truncate: null, - valuesFormat: ValueFormats.VALUE, - percentDecimals: 1, - }, - }, - syncColors: false, -}; - -const containerSize = { - width: '700px', - height: '700px', -}; - -const pieRenderer = getPieVisRenderer({ palettes, theme, getStartDeps }); - -storiesOf('renderers/pieVis', module).add('Default', () => { - return pieRenderer} config={config} {...containerSize} />; -}); diff --git a/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.tsx b/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.tsx deleted file mode 100644 index 4e7037cd9715d..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/components/pie_vis_component.tsx +++ /dev/null @@ -1,417 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { memo, useCallback, useMemo, useState, useEffect, useRef } from 'react'; -import { - Chart, - Datum, - LayerValue, - Partition, - Position, - Settings, - RenderChangeListener, - TooltipProps, - TooltipType, - SeriesIdentifier, - PartitionLayout, -} from '@elastic/charts'; -import { useEuiTheme } from '@elastic/eui'; -import { - LegendToggle, - ClickTriggerEvent, - ChartsPluginSetup, - PaletteRegistry, -} from '../../../../charts/public'; -import type { PersistedState } from '../../../../visualizations/public'; -import { - Datatable, - DatatableColumn, - IInterpreterRenderHandlers, -} from '../../../../expressions/public'; -import type { FieldFormat } from '../../../../field_formats/common'; -import { DEFAULT_PERCENT_DECIMALS } from '../../common/constants'; -import { - PieVisParams, - BucketColumns, - ValueFormats, - PieContainerDimensions, -} from '../../common/types/expression_renderers'; -import { - getColorPicker, - getLayers, - getLegendActions, - canFilter, - getFilterClickData, - getFilterEventData, - getPartitionTheme, - getColumns, - getSplitDimensionAccessor, - getColumnByAccessor, -} from '../utils'; -import { ChartSplit, SMALL_MULTIPLES_ID } from './chart_split'; -import { VisualizationNoResults } from './visualization_noresults'; -import { VisTypePiePluginStartDependencies } from '../plugin'; -import { pieChartWrapperStyle, pieChartContainerStyleFactory } from './pie_vis_component.styles'; - -declare global { - interface Window { - /** - * Flag used to enable debugState on elastic charts - */ - _echDebugStateFlag?: boolean; - } -} -export interface PieComponentProps { - visParams: PieVisParams; - visData: Datatable; - uiState: PersistedState; - fireEvent: IInterpreterRenderHandlers['event']; - renderComplete: IInterpreterRenderHandlers['done']; - chartsThemeService: ChartsPluginSetup['theme']; - palettesRegistry: PaletteRegistry; - services: VisTypePiePluginStartDependencies; - syncColors: boolean; -} - -const PieComponent = (props: PieComponentProps) => { - const theme = useEuiTheme(); - const chartTheme = props.chartsThemeService.useChartsTheme(); - const chartBaseTheme = props.chartsThemeService.useChartsBaseTheme(); - const [showLegend, setShowLegend] = useState(() => { - const bwcLegendStateDefault = - props.visParams.addLegend == null ? false : props.visParams.addLegend; - return props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) ?? bwcLegendStateDefault; - }); - const [dimensions, setDimensions] = useState(); - - const parentRef = useRef(null); - - useEffect(() => { - if (parentRef && parentRef.current) { - const parentHeight = parentRef.current!.getBoundingClientRect().height; - const parentWidth = parentRef.current!.getBoundingClientRect().width; - setDimensions({ width: parentWidth, height: parentHeight }); - } - }, [parentRef]); - - const onRenderChange = useCallback( - (isRendered) => { - if (isRendered) { - props.renderComplete(); - } - }, - [props] - ); - - // handles slice click event - const handleSliceClick = useCallback( - ( - clickedLayers: LayerValue[], - bucketColumns: Array>, - visData: Datatable, - splitChartDimension?: DatatableColumn, - splitChartFormatter?: FieldFormat - ): void => { - const data = getFilterClickData( - clickedLayers, - bucketColumns, - visData, - splitChartDimension, - splitChartFormatter - ); - const event = { - name: 'filterBucket', - data: { data }, - }; - props.fireEvent(event); - }, - [props] - ); - - // handles legend action event data - const getLegendActionEventData = useCallback( - (visData: Datatable) => - (series: SeriesIdentifier): ClickTriggerEvent | null => { - const data = getFilterEventData(visData, series); - - return { - name: 'filterBucket', - data: { - negate: false, - data, - }, - }; - }, - [] - ); - - const handleLegendAction = useCallback( - (event: ClickTriggerEvent, negate = false) => { - props.fireEvent({ - ...event, - data: { - ...event.data, - negate, - }, - }); - }, - [props] - ); - - const toggleLegend = useCallback(() => { - setShowLegend((value) => { - const newValue = !value; - props.uiState?.set('vis.legendOpen', newValue); - return newValue; - }); - }, [props.uiState]); - - useEffect(() => { - setShowLegend(props.visParams.addLegend); - props.uiState?.set('vis.legendOpen', props.visParams.addLegend); - }, [props.uiState, props.visParams.addLegend]); - - const setColor = useCallback( - (newColor: string | null, seriesLabel: string | number) => { - const colors = props.uiState?.get('vis.colors') || {}; - if (colors[seriesLabel] === newColor || !newColor) { - delete colors[seriesLabel]; - } else { - colors[seriesLabel] = newColor; - } - props.uiState?.setSilent('vis.colors', null); - props.uiState?.set('vis.colors', colors); - props.uiState?.emit('reload'); - }, - [props.uiState] - ); - - const { visData, visParams, services, syncColors } = props; - - function getSliceValue(d: Datum, metricColumn: DatatableColumn) { - const value = d[metricColumn.id]; - return Number.isFinite(value) && value >= 0 ? value : 0; - } - - // formatters - const metricFieldFormatter = services.fieldFormats.deserialize( - visParams.dimensions.metric.format - ); - const splitChartFormatter = visParams.dimensions.splitColumn - ? services.fieldFormats.deserialize(visParams.dimensions.splitColumn[0].format) - : visParams.dimensions.splitRow - ? services.fieldFormats.deserialize(visParams.dimensions.splitRow[0].format) - : undefined; - const percentFormatter = services.fieldFormats.deserialize({ - id: 'percent', - params: { - pattern: `0,0.[${'0'.repeat(visParams.labels.percentDecimals ?? DEFAULT_PERCENT_DECIMALS)}]%`, - }, - }); - - const { bucketColumns, metricColumn } = useMemo( - () => getColumns(visParams, visData), - [visData, visParams] - ); - - const layers = useMemo( - () => - getLayers( - bucketColumns, - visParams, - props.uiState?.get('vis.colors', {}), - visData.rows, - props.palettesRegistry, - services.fieldFormats, - syncColors - ), - [ - bucketColumns, - visParams, - props.uiState, - props.palettesRegistry, - visData.rows, - services.fieldFormats, - syncColors, - ] - ); - - const rescaleFactor = useMemo(() => { - const overallSum = visData.rows.reduce((sum, row) => sum + row[metricColumn.id], 0); - const slices = visData.rows.map((row) => row[metricColumn.id] / overallSum); - const smallSlices = slices.filter((value) => value < 0.02).length; - if (smallSlices) { - // shrink up to 20% to give some room for the linked values - return 1 / (1 + Math.min(smallSlices * 0.05, 0.2)); - } - return 1; - }, [visData.rows, metricColumn]); - - const themeOverrides = useMemo( - () => getPartitionTheme(visParams, chartTheme, dimensions, rescaleFactor), - [chartTheme, visParams, dimensions, rescaleFactor] - ); - const tooltip: TooltipProps = { - type: visParams.addTooltip ? TooltipType.Follow : TooltipType.None, - }; - const legendPosition = visParams.legendPosition ?? Position.Right; - - const legendColorPicker = useMemo( - () => - getColorPicker( - legendPosition, - setColor, - bucketColumns, - visParams.palette.name, - visData.rows, - props.uiState, - visParams.distinctColors - ), - [ - legendPosition, - setColor, - bucketColumns, - visParams.palette.name, - visParams.distinctColors, - visData.rows, - props.uiState, - ] - ); - - const splitChartColumnAccessor = visParams.dimensions.splitColumn - ? getSplitDimensionAccessor( - services.fieldFormats, - visData.columns - )(visParams.dimensions.splitColumn[0]) - : undefined; - const splitChartRowAccessor = visParams.dimensions.splitRow - ? getSplitDimensionAccessor( - services.fieldFormats, - visData.columns - )(visParams.dimensions.splitRow[0]) - : undefined; - - const splitChartDimension = visParams.dimensions.splitColumn - ? getColumnByAccessor(visParams.dimensions.splitColumn[0].accessor, visData.columns) - : visParams.dimensions.splitRow - ? getColumnByAccessor(visParams.dimensions.splitRow[0].accessor, visData.columns) - : undefined; - - /** - * Checks whether data have all zero values. - * If so, the no data container is loaded. - */ - const isAllZeros = useMemo( - () => visData.rows.every((row) => row[metricColumn.id] === 0), - [visData.rows, metricColumn] - ); - - /** - * Checks whether data have negative values. - * If so, the no data container is loaded. - */ - const hasNegative = useMemo( - () => - visData.rows.some((row) => { - const value = row[metricColumn.id]; - return typeof value === 'number' && value < 0; - }), - [visData.rows, metricColumn] - ); - - const canShowPieChart = !isAllZeros && !hasNegative; - - return ( -
- {!canShowPieChart ? ( - - ) : ( -
- - - - { - handleSliceClick( - args[0][0] as LayerValue[], - bucketColumns, - visData, - splitChartDimension, - splitChartFormatter - ); - }} - legendAction={getLegendActions( - canFilter, - getLegendActionEventData(visData), - handleLegendAction, - visParams, - services.data.actions, - services.fieldFormats - )} - theme={[ - // Chart background should be transparent for the usage at Canvas. - { background: { color: 'transparent' } }, - themeOverrides, - chartTheme, - { - legend: { - labelOptions: { - maxLines: visParams.truncateLegend ? visParams.maxLegendLines ?? 1 : 0, - }, - }, - }, - ]} - baseTheme={chartBaseTheme} - onRenderChange={onRenderChange} - /> - getSliceValue(d, metricColumn)} - percentFormatter={(d: number) => percentFormatter.convert(d / 100)} - valueGetter={ - !visParams.labels.show || - visParams.labels.valuesFormat === ValueFormats.VALUE || - !visParams.labels.values - ? undefined - : 'percent' - } - valueFormatter={(d: number) => - !visParams.labels.show || !visParams.labels.values - ? '' - : metricFieldFormatter.convert(d) - } - layers={layers} - topGroove={!visParams.labels.show ? 0 : undefined} - /> - -
- )} -
- ); -}; - -// eslint-disable-next-line import/no-default-export -export default memo(PieComponent); diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_color_picker.tsx b/src/plugins/chart_expressions/expression_pie/public/utils/get_color_picker.tsx deleted file mode 100644 index 7a1438fac2458..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_color_picker.tsx +++ /dev/null @@ -1,118 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useCallback } from 'react'; -import Color from 'color'; -import { LegendColorPicker, Position } from '@elastic/charts'; -import { PopoverAnchorPosition, EuiPopover, EuiOutsideClickDetector } from '@elastic/eui'; -import type { DatatableRow } from '../../../../expressions/public'; -import type { PersistedState } from '../../../../visualizations/public'; -import { ColorPicker } from '../../../../charts/public'; -import { BucketColumns } from '../../common/types'; - -const KEY_CODE_ENTER = 13; - -function getAnchorPosition(legendPosition: Position): PopoverAnchorPosition { - switch (legendPosition) { - case Position.Bottom: - return 'upCenter'; - case Position.Top: - return 'downCenter'; - case Position.Left: - return 'rightCenter'; - default: - return 'leftCenter'; - } -} - -function getLayerIndex( - seriesKey: string, - data: DatatableRow[], - layers: Array> -): number { - const row = data.find((d) => Object.keys(d).find((key) => d[key] === seriesKey)); - const bucketId = row && Object.keys(row).find((key) => row[key] === seriesKey); - return layers.findIndex((layer) => layer.id === bucketId) + 1; -} - -function isOnInnerLayer( - firstBucket: Partial, - data: DatatableRow[], - seriesKey: string -): DatatableRow | undefined { - return data.find((d) => firstBucket.id && d[firstBucket.id] === seriesKey); -} - -export const getColorPicker = - ( - legendPosition: Position, - setColor: (newColor: string | null, seriesKey: string | number) => void, - bucketColumns: Array>, - palette: string, - data: DatatableRow[], - uiState: PersistedState, - distinctColors: boolean - ): LegendColorPicker => - ({ anchor, color, onClose, onChange, seriesIdentifiers: [seriesIdentifier] }) => { - const seriesName = seriesIdentifier.key; - const overwriteColors: Record = uiState?.get('vis.colors', {}) ?? {}; - const colorIsOverwritten = Object.keys(overwriteColors).includes(seriesName.toString()); - let keyDownEventOn = false; - const handleChange = (newColor: string | null) => { - if (newColor) { - onChange(newColor); - } - setColor(newColor, seriesName); - // close the popover if no color is applied or the user has clicked a color - if (!newColor || !keyDownEventOn) { - onClose(); - } - }; - - const onKeyDown = (e: React.KeyboardEvent) => { - if (e.keyCode === KEY_CODE_ENTER) { - onClose?.(); - } - keyDownEventOn = true; - }; - - const handleOutsideClick = useCallback(() => { - onClose?.(); - }, [onClose]); - - if (!distinctColors) { - const enablePicker = - isOnInnerLayer(bucketColumns[0], data, seriesName) || !bucketColumns[0].id; - if (!enablePicker) return null; - } - const hexColor = new Color(color).hex(); - return ( - - - - - - ); - }; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_layers.ts b/src/plugins/chart_expressions/expression_pie/public/utils/get_layers.ts deleted file mode 100644 index 9268f5631e735..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_layers.ts +++ /dev/null @@ -1,201 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Datum, PartitionLayer, ShapeTreeNode, ArrayEntry } from '@elastic/charts'; -import { isEqual } from 'lodash'; -import type { FieldFormatsStart } from 'src/plugins/field_formats/public'; -import { SeriesLayer, PaletteRegistry, lightenColor } from '../../../../charts/public'; -import type { DatatableRow } from '../../../../expressions/public'; -import type { BucketColumns, PieVisParams, SplitDimensionParams } from '../../common/types'; -import { getDistinctSeries } from './get_distinct_series'; - -const EMPTY_SLICE = Symbol('empty_slice'); - -export const computeColor = ( - d: ShapeTreeNode, - isSplitChart: boolean, - overwriteColors: { [key: string]: string } = {}, - columns: Array>, - rows: DatatableRow[], - visParams: PieVisParams, - palettes: PaletteRegistry | null, - syncColors: boolean, - formatter: FieldFormatsStart, - format?: BucketColumns['format'] -) => { - const { parentSeries, allSeries } = getDistinctSeries(rows, columns); - const dataName = d.dataName; - - let formattedLabel = ''; - if (format) { - formattedLabel = formatter.deserialize(format).convert(dataName) ?? ''; - } - - if (visParams.distinctColors) { - let overwriteColor; - // this is for supporting old visualizations (created by vislib plugin) - // it seems that there for some aggs, the uiState saved from vislib is - // different than the es-charts handle it - if (overwriteColors.hasOwnProperty(formattedLabel)) { - overwriteColor = overwriteColors[formattedLabel]; - } - - if (Object.keys(overwriteColors).includes(dataName.toString())) { - overwriteColor = overwriteColors[dataName]; - } - - if (overwriteColor) { - return overwriteColor; - } - - const index = allSeries.findIndex((name) => isEqual(name, dataName)); - const isSplitParentLayer = isSplitChart && parentSeries.includes(dataName); - return palettes?.get(visParams.palette.name).getCategoricalColor( - [ - { - name: dataName, - rankAtDepth: isSplitParentLayer - ? parentSeries.findIndex((name) => name === dataName) - : index > -1 - ? index - : 0, - totalSeriesAtDepth: isSplitParentLayer ? parentSeries.length : allSeries.length || 1, - }, - ], - { - maxDepth: 1, - totalSeries: allSeries.length || 1, - behindText: visParams.labels.show, - syncColors, - }, - visParams.palette?.params ?? { colors: [] } - ); - } - const seriesLayers: SeriesLayer[] = []; - let tempParent: typeof d | typeof d['parent'] = d; - while (tempParent.parent && tempParent.depth > 0) { - const seriesName = String(tempParent.parent.children[tempParent.sortIndex][0]); - const isSplitParentLayer = isSplitChart && parentSeries.includes(seriesName); - seriesLayers.unshift({ - name: seriesName, - rankAtDepth: isSplitParentLayer - ? parentSeries.findIndex((name) => name === seriesName) - : tempParent.sortIndex, - totalSeriesAtDepth: isSplitParentLayer - ? parentSeries.length - : tempParent.parent.children.length, - }); - tempParent = tempParent.parent; - } - - let overwriteColor; - // this is for supporting old visualizations (created by vislib plugin) - // it seems that there for some aggs, the uiState saved from vislib is - // different than the es-charts handle it - if (overwriteColors.hasOwnProperty(formattedLabel)) { - overwriteColor = overwriteColors[formattedLabel]; - } - - seriesLayers.forEach((layer) => { - if (Object.keys(overwriteColors).includes(layer.name)) { - overwriteColor = overwriteColors[layer.name]; - } - }); - - if (overwriteColor) { - return lightenColor(overwriteColor, seriesLayers.length, columns.length); - } - return palettes?.get(visParams.palette.name).getCategoricalColor( - seriesLayers, - { - behindText: visParams.labels.show, - maxDepth: columns.length, - totalSeries: rows.length, - syncColors, - }, - visParams.palette?.params ?? { colors: [] } - ); -}; - -export const getLayers = ( - columns: Array>, - visParams: PieVisParams, - overwriteColors: { [key: string]: string } = {}, - rows: DatatableRow[], - palettes: PaletteRegistry | null, - formatter: FieldFormatsStart, - syncColors: boolean -): PartitionLayer[] => { - const fillLabel: PartitionLayer['fillLabel'] = { - valueFont: { - fontWeight: 700, - }, - }; - - if (!visParams.labels.values) { - fillLabel.valueFormatter = () => ''; - } - const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow); - return columns.map((col) => { - return { - groupByRollup: (d: Datum) => { - return col.id ? d[col.id] : col.name; - }, - showAccessor: (d: Datum) => d !== EMPTY_SLICE, - nodeLabel: (d: unknown) => { - if (col.format) { - return formatter.deserialize(col.format).convert(d) ?? ''; - } - return String(d); - }, - sortPredicate: ([name1, node1]: ArrayEntry, [name2, node2]: ArrayEntry) => { - const params = col.meta?.sourceParams?.params as SplitDimensionParams | undefined; - const sort: string | undefined = params?.orderBy; - // unconditionally put "Other" to the end (as the "Other" slice may be larger than a regular slice, yet should be at the end) - if (name1 === '__other__' && name2 !== '__other__') return 1; - if (name2 === '__other__' && name1 !== '__other__') return -1; - // metric sorting - if (sort && sort !== '_key') { - if (params?.order === 'desc') { - return node2.value - node1.value; - } else { - return node1.value - node2.value; - } - // alphabetical sorting - } else { - if (name1 > name2) { - return params?.order === 'desc' ? -1 : 1; - } - if (name2 > name1) { - return params?.order === 'desc' ? 1 : -1; - } - } - return 0; - }, - fillLabel, - shape: { - fillColor: (d) => { - const outputColor = computeColor( - d, - isSplitChart, - overwriteColors, - columns, - rows, - visParams, - palettes, - syncColors, - formatter, - col.format - ); - - return outputColor || 'rgba(0,0,0,0)'; - }, - }, - }; - }); -}; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_partition_theme.test.ts b/src/plugins/chart_expressions/expression_pie/public/utils/get_partition_theme.test.ts deleted file mode 100644 index 1cccdf8a5e47b..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_partition_theme.test.ts +++ /dev/null @@ -1,72 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getPartitionTheme } from './get_partition_theme'; -import { createMockPieParams } from '../mocks'; - -const visParams = createMockPieParams(); - -describe('getConfig', () => { - it('should cap the outerSizeRatio to 1', () => { - expect( - getPartitionTheme(visParams, {}, { width: 400, height: 400 }).partition?.outerSizeRatio - ).toBe(1); - }); - - it('should not have outerSizeRatio for split chart', () => { - expect( - getPartitionTheme( - { - ...visParams, - dimensions: { - ...visParams.dimensions, - splitColumn: [ - { - type: 'vis_dimension', - accessor: 1, - format: { - id: 'number', - params: {}, - }, - }, - ], - }, - }, - {}, - { width: 400, height: 400 } - ).partition?.outerSizeRatio - ).toBeUndefined(); - - expect( - getPartitionTheme( - { - ...visParams, - dimensions: { - ...visParams.dimensions, - splitRow: [ - { - type: 'vis_dimension', - accessor: 1, - format: { - id: 'number', - params: {}, - }, - }, - ], - }, - }, - {}, - { width: 400, height: 400 } - ).partition?.outerSizeRatio - ).toBeUndefined(); - }); - - it('should not set outerSizeRatio if dimensions are not defined', () => { - expect(getPartitionTheme(visParams, {}).partition?.outerSizeRatio).toBeUndefined(); - }); -}); diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_partition_theme.ts b/src/plugins/chart_expressions/expression_pie/public/utils/get_partition_theme.ts deleted file mode 100644 index 4daaf835fa782..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_partition_theme.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { PartialTheme } from '@elastic/charts'; -import { Required } from '@kbn/utility-types'; -import { LabelPositions, PieVisParams, PieContainerDimensions } from '../../common/types'; - -const MAX_SIZE = 1000; - -export const getPartitionTheme = ( - visParams: PieVisParams, - chartTheme: PartialTheme, - dimensions?: PieContainerDimensions, - rescaleFactor: number = 1 -): PartialTheme => { - // On small multiples we want the labels to only appear inside - const isSplitChart = Boolean(visParams.dimensions.splitColumn || visParams.dimensions.splitRow); - const paddingProps: PartialTheme | null = - dimensions && !isSplitChart - ? { - chartPaddings: { - // TODO: simplify ratio logic to be static px units - top: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height, - bottom: ((1 - Math.min(1, MAX_SIZE / dimensions?.height)) / 2) * dimensions?.height, - left: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height, - right: ((1 - Math.min(1, MAX_SIZE / dimensions?.width)) / 2) * dimensions?.height, - }, - } - : null; - - const outerSizeRatio: PartialTheme['partition'] | null = - dimensions && !isSplitChart - ? { - outerSizeRatio: - // Cap the ratio to 1 and then rescale - rescaleFactor * Math.min(MAX_SIZE / Math.min(dimensions?.width, dimensions?.height), 1), - } - : null; - const theme: Required = { - chartMargins: { top: 0, bottom: 0, left: 0, right: 0 }, - ...paddingProps, - partition: { - fontFamily: chartTheme.barSeriesStyle?.displayValue?.fontFamily, - ...outerSizeRatio, - minFontSize: 10, - maxFontSize: 16, - linkLabel: { - maxCount: 5, - fontSize: 11, - textColor: chartTheme.axes?.axisTitle?.fill, - maxTextLength: visParams.labels.truncate ?? undefined, - }, - sectorLineStroke: chartTheme.lineSeriesStyle?.point?.fill, - sectorLineWidth: 1.5, - circlePadding: 4, - emptySizeRatio: visParams.isDonut ? visParams.emptySizeRatio : 0, - }, - }; - if (!visParams.labels.show) { - // Force all labels to be linked, then prevent links from showing - theme.partition.linkLabel = { maxCount: 0, maximumSection: Number.POSITIVE_INFINITY }; - } - - if (visParams.labels.last_level && visParams.labels.show) { - theme.partition.linkLabel = { - maxCount: Number.POSITIVE_INFINITY, - maximumSection: Number.POSITIVE_INFINITY, - maxTextLength: visParams.labels.truncate ?? undefined, - }; - } - - if ( - (visParams.labels.position === LabelPositions.INSIDE || isSplitChart) && - visParams.labels.show - ) { - theme.partition.linkLabel = { maxCount: 0 }; - } - - return theme; -}; diff --git a/src/plugins/chart_expressions/expression_pie/public/utils/get_split_dimension_accessor.ts b/src/plugins/chart_expressions/expression_pie/public/utils/get_split_dimension_accessor.ts deleted file mode 100644 index ebc1979908459..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/public/utils/get_split_dimension_accessor.ts +++ /dev/null @@ -1,31 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import { AccessorFn } from '@elastic/charts'; -import { getColumnByAccessor } from './accessor'; -import { DatatableColumn } from '../../../../expressions/public'; -import type { FieldFormatsStart } from '../../../../field_formats/public'; -import { ExpressionValueVisDimension } from '../../../../visualizations/common'; - -export const getSplitDimensionAccessor = - (fieldFormats: FieldFormatsStart, columns: DatatableColumn[]) => - (splitDimension: ExpressionValueVisDimension): AccessorFn => { - const formatter = fieldFormats.deserialize(splitDimension.format); - const splitChartColumn = getColumnByAccessor(splitDimension.accessor, columns); - const accessor = splitChartColumn.id; - - const fn: AccessorFn = (d) => { - const v = d[accessor]; - if (v === undefined) { - return; - } - const f = formatter.convert(v); - return f; - }; - - return fn; - }; diff --git a/src/plugins/chart_expressions/expression_pie/server/plugin.ts b/src/plugins/chart_expressions/expression_pie/server/plugin.ts deleted file mode 100755 index f46983f0f8825..0000000000000 --- a/src/plugins/chart_expressions/expression_pie/server/plugin.ts +++ /dev/null @@ -1,23 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ -import { CoreSetup, CoreStart, Plugin } from '../../../../core/server'; -import { pieLabelsFunction, pieVisFunction } from '../common'; -import { ExpressionPiePluginSetup, ExpressionPiePluginStart, SetupDeps, StartDeps } from './types'; - -export class ExpressionPiePlugin - implements Plugin -{ - public setup(core: CoreSetup, { expressions }: SetupDeps) { - expressions.registerFunction(pieLabelsFunction); - expressions.registerFunction(pieVisFunction); - } - - public start(core: CoreStart, deps: StartDeps) {} - - public stop() {} -} diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx index d39ffb9755445..37da5f93ff675 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -16,7 +16,7 @@ import * as sinon from 'sinon'; import { serviceContextMock } from '../../../../contexts/services_context.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { ServicesContextProvider, EditorContextProvider, diff --git a/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx b/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx index a2a55404072eb..1dc490836808b 100644 --- a/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx +++ b/src/plugins/dashboard/public/application/actions/library_notification_popover.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DashboardContainer } from '../embeddable/dashboard_container'; import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 5f50cfd842b67..761db3ca47ff8 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { findTestSubject, nextTick } from '@kbn/test/jest'; +import { findTestSubject, nextTick } from '@kbn/test-jest-helpers'; import { DashboardContainer, DashboardContainerServices } from './dashboard_container'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; import { I18nProvider } from '@kbn/i18n-react'; diff --git a/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx b/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx index 3b4e2c06ab0a8..0b3a569813845 100644 --- a/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/empty_screen/dashboard_empty_screen.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; import { coreMock } from '../../../../../../core/public/mocks'; diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index 59f346caf4b0d..4717082cbe0d0 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -10,7 +10,7 @@ import sizeMe from 'react-sizeme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { skip } from 'rxjs/operators'; import { DashboardGrid, DashboardGridProps } from './dashboard_grid'; import { DashboardContainer, DashboardContainerServices } from '../dashboard_container'; diff --git a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index d9de67ee9455d..9075a7f90b103 100644 --- a/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { skip } from 'rxjs/operators'; import { mount } from 'enzyme'; import { I18nProvider } from '@kbn/i18n-react'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DashboardViewport, DashboardViewportProps } from './dashboard_viewport'; import { DashboardContainer, DashboardContainerServices } from '../dashboard_container'; import { getSampleDashboardInput } from '../../test_helpers'; diff --git a/src/plugins/dashboard/public/application/top_nav/clone_modal.test.js b/src/plugins/dashboard/public/application/top_nav/clone_modal.test.js index d7e35df65a7d2..0aab14334a997 100644 --- a/src/plugins/dashboard/public/application/top_nav/clone_modal.test.js +++ b/src/plugins/dashboard/public/application/top_nav/clone_modal.test.js @@ -8,7 +8,7 @@ import React from 'react'; import sinon from 'sinon'; -import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DashboardCloneModal } from './clone_modal'; diff --git a/src/plugins/dashboard/public/application/top_nav/save_modal.test.js b/src/plugins/dashboard/public/application/top_nav/save_modal.test.js index a532506595a3d..5a8cb8d56db69 100644 --- a/src/plugins/dashboard/public/application/top_nav/save_modal.test.js +++ b/src/plugins/dashboard/public/application/top_nav/save_modal.test.js @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; jest.mock('../../../../saved_objects/public', () => ({ SavedObjectSaveModal: () => null, diff --git a/src/plugins/dashboard/server/ui_settings.ts b/src/plugins/dashboard/server/ui_settings.ts index 99eb29a27deaa..6efd68da25f2d 100644 --- a/src/plugins/dashboard/server/ui_settings.ts +++ b/src/plugins/dashboard/server/ui_settings.ts @@ -22,7 +22,7 @@ export const getUISettings = (): Record> => ({ }), description: i18n.translate('dashboard.labs.enableLabsDescription', { defaultMessage: - 'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Dashboard.', + 'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Dashboard.', }), value: false, type: 'boolean', diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index 404249d56d638..7bb4b78850dcd 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -35,7 +35,6 @@ export type { GetFieldsOptions, GetFieldsOptionsTimePattern, IDataViewsApiClient, - IIndexPatternsApiClient, SavedObject, AggregationRestrictions, TypeMeta, @@ -43,9 +42,7 @@ export type { FieldSpecExportFmt, FieldSpec, DataViewFieldMap, - IndexPatternFieldMap, DataViewSpec, - IndexPatternSpec, SourceFilter, IndexPatternExpressionType, IndexPatternLoadStartDependencies, diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 7037af5ce54b1..4b7b447d2c8be 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -78,7 +78,6 @@ export type { IFieldType, IndexPatternAttributes, AggregationRestrictions as IndexPatternAggRestrictions, - IndexPatternSpec, IndexPatternLoadExpressionFunctionDefinition, GetFieldsOptions, AggregationRestrictions, diff --git a/src/plugins/data/public/query/query_string/query_string_manager.test.ts b/src/plugins/data/public/query/query_string/query_string_manager.test.ts index 54f0eb06fb04e..32852b4fe7c32 100644 --- a/src/plugins/data/public/query/query_string/query_string_manager.test.ts +++ b/src/plugins/data/public/query/query_string/query_string_manager.test.ts @@ -8,7 +8,7 @@ import { QueryStringManager } from './query_string_manager'; import { Storage } from '../../../../kibana_utils/public/storage'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { coreMock } from '../../../../../core/public/mocks'; import { Query } from '../../../common/query'; diff --git a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts index 857a932d9157b..32e9ec7fb374a 100644 --- a/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts +++ b/src/plugins/data/public/query/state_sync/connect_to_query_state.test.ts @@ -13,7 +13,7 @@ import { Filter, FilterStateStore, UI_SETTINGS } from '../../../common'; import { coreMock } from '../../../../../core/public/mocks'; import { BaseStateContainer, createStateContainer, Storage } from '../../../../kibana_utils/public'; import { QueryService, QueryStart } from '../query_service'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { connectToQueryState } from './connect_to_query_state'; import { TimefilterContract } from '../timefilter'; import { QueryState } from './types'; diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts index 2e48a11efd69c..2442ca68f997d 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts @@ -18,7 +18,7 @@ import { Storage, } from '../../../../kibana_utils/public'; import { QueryService, QueryStart } from '../query_service'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { TimefilterContract } from '../timefilter'; import { syncQueryStateWithUrl } from './sync_state_with_url'; import { QueryState } from './types'; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx index 3a0c0db96c5de..8f48bef8e0e54 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/filter_editor.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { FilterEditor, Props } from '.'; import React from 'react'; diff --git a/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx index acbd48718d92e..67d36f465568d 100644 --- a/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { QueryLanguageSwitcher, QueryLanguageSwitcherProps } from './language_switcher'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { coreMock } from '../../../../../core/public/mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiButtonEmpty, EuiIcon, EuiPopover } from '@elastic/eui'; const startMock = coreMock.createStart(); diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx index 53edaa8d9bf4f..f80fc68494091 100644 --- a/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl as mount } from '@kbn/test/jest'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { NoDataPopover } from './no_data_popover'; import { EuiTourStep } from '@elastic/eui'; import { act } from 'react-dom/test-utils'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx index 94efcefb719a8..535b7ea66237b 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_description.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ShardFailureDescription } from './shard_failure_description'; import { shardFailureResponse } from './__mocks__/shard_failure_response'; import { ShardFailure } from './shard_failure_types'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.test.tsx index 35933250276c7..d4b30d5a1923b 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_modal.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ShardFailureModal } from './shard_failure_modal'; import { shardFailureRequest } from './__mocks__/shard_failure_request'; import { shardFailureResponse } from './__mocks__/shard_failure_response'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx index b8289bc23cf01..0cb71f9097f9e 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_open_modal_button.test.tsx @@ -9,7 +9,7 @@ import { openModal } from './shard_failure_open_modal_button.test.mocks'; import React from 'react'; import { themeServiceMock } from 'src/core/public/mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import ShardFailureOpenModalButton from './shard_failure_open_modal_button'; import { shardFailureRequest } from './__mocks__/shard_failure_request'; import { shardFailureResponse } from './__mocks__/shard_failure_response'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx index 32f245de53e7b..c4a53f850ab6a 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/shard_failure_table.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ShardFailureTable } from './shard_failure_table'; import { shardFailureResponse } from './__mocks__/shard_failure_response'; import { ShardFailure } from './shard_failure_types'; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx index e6b40bcb5936c..eb7f51aad8223 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_view.test.tsx @@ -8,7 +8,7 @@ import React, { Suspense } from 'react'; import { getTableViewDescription } from '../index'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TablesAdapter } from '../../../../../expressions/common'; jest.mock('../../../../../share/public', () => ({ diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 51dd883211620..ea6a7bc3446e3 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -94,7 +94,7 @@ export class DataServerPlugin this.autocompleteService.setup(core); this.kqlTelemetryService.setup(core, { usageCollection }); - core.uiSettings.register(getUiSettings()); + core.uiSettings.register(getUiSettings(core.docLinks)); const searchSetup = this.searchService.setup(core, { bfetch, diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 19ecde71e6a19..4783223e49869 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { UiSettingsParams } from 'kibana/server'; +import type { DocLinksServiceSetup, UiSettingsParams } from 'kibana/server'; import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../common'; const luceneQueryLanguageLabel = i18n.translate('data.advancedSettings.searchQueryLanguageLucene', { @@ -31,7 +31,9 @@ const requestPreferenceOptionLabels = { }), }; -export function getUiSettings(): Record> { +export function getUiSettings( + docLinks: DocLinksServiceSetup +): Record> { return { [UI_SETTINGS.META_FIELDS]: { name: i18n.translate('data.advancedSettings.metaFieldsTitle', { @@ -71,7 +73,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.query.queryStringOptionsText', values: { optionsLink: - '' + + `` + i18n.translate('data.advancedSettings.query.queryStringOptions.optionsLinkText', { defaultMessage: 'Options', }) + @@ -150,7 +152,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.sortOptionsText', values: { optionsLink: - '' + + `` + i18n.translate('data.advancedSettings.sortOptions.optionsLinkText', { defaultMessage: 'Options', }) + @@ -232,7 +234,7 @@ export function getUiSettings(): Record> { setRequestReferenceSetting: `${UI_SETTINGS.COURIER_SET_REQUEST_PREFERENCE}`, customSettingValue: '"custom"', requestPreferenceLink: - '' + + `` + i18n.translate( 'data.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText', { @@ -256,7 +258,7 @@ export function getUiSettings(): Record> { 'Controls the {maxRequestsLink} setting used for _msearch requests sent by Kibana. ' + 'Set to 0 to disable this config and use the Elasticsearch default.', values: { - maxRequestsLink: `max_concurrent_shard_requests`, }, }), @@ -265,7 +267,7 @@ export function getUiSettings(): Record> { }, [UI_SETTINGS.SEARCH_INCLUDE_FROZEN]: { name: 'Search in frozen indices', - description: `Will include frozen indices in results if enabled. Searching through frozen indices might increase the search time.`, value: false, @@ -444,7 +446,7 @@ export function getUiSettings(): Record> { 'data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', values: { acceptedFormatsLink: - `` + i18n.translate('data.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText', { defaultMessage: 'accepted formats', @@ -495,7 +497,7 @@ export function getUiSettings(): Record> { 'Elasticsearch terms aggregation. {learnMoreLink}', values: { learnMoreLink: - '' + + `` + i18n.translate('data.advancedSettings.autocompleteValueSuggestionMethodLink', { defaultMessage: 'Learn more.', }) + @@ -517,7 +519,7 @@ export function getUiSettings(): Record> { 'Disable this property to get autocomplete suggestions from your full dataset, rather than from the current time range. {learnMoreLink}', values: { learnMoreLink: - '' + + `` + i18n.translate('data.advancedSettings.autocompleteValueSuggestionMethodLearnMoreLink', { defaultMessage: 'Learn more.', }) + diff --git a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx index c28b03de77adc..0c5381e99b8fa 100644 --- a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx @@ -51,7 +51,7 @@ export interface Props { /** * Handler for the "save" footer button */ - onSave: (indexPatternSpec: DataViewSpec) => void; + onSave: (dataViewSpec: DataViewSpec) => void; /** * Handler for the "cancel" footer button */ diff --git a/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx b/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx index f5a996a441515..3de6995267bb3 100644 --- a/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx +++ b/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_list_prompt/empty_index_list_prompt.test.tsx @@ -11,7 +11,7 @@ import { EmptyIndexListPrompt } from './empty_index_list_prompt'; import { shallow } from 'enzyme'; import sinon from 'sinon'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; jest.mock('react-router-dom', () => ({ useHistory: () => ({ diff --git a/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx b/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx index 4cf3c4b55acde..242f124b326b8 100644 --- a/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx +++ b/src/plugins/data_view_editor/public/components/empty_prompts/empty_index_pattern_prompt/empty_index_pattern_prompt.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EmptyIndexPatternPrompt } from '../empty_index_pattern_prompt'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; describe('EmptyIndexPatternPrompt', () => { it('should render normally', () => { diff --git a/src/plugins/data_view_editor/public/test_utils/test_utils.ts b/src/plugins/data_view_editor/public/test_utils/test_utils.ts index 311d93d31b593..1a553b74c19c8 100644 --- a/src/plugins/data_view_editor/public/test_utils/test_utils.ts +++ b/src/plugins/data_view_editor/public/test_utils/test_utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { getRandomString } from '@kbn/test/jest'; +export { getRandomString } from '@kbn/test-jest-helpers'; -export type { TestBed } from '@kbn/test/jest'; -export { registerTestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; +export { registerTestBed } from '@kbn/test-jest-helpers'; diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts index 1fd280a937a03..f1cf9b862ebaa 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor.helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { Context } from '../../public/components/field_editor_context'; import { FieldEditor, Props } from '../../public/components/field_editor/field_editor'; diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.helpers.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.helpers.ts index 0e87756819bf2..c3816b0bfb8c3 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.helpers.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_content.helpers.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { Context } from '../../public/components/field_editor_context'; import { diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts index 305cf84d59622..9df7de5f65d62 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/field_editor_flyout_preview.helpers.ts @@ -7,7 +7,7 @@ */ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { API_BASE_PATH } from '../../common/constants'; import { Context } from '../../public/components/field_editor_context'; diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/common_actions.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/common_actions.ts index 9f8b52af5878e..77e8e8834a0f4 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/common_actions.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/common_actions.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; /** * We often need to wait for both the documents & the preview to be fetched. diff --git a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts index 2fc870bd42d66..b29b76a9daf23 100644 --- a/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts +++ b/src/plugins/data_view_field_editor/__jest__/client_integration/helpers/index.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -export type { TestBed } from '@kbn/test/jest'; -export { findTestSubject } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; +export { findTestSubject } from '@kbn/test-jest-helpers'; export { setupEnvironment, diff --git a/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/color/color.test.tsx b/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/color/color.test.tsx index 362fa0887b780..750cf8378f334 100644 --- a/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/color/color.test.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/color/color.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { ColorFormatEditor } from './color'; import { FieldFormat, DEFAULT_CONVERTER_COLOR } from '../../../../../../field_formats/common'; diff --git a/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx b/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx index 5142f11ae87e5..9f2bd59d7ceb4 100644 --- a/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_format_editor/editors/static_lookup/static_lookup.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { StaticLookupFormatEditorFormatParams } from './static_lookup'; import { FieldFormat } from 'src/plugins/field_formats/common'; diff --git a/src/plugins/data_view_field_editor/public/components/field_format_editor/samples/samples.test.tsx b/src/plugins/data_view_field_editor/public/components/field_format_editor/samples/samples.test.tsx index 433e08ad5bcd0..9b400dd4931e0 100644 --- a/src/plugins/data_view_field_editor/public/components/field_format_editor/samples/samples.test.tsx +++ b/src/plugins/data_view_field_editor/public/components/field_format_editor/samples/samples.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { FormatEditorSamples } from './samples'; diff --git a/src/plugins/data_view_field_editor/public/plugin.test.tsx b/src/plugins/data_view_field_editor/public/plugin.test.tsx index 4f609965171b5..fe7e8c57cd4ec 100644 --- a/src/plugins/data_view_field_editor/public/plugin.test.tsx +++ b/src/plugins/data_view_field_editor/public/plugin.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import React from 'react'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; jest.mock('../../kibana_react/public', () => { const original = jest.requireActual('../../kibana_react/public'); diff --git a/src/plugins/data_views/common/data_views/data_views.ts b/src/plugins/data_views/common/data_views/data_views.ts index ddd7989eb5990..8ce6b00d131bb 100644 --- a/src/plugins/data_views/common/data_views/data_views.ts +++ b/src/plugins/data_views/common/data_views/data_views.ts @@ -367,7 +367,7 @@ export class DataViewsService { /** * Converts index pattern saved object to index pattern spec * @param savedObject - * @returns IndexPatternSpec + * @returns DataViewSpec */ savedObjectToSpec = (savedObject: SavedObject): DataViewSpec => { diff --git a/src/plugins/data_views/common/index.ts b/src/plugins/data_views/common/index.ts index 4de8e98acef39..ece482ff79eaa 100644 --- a/src/plugins/data_views/common/index.ts +++ b/src/plugins/data_views/common/index.ts @@ -41,7 +41,6 @@ export type { GetFieldsOptions, GetFieldsOptionsTimePattern, IDataViewsApiClient, - IIndexPatternsApiClient, SavedObject, AggregationRestrictions, TypeMeta, @@ -49,9 +48,7 @@ export type { FieldSpecExportFmt, FieldSpec, DataViewFieldMap, - IndexPatternFieldMap, DataViewSpec, - IndexPatternSpec, SourceFilter, } from './types'; export { DataViewType } from './types'; diff --git a/src/plugins/data_views/common/types.ts b/src/plugins/data_views/common/types.ts index db0e19cca32b4..f991b61bfad5f 100644 --- a/src/plugins/data_views/common/types.ts +++ b/src/plugins/data_views/common/types.ts @@ -32,7 +32,7 @@ export interface RuntimeField { /** * @deprecated * IIndexPattern allows for an IndexPattern OR an index pattern saved object - * Use IndexPattern or IndexPatternSpec instead + * Use DataView or DataViewSpec instead */ export interface IIndexPattern extends DataViewBase { title: string; @@ -145,11 +145,6 @@ export interface IDataViewsApiClient { hasUserIndexPattern: () => Promise; } -/** - * @deprecated Use IDataViewsApiClient. All index pattern interfaces were renamed. - */ -export type IIndexPatternsApiClient = IDataViewsApiClient; - export type { SavedObject }; export type AggregationRestrictions = Record< @@ -229,11 +224,6 @@ export interface FieldSpec extends DataViewFieldBase { export type DataViewFieldMap = Record; -/** - * @deprecated Use DataViewFieldMap. All index pattern interfaces were renamed. - */ -export type IndexPatternFieldMap = DataViewFieldMap; - /** * Static index pattern format * Serialized data object, representing index pattern attributes and state @@ -259,11 +249,6 @@ export interface DataViewSpec { allowNoIndex?: boolean; } -/** - * @deprecated Use DataViewSpec. All index pattern interfaces were renamed. - */ -export type IndexPatternSpec = DataViewSpec; - export interface SourceFilter { value: string; } diff --git a/src/plugins/data_views/server/index_patterns_api_client.ts b/src/plugins/data_views/server/index_patterns_api_client.ts index b40c0922589b1..4f036449fe7ee 100644 --- a/src/plugins/data_views/server/index_patterns_api_client.ts +++ b/src/plugins/data_views/server/index_patterns_api_client.ts @@ -9,14 +9,14 @@ import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server'; import { GetFieldsOptions, - IIndexPatternsApiClient, GetFieldsOptionsTimePattern, + IDataViewsApiClient, } from '../common/types'; import { DataViewMissingIndices } from '../common/lib'; import { IndexPatternsFetcher } from './fetcher'; import { hasUserIndexPattern } from './has_user_index_pattern'; -export class IndexPatternsApiServer implements IIndexPatternsApiClient { +export class IndexPatternsApiServer implements IDataViewsApiClient { esClient: ElasticsearchClient; constructor( elasticsearchClient: ElasticsearchClient, diff --git a/src/plugins/data_views/server/rest_api_routes/create_data_view.ts b/src/plugins/data_views/server/rest_api_routes/create_data_view.ts index dba6b5584e9ae..4b103ba87662c 100644 --- a/src/plugins/data_views/server/rest_api_routes/create_data_view.ts +++ b/src/plugins/data_views/server/rest_api_routes/create_data_view.ts @@ -45,7 +45,7 @@ export const createDataView = async ({ return dataViewsService.createAndSave(spec, override, !refreshFields); }; -const indexPatternSpecSchema = schema.object({ +const dataViewSpecSchema = schema.object({ title: schema.string(), version: schema.maybe(schema.string()), id: schema.maybe(schema.string()), @@ -91,9 +91,8 @@ const registerCreateDataViewRouteFactory = body: schema.object({ override: schema.maybe(schema.boolean({ defaultValue: false })), refresh_fields: schema.maybe(schema.boolean({ defaultValue: false })), - data_view: serviceKey === SERVICE_KEY ? indexPatternSpecSchema : schema.never(), - index_pattern: - serviceKey === SERVICE_KEY_LEGACY ? indexPatternSpecSchema : schema.never(), + data_view: serviceKey === SERVICE_KEY ? dataViewSpecSchema : schema.never(), + index_pattern: serviceKey === SERVICE_KEY_LEGACY ? dataViewSpecSchema : schema.never(), }), }, }, diff --git a/src/plugins/discover/public/application/context/components/action_bar/action_bar.test.tsx b/src/plugins/discover/public/application/context/components/action_bar/action_bar.test.tsx index cce6c7fbe6053..5a4e959ccb576 100644 --- a/src/plugins/discover/public/application/context/components/action_bar/action_bar.test.tsx +++ b/src/plugins/discover/public/application/context/components/action_bar/action_bar.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ActionBar, ActionBarProps } from './action_bar'; import { findTestSubject } from '@elastic/eui/lib/test'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE } from '../../services/constants'; diff --git a/src/plugins/discover/public/application/context/components/context_error_message/context_error_message.test.tsx b/src/plugins/discover/public/application/context/components/context_error_message/context_error_message.test.tsx index 47937058451fa..049a3977b5b96 100644 --- a/src/plugins/discover/public/application/context/components/context_error_message/context_error_message.test.tsx +++ b/src/plugins/discover/public/application/context/components/context_error_message/context_error_message.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { ContextErrorMessage } from './context_error_message'; import { FailureReason, LoadingStatus } from '../../services/context_query_state'; diff --git a/src/plugins/discover/public/application/context/context_app.test.tsx b/src/plugins/discover/public/application/context/context_app.test.tsx index aa3428e52fa96..c9089a6c1111c 100644 --- a/src/plugins/discover/public/application/context/context_app.test.tsx +++ b/src/plugins/discover/public/application/context/context_app.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { waitFor } from '@testing-library/react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { createFilterManagerMock } from '../../../../data/public/query/filter_manager/filter_manager.mock'; import { mockTopNavMenu } from './__mocks__/top_nav_menu'; import { ContextAppContent } from './context_app_content'; diff --git a/src/plugins/discover/public/application/context/context_app_content.test.tsx b/src/plugins/discover/public/application/context/context_app_content.test.tsx index a066dbe0deddb..e57206bcba2c1 100644 --- a/src/plugins/discover/public/application/context/context_app_content.test.tsx +++ b/src/plugins/discover/public/application/context/context_app_content.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { ActionBar } from './components/action_bar/action_bar'; import { GetStateReturn } from './services/context_state'; diff --git a/src/plugins/discover/public/application/doc/components/doc.test.tsx b/src/plugins/discover/public/application/doc/components/doc.test.tsx index f9b024b9c6835..29453e7f83f19 100644 --- a/src/plugins/discover/public/application/doc/components/doc.test.tsx +++ b/src/plugins/discover/public/application/doc/components/doc.test.tsx @@ -9,7 +9,7 @@ import { throwError, of } from 'rxjs'; import React from 'react'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; diff --git a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx index 3feb8f2cea6b5..1be37081a01b5 100644 --- a/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/discover_chart.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Subject, BehaviorSubject } from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { esHits } from '../../../../__mocks__/es_hits'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; diff --git a/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx b/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx index 547c6ffe42f48..d336cd2c092b6 100644 --- a/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx +++ b/src/plugins/discover/public/application/main/components/chart/histogram.test.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { BehaviorSubject } from 'rxjs'; import { FetchStatus } from '../../../types'; import { DataCharts$ } from '../../utils/use_saved_search'; diff --git a/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorere_callout.test.tsx b/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorere_callout.test.tsx index 37c9f2284aa20..3e6b8e3973001 100644 --- a/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorere_callout.test.tsx +++ b/src/plugins/discover/public/application/main/components/document_explorer_callout/document_explorere_callout.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { KibanaContextProvider } from '../../../../../../kibana_react/public'; import { CALLOUT_STATE_KEY, DocumentExplorerCallout } from './document_explorer_callout'; import { LocalStorageMock } from '../../../../__mocks__/local_storage_mock'; diff --git a/src/plugins/discover/public/application/main/components/hits_counter/hits_counter.test.tsx b/src/plugins/discover/public/application/main/components/hits_counter/hits_counter.test.tsx index d46600fca01f4..4ba4eda45d279 100644 --- a/src/plugins/discover/public/application/main/components/hits_counter/hits_counter.test.tsx +++ b/src/plugins/discover/public/application/main/components/hits_counter/hits_counter.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { HitsCounter, HitsCounterProps } from './hits_counter'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx index cf547b42c7277..78f18e0218872 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_documents.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { BehaviorSubject } from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { esHits } from '../../../../__mocks__/es_hits'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx index aa2149abdde62..7b0845cd199c0 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { Subject, BehaviorSubject } from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { setHeaderActionMenuMounter } from '../../../../kibana_services'; import { DiscoverLayout, SIDEBAR_CLOSED_KEY } from './discover_layout'; import { esHits } from '../../../../__mocks__/es_hits'; diff --git a/src/plugins/discover/public/application/main/components/loading_spinner/loading_spinner.test.tsx b/src/plugins/discover/public/application/main/components/loading_spinner/loading_spinner.test.tsx index 020c6d38398ba..36978a1f72684 100644 --- a/src/plugins/discover/public/application/main/components/loading_spinner/loading_spinner.test.tsx +++ b/src/plugins/discover/public/application/main/components/loading_spinner/loading_spinner.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { LoadingSpinner } from './loading_spinner'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx b/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx index 2418416e5c277..4dc1a5feda5dc 100644 --- a/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx +++ b/src/plugins/discover/public/application/main/components/no_results/no_results.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DiscoverNoResults, DiscoverNoResultsProps } from './no_results'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/change_indexpattern.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/change_indexpattern.test.tsx index b61683c8de14a..a5e93c1d895bc 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/change_indexpattern.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/change_indexpattern.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiSelectable } from '@elastic/eui'; import { ShallowWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ChangeIndexPattern } from './change_indexpattern'; import { indexPatternMock } from '../../../../__mocks__/index_pattern'; import { indexPatternWithTimefieldMock } from '../../../../__mocks__/index_pattern_with_timefield'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx index 758738a6cbc56..c2ba365ef5fda 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DiscoverField } from './discover_field'; import { DataViewField } from '../../../../../../data/common'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field_details.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field_details.test.tsx index c63a50c17fa8f..77f2d0fd89d25 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field_details.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field_details.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DiscoverFieldDetails } from './discover_field_details'; import { DataViewField } from '../../../../../../data/common'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.test.tsx index e654d87ea2ba6..40474f3e4fc1d 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DiscoverFieldSearch, Props } from './discover_field_search'; import { EuiButtonGroupProps, EuiPopover } from '@elastic/eui'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern.test.tsx index 153f1cb4c7b3d..73f460cccf784 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { ShallowWrapper } from 'enzyme'; import { ChangeIndexPattern } from './change_indexpattern'; import { SavedObject } from 'kibana/server'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx index 272e8178aefc2..b511dc08f1e91 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl, findTestSubject } from '@kbn/test/jest'; +import { mountWithIntl, findTestSubject } from '@kbn/test-jest-helpers'; import { EuiContextMenuPanel, EuiPopover, EuiContextMenuItem } from '@elastic/eui'; import { DiscoverServices } from '../../../../build_services'; import { DiscoverIndexPatternManagement } from './discover_index_pattern_management'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx index b6dbf40cc7b3d..8a9e9a0ee6872 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.test.tsx @@ -12,7 +12,7 @@ import { findTestSubject } from '@elastic/eui/lib/test'; // @ts-expect-error import realHits from '../../../../__fixtures__/real_hits.js'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { DiscoverSidebarProps } from './discover_sidebar'; import { flattenHit, IndexPatternAttributes } from '../../../../../../data/common'; diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx index eee09f4ef481e..7f1f6b94eab16 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.test.tsx @@ -13,7 +13,7 @@ import { findTestSubject } from '@elastic/eui/lib/test'; // @ts-expect-error import realHits from '../../../../__fixtures__/real_hits.js'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { flattenHit, IndexPatternAttributes } from '../../../../../../data/common'; import { SavedObject } from '../../../../../../../core/types'; diff --git a/src/plugins/discover/public/application/main/components/skip_bottom_button/skip_bottom_button.test.tsx b/src/plugins/discover/public/application/main/components/skip_bottom_button/skip_bottom_button.test.tsx index 0cdbbb7602e8c..21456ca59dab1 100644 --- a/src/plugins/discover/public/application/main/components/skip_bottom_button/skip_bottom_button.test.tsx +++ b/src/plugins/discover/public/application/main/components/skip_bottom_button/skip_bottom_button.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { SkipBottomButton, SkipBottomButtonProps } from './skip_bottom_button'; diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx index ca5eacd4fdb8a..2c5c5ffb37d69 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { indexPatternMock } from '../../../../__mocks__/index_pattern'; import { savedSearchMock } from '../../../../__mocks__/saved_search'; import { DiscoverTopNav, DiscoverTopNavProps } from './discover_topnav'; diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx index c2059915b2af8..ef64e602280b4 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_options_popover.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { OptionsPopover } from './open_options_popover'; diff --git a/src/plugins/discover/public/application/main/discover_main_app.test.tsx b/src/plugins/discover/public/application/main/discover_main_app.test.tsx index d1699900b1498..5d3b28bb35c5f 100644 --- a/src/plugins/discover/public/application/main/discover_main_app.test.tsx +++ b/src/plugins/discover/public/application/main/discover_main_app.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { DiscoverMainApp } from './discover_main_app'; import { savedSearchMock } from '../../__mocks__/saved_search'; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx index c4ef4ffef3234..f2b3b130e395c 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid.test.tsx @@ -12,7 +12,7 @@ import { act } from 'react-dom/test-utils'; import { findTestSubject } from '@elastic/eui/lib/test'; import { esHits } from '../../__mocks__/es_hits'; import { indexPatternMock } from '../../__mocks__/index_pattern'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DiscoverGrid, DiscoverGridProps } from './discover_grid'; import { getDocId } from './discover_grid_document_selection'; import { ElasticSearchHit } from '../../types'; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx index 7568cb427e7b5..651004638a85e 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_cell_actions.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { FilterInBtn, FilterOutBtn, buildCellActions } from './discover_grid_cell_actions'; import { DiscoverGridContext } from './discover_grid_context'; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx index d57fba241a1e7..2afee7a6a48a3 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_document_selection.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DiscoverGridDocumentToolbarBtn, diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx index de2117afe7bdb..bb8341f746e85 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_expand_button.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { ExpandButton } from './discover_grid_expand_button'; import { DiscoverGridContext } from './discover_grid_context'; diff --git a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx index a6c5ecdcdf35c..cc10da2dfc464 100644 --- a/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx +++ b/src/plugins/discover/public/components/discover_grid/discover_grid_flyout.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DiscoverGridFlyout, DiscoverGridFlyoutProps } from './discover_grid_flyout'; import { esHits } from '../../__mocks__/es_hits'; import { createFilterManagerMock } from '../../../../data/public/query/filter_manager/filter_manager.mock'; diff --git a/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx b/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx index 60e9c25cb4532..d29bd02e92d35 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_header/table_header.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { DataView, DataViewField } from 'src/plugins/data/common'; import { TableHeader } from './table_header'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx b/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx index 084fddc991f74..61e536129ffdb 100644 --- a/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx +++ b/src/plugins/discover/public/components/doc_table/components/table_row.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl, findTestSubject } from '@kbn/test/jest'; +import { mountWithIntl, findTestSubject } from '@kbn/test-jest-helpers'; import { TableRow, TableRowProps } from './table_row'; import { setDocViewsRegistry } from '../../../kibana_services'; import { createFilterManagerMock } from '../../../../../data/public/query/filter_manager/filter_manager.mock'; diff --git a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx index 3634a47e3bcf1..9de67c340f8bd 100644 --- a/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx +++ b/src/plugins/discover/public/components/doc_table/doc_table_wrapper.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { indexPatternMock } from '../../__mocks__/index_pattern'; import { DocTableWrapper } from './doc_table_wrapper'; import { DocTableRow } from './components/table_row'; diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.test.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.test.tsx index 70648b46d7804..986ad37384c92 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.test.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_source/source.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import type { DataView } from 'src/plugins/data/common'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DocViewerSource } from './source'; import * as hooks from '../../../../utils/use_es_doc_search'; import * as useUiSettingHook from '../../../../../../kibana_react/public/ui_settings/use_ui_setting'; diff --git a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx index cb85315e7dd42..26dbd76a1dc1a 100644 --- a/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx +++ b/src/plugins/discover/public/services/doc_views/components/doc_viewer_table/legacy/table.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { DocViewerLegacyTable } from './table'; import { DataView } from '../../../../../../../data/common'; diff --git a/src/plugins/discover/public/services/saved_searches/saved_search_url_conflict_callout.test.tsx b/src/plugins/discover/public/services/saved_searches/saved_search_url_conflict_callout.test.tsx index 0aac9aea62192..6bdea087fcf46 100644 --- a/src/plugins/discover/public/services/saved_searches/saved_search_url_conflict_callout.test.tsx +++ b/src/plugins/discover/public/services/saved_searches/saved_search_url_conflict_callout.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import type { History } from 'history'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { SavedSearchURLConflictCallout } from './saved_search_url_conflict_callout'; import type { SavedSearch } from './types'; diff --git a/src/plugins/discover/public/utils/with_query_params.test.tsx b/src/plugins/discover/public/utils/with_query_params.test.tsx index 3d416d6a3e8b5..e23688c0031c7 100644 --- a/src/plugins/discover/public/utils/with_query_params.test.tsx +++ b/src/plugins/discover/public/utils/with_query_params.test.tsx @@ -9,7 +9,7 @@ import React, { ReactElement } from 'react'; import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { withQueryParams } from './with_query_params'; const mountComponent = (children: ReactElement, query = '') => { diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts index 27cb3cec8be41..879b75986365b 100644 --- a/src/plugins/discover/server/plugin.ts +++ b/src/plugins/discover/server/plugin.ts @@ -14,7 +14,7 @@ import { searchSavedObjectType } from './saved_objects'; export class DiscoverServerPlugin implements Plugin { public setup(core: CoreSetup) { core.capabilities.registerProvider(capabilitiesProvider); - core.uiSettings.register(getUiSettings()); + core.uiSettings.register(getUiSettings(core.docLinks)); core.savedObjects.registerType(searchSavedObjectType); return {}; diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index f35212ba43618..c9c9692e6986b 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { UiSettingsParams } from 'kibana/server'; +import type { DocLinksServiceSetup, UiSettingsParams } from 'kibana/server'; import { METRIC_TYPE } from '@kbn/analytics'; import { DEFAULT_COLUMNS_SETTING, @@ -31,7 +31,9 @@ import { ROW_HEIGHT_OPTION, } from '../common'; -export const getUiSettings: () => Record = () => ({ +export const getUiSettings: (docLinks: DocLinksServiceSetup) => Record = ( + docLinks: DocLinksServiceSetup +) => ({ [DEFAULT_COLUMNS_SETTING]: { name: i18n.translate('discover.advancedSettings.defaultColumnsTitle', { defaultMessage: 'Default columns', @@ -215,7 +217,7 @@ export const getUiSettings: () => Record = () => ({ defaultMessage: `Enable the {fieldStatisticsDocs} to show details such as the minimum and maximum values of a numeric field or a map of a geo field. This functionality is in beta and is subject to change.`, values: { fieldStatisticsDocs: - `` + i18n.translate('discover.advancedSettings.discover.fieldStatisticsLinkText', { defaultMessage: 'Field statistics view', @@ -240,7 +242,7 @@ export const getUiSettings: () => Record = () => ({ defaultMessage: `Controls whether {multiFields} display in the expanded document view. In most cases, multi-fields are the same as the original field. This option is only available when \`searchFieldsFromSource\` is off.`, values: { multiFields: - `` + i18n.translate('discover.advancedSettings.discover.multiFieldsLinkText', { defaultMessage: 'multi-fields', diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx index d5452870ed0de..07867476508a5 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { EmbeddableChildPanel } from './embeddable_child_panel'; import { CONTACT_CARD_EMBEDDABLE } from '../test_samples/embeddables/contact_card/contact_card_embeddable_factory'; import { SlowContactCardEmbeddableFactory } from '../test_samples/embeddables/contact_card/slow_contact_card_embeddable_factory'; diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 8d313030556c6..81ea83fe06d10 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { mount } from 'enzyme'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { I18nProvider } from '@kbn/i18n-react'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx index 1c96945f014c8..c860fc7e770b9 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.test.tsx @@ -16,7 +16,7 @@ import { import { HelloWorldContainer } from '../../../../test_samples/embeddables/hello_world_container'; import { ContactCardEmbeddable } from '../../../../test_samples/embeddables/contact_card/contact_card_embeddable'; import { ContainerInput } from '../../../../containers'; -import { mountWithIntl as mount } from '@kbn/test/jest'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { coreMock } from '../../../../../../../../core/public/mocks'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx index be8416da38700..79e68aabe577a 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/tests/saved_object_finder_create_new.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { SavedObjectFinderCreateNew } from '../saved_object_finder_create_new'; import { shallow } from 'enzyme'; import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; describe('SavedObjectFinderCreateNew', () => { test('renders correctly with no items', () => { diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts index 4d04778398938..983f9ceedf369 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/customize_title/customize_panel_action.test.ts @@ -7,7 +7,7 @@ */ import { Container, isErrorEmbeddable } from '../../../..'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { CustomizePanelTitleAction } from './customize_panel_action'; import { ContactCardEmbeddable, diff --git a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx index 44cd93069bfb3..baae08b23cfa1 100644 --- a/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx +++ b/src/plugins/embeddable/public/tests/customize_panel_modal.test.tsx @@ -24,7 +24,7 @@ import { testPlugin } from './test_plugin'; import { CustomizePanelModal } from '../lib/panel/panel_header/panel_actions/customize_title/customize_panel_modal'; import { EmbeddableStart } from '../plugin'; import { createEmbeddablePanelMock } from '../mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { OverlayStart } from 'kibana/public'; let api: EmbeddableStart; diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx index 0ae82872124a4..cb8994b153bdd 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import sinon from 'sinon'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithI18nProvider } from '@kbn/test/jest'; +import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { Frequency } from './types'; import { CronEditor } from './cron_editor'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts index cd9bb97cab8d9..113d33dba7100 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts @@ -7,9 +7,6 @@ */ // eslint-disable-next-line import/no-extraneous-dependencies -export type { TestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; // eslint-disable-next-line import/no-extraneous-dependencies -export { registerTestBed } from '@kbn/test/jest'; - -// eslint-disable-next-line import/no-extraneous-dependencies -export { getRandomString } from '@kbn/test/jest'; +export { registerTestBed, getRandomString } from '@kbn/test-jest-helpers'; diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 3746d4d61a7bc..24116025267ba 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -34,6 +34,7 @@ export type { ExpressionRendererComponent, ReactExpressionRendererProps, ReactExpressionRendererType, + useExpressionRenderer, } from './react_expression_renderer'; export type { AnyExpressionFunctionDefinition, diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 64384ebbfc852..f0571d82f427e 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -7,7 +7,7 @@ */ import { BehaviorSubject, Observable, Subject, Subscription, asyncScheduler, identity } from 'rxjs'; -import { filter, map, delay, throttleTime } from 'rxjs/operators'; +import { filter, map, delay, shareReplay, throttleTime } from 'rxjs/operators'; import { defaults } from 'lodash'; import { SerializableRecord, UnwrapObservable } from '@kbn/utility-types'; import { Adapters } from '../../inspector/public'; @@ -48,6 +48,7 @@ export class ExpressionLoader { // as loading$ could emit straight away in the constructor // and we want to notify subscribers about it, but all subscriptions will happen later this.loading$ = this.loadingSubject.asObservable().pipe( + shareReplay(1), filter((_) => _ === true), map(() => void 0) ); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx deleted file mode 100644 index 897d69f356035..0000000000000 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ /dev/null @@ -1,243 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { useRef, useEffect, useState, useLayoutEffect } from 'react'; -import classNames from 'classnames'; -import { Observable, Subscription } from 'rxjs'; -import { filter } from 'rxjs/operators'; -import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect'; -import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; -import { euiLightVars as theme } from '@kbn/ui-theme'; -import { IExpressionLoaderParams, ExpressionRenderError, ExpressionRendererEvent } from './types'; -import { ExpressionAstExpression, IInterpreterRenderHandlers } from '../common'; -import { ExpressionLoader } from './loader'; - -// Accept all options of the runner as props except for the -// dom element which is provided by the component itself -export interface ReactExpressionRendererProps extends IExpressionLoaderParams { - className?: string; - dataAttrs?: string[]; - expression: string | ExpressionAstExpression; - renderError?: ( - message?: string | null, - error?: ExpressionRenderError | null - ) => React.ReactElement | React.ReactElement[]; - padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; - onEvent?: (event: ExpressionRendererEvent) => void; - onData$?: ( - data: TData, - adapters?: TInspectorAdapters, - partial?: boolean - ) => void; - /** - * An observable which can be used to re-run the expression without destroying the component - */ - reload$?: Observable; - onRender$?: (item: number) => void; - debounce?: number; -} - -export type ReactExpressionRendererType = React.ComponentType; - -interface State { - isEmpty: boolean; - isLoading: boolean; - error: null | ExpressionRenderError; -} - -export type ExpressionRendererComponent = React.FC; - -const defaultState: State = { - isEmpty: true, - isLoading: false, - error: null, -}; - -// eslint-disable-next-line import/no-default-export -export default function ReactExpressionRenderer({ - className, - dataAttrs, - padding, - renderError, - expression, - onEvent, - onData$, - onRender$, - reload$, - debounce, - ...expressionLoaderOptions -}: ReactExpressionRendererProps) { - const mountpoint: React.MutableRefObject = useRef(null); - const [state, setState] = useState({ ...defaultState }); - const hasCustomRenderErrorHandler = !!renderError; - const expressionLoaderRef: React.MutableRefObject = useRef(null); - // flag to skip next render$ notification, - // because of just handled error - const hasHandledErrorRef = useRef(false); - - // will call done() in LayoutEffect when done with rendering custom error state - const errorRenderHandlerRef: React.MutableRefObject = - useRef(null); - const [debouncedExpression, setDebouncedExpression] = useState(expression); - const [waitingForDebounceToComplete, setDebouncePending] = useState(false); - const firstRender = useRef(true); - useShallowCompareEffect(() => { - if (firstRender.current) { - firstRender.current = false; - return; - } - if (debounce === undefined) { - return; - } - setDebouncePending(true); - const handler = setTimeout(() => { - setDebouncedExpression(expression); - setDebouncePending(false); - }, debounce); - - return () => { - clearTimeout(handler); - }; - }, [expression, expressionLoaderOptions, debounce]); - - const activeExpression = debounce !== undefined ? debouncedExpression : expression; - - /* eslint-disable react-hooks/exhaustive-deps */ - // OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() - useEffect(() => { - const subs: Subscription[] = []; - expressionLoaderRef.current = new ExpressionLoader(mountpoint.current!, activeExpression, { - ...expressionLoaderOptions, - // react component wrapper provides different - // error handling api which is easier to work with from react - // if custom renderError is not provided then we fallback to default error handling from ExpressionLoader - onRenderError: hasCustomRenderErrorHandler - ? (domNode, error, handlers) => { - errorRenderHandlerRef.current = handlers; - setState(() => ({ - ...defaultState, - isEmpty: false, - error, - })); - - if (expressionLoaderOptions.onRenderError) { - expressionLoaderOptions.onRenderError(domNode, error, handlers); - } - } - : expressionLoaderOptions.onRenderError, - }); - if (onEvent) { - subs.push( - expressionLoaderRef.current.events$.subscribe((event) => { - onEvent(event); - }) - ); - } - if (onData$) { - subs.push( - expressionLoaderRef.current.data$.subscribe(({ partial, result }) => { - onData$(result, expressionLoaderRef.current?.inspect(), partial); - }) - ); - } - subs.push( - expressionLoaderRef.current.loading$.subscribe(() => { - hasHandledErrorRef.current = false; - setState((prevState) => ({ ...prevState, isLoading: true })); - }), - expressionLoaderRef.current.render$ - .pipe(filter(() => !hasHandledErrorRef.current)) - .subscribe((item) => { - setState(() => ({ - ...defaultState, - isEmpty: false, - })); - onRender$?.(item); - }) - ); - - return () => { - subs.forEach((s) => s.unsubscribe()); - if (expressionLoaderRef.current) { - expressionLoaderRef.current.destroy(); - expressionLoaderRef.current = null; - } - - errorRenderHandlerRef.current = null; - }; - }, [ - hasCustomRenderErrorHandler, - onEvent, - expressionLoaderOptions.interactive, - expressionLoaderOptions.renderMode, - expressionLoaderOptions.syncColors, - ]); - - useEffect(() => { - const subscription = reload$?.subscribe(() => { - if (expressionLoaderRef.current) { - expressionLoaderRef.current.update(activeExpression, expressionLoaderOptions); - } - }); - return () => subscription?.unsubscribe(); - }, [reload$, activeExpression, ...Object.values(expressionLoaderOptions)]); - - // Re-fetch data automatically when the inputs change - useShallowCompareEffect( - () => { - // only update the loader if the debounce period is over - if (expressionLoaderRef.current && !waitingForDebounceToComplete) { - expressionLoaderRef.current.update(activeExpression, expressionLoaderOptions); - } - }, - // when debounced, wait for debounce status to change to update loader. - // Otherwise, update when expression is changed by reference and when any other loaderOption is changed by reference - debounce === undefined - ? [{ activeExpression, ...expressionLoaderOptions }] - : [{ waitingForDebounceToComplete }] - ); - - /* eslint-enable react-hooks/exhaustive-deps */ - // call expression loader's done() handler when finished rendering custom error state - useLayoutEffect(() => { - if (state.error && errorRenderHandlerRef.current) { - hasHandledErrorRef.current = true; - errorRenderHandlerRef.current.done(); - errorRenderHandlerRef.current = null; - } - }, [state.error]); - - const classes = classNames('expExpressionRenderer', className, { - 'expExpressionRenderer-isEmpty': state.isEmpty, - 'expExpressionRenderer-hasError': !!state.error, - }); - - const expressionStyles: React.CSSProperties = {}; - - if (padding) { - expressionStyles.padding = theme.paddingSizes[padding]; - } - - return ( -
- {state.isEmpty && } - {(state.isLoading || waitingForDebounceToComplete) && ( - - )} - {!state.isLoading && - state.error && - renderError && - renderError(state.error.message, state.error)} -
-
- ); -} diff --git a/src/plugins/chart_expressions/expression_pie/common/expression_functions/index.ts b/src/plugins/expressions/public/react_expression_renderer/index.ts similarity index 76% rename from src/plugins/chart_expressions/expression_pie/common/expression_functions/index.ts rename to src/plugins/expressions/public/react_expression_renderer/index.ts index ee8f0ec06d436..75d982ef26526 100644 --- a/src/plugins/chart_expressions/expression_pie/common/expression_functions/index.ts +++ b/src/plugins/expressions/public/react_expression_renderer/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { pieVisFunction } from './pie_vis_function'; -export { pieLabelsFunction } from './pie_labels_function'; +export * from './react_expression_renderer'; +export * from './use_expression_renderer'; diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.test.tsx similarity index 95% rename from src/plugins/expressions/public/react_expression_renderer.test.tsx rename to src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.test.tsx index cf19b333fed45..d7e08b0f7fc93 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.test.tsx @@ -10,14 +10,14 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { Subject } from 'rxjs'; import { share } from 'rxjs/operators'; -import { default as ReactExpressionRenderer } from './react_expression_renderer'; -import { ExpressionLoader } from './loader'; +import { ReactExpressionRenderer } from './react_expression_renderer'; import { mount } from 'enzyme'; import { EuiProgress } from '@elastic/eui'; -import { IInterpreterRenderHandlers } from '../common'; -import { RenderErrorHandlerFnType, ExpressionRendererEvent } from './types'; +import { IInterpreterRenderHandlers } from '../../common'; +import { ExpressionLoader } from '../loader'; +import { RenderErrorHandlerFnType, ExpressionRendererEvent } from '../types'; -jest.mock('./loader', () => { +jest.mock('../loader', () => { return { ExpressionLoader: jest.fn().mockImplementation(() => { return {}; @@ -103,7 +103,7 @@ describe('ExpressionRenderer', () => { }); it('waits for debounce period if specified', () => { - jest.useFakeTimers(); + jest.useFakeTimers('modern'); const refreshSubject = new Subject(); const loaderUpdate = jest.fn(); @@ -124,19 +124,19 @@ describe('ExpressionRenderer', () => { instance.setProps({ expression: 'abc' }); - expect(loaderUpdate).toHaveBeenCalledTimes(1); + expect(loaderUpdate).not.toHaveBeenCalled(); act(() => { jest.runAllTimers(); }); - expect(loaderUpdate).toHaveBeenCalledTimes(2); + expect(loaderUpdate).toHaveBeenCalledTimes(1); instance.unmount(); }); it('should not update twice immediately after rendering', () => { - jest.useFakeTimers(); + jest.useFakeTimers('modern'); const refreshSubject = new Subject(); const loaderUpdate = jest.fn(); @@ -159,13 +159,13 @@ describe('ExpressionRenderer', () => { jest.runAllTimers(); }); - expect(loaderUpdate).toHaveBeenCalledTimes(1); + expect(loaderUpdate).not.toHaveBeenCalled(); instance.unmount(); }); it('waits for debounce period on other loader option change if specified', () => { - jest.useFakeTimers(); + jest.useFakeTimers('modern'); const refreshSubject = new Subject(); const loaderUpdate = jest.fn(); @@ -191,13 +191,13 @@ describe('ExpressionRenderer', () => { instance.setProps({ searchContext: { from: 'now-30m', to: 'now' } }); - expect(loaderUpdate).toHaveBeenCalledTimes(1); + expect(loaderUpdate).not.toHaveBeenCalled(); act(() => { jest.runAllTimers(); }); - expect(loaderUpdate).toHaveBeenCalledTimes(2); + expect(loaderUpdate).toHaveBeenCalledTimes(1); instance.unmount(); }); diff --git a/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx new file mode 100644 index 0000000000000..000c68af26c5d --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/react_expression_renderer.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useRef } from 'react'; +import classNames from 'classnames'; +import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; +import { euiLightVars as theme } from '@kbn/ui-theme'; +import { ExpressionRenderError } from '../types'; +import type { ExpressionRendererParams } from './use_expression_renderer'; +import { useExpressionRenderer } from './use_expression_renderer'; + +// Accept all options of the runner as props except for the +// dom element which is provided by the component itself +export interface ReactExpressionRendererProps + extends Omit { + className?: string; + dataAttrs?: string[]; + renderError?: ( + message?: string | null, + error?: ExpressionRenderError | null + ) => React.ReactElement | React.ReactElement[]; + padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; +} + +export type ReactExpressionRendererType = React.ComponentType; +export type ExpressionRendererComponent = React.FC; + +export function ReactExpressionRenderer({ + className, + dataAttrs, + padding, + renderError, + ...expressionRendererOptions +}: ReactExpressionRendererProps) { + const nodeRef = useRef(null); + const { error, isEmpty, isLoading } = useExpressionRenderer(nodeRef, { + ...expressionRendererOptions, + hasCustomErrorRenderer: !!renderError, + }); + + const classes = classNames('expExpressionRenderer', className, { + 'expExpressionRenderer-isEmpty': isEmpty, + 'expExpressionRenderer-hasError': !!error, + }); + + const expressionStyles: React.CSSProperties = {}; + + if (padding) { + expressionStyles.padding = theme.paddingSizes[padding]; + } + + return ( +
+ {isEmpty && } + {isLoading && } + {!isLoading && error && renderError?.(error.message, error)} +
+
+ ); +} diff --git a/src/plugins/expressions/public/react_expression_renderer/shallow_equal.d.ts b/src/plugins/expressions/public/react_expression_renderer/shallow_equal.d.ts new file mode 100644 index 0000000000000..f18838cf23329 --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/shallow_equal.d.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +declare module 'react-redux/lib/utils/shallowEqual' { + const shallowEqual: typeof import('react-redux').shallowEqual; + + // eslint-disable-next-line import/no-default-export + export default shallowEqual; +} diff --git a/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts new file mode 100644 index 0000000000000..be0c3717a9e35 --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.test.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { useDebouncedValue } from './use_debounced_value'; + +describe('useDebouncedValue', () => { + beforeEach(() => { + jest.useFakeTimers('modern'); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should return the initial value', () => { + const { result } = renderHook(() => useDebouncedValue('something', 1000)); + + const [value] = result.current; + expect(value).toBe('something'); + }); + + it('should debounce value update', () => { + const hook = renderHook((value) => useDebouncedValue(value, 1000), { + initialProps: 'something', + }); + hook.rerender('something else'); + + const [value, isPending] = hook.result.current; + expect(value).toBe('something'); + expect(isPending).toBe(true); + }); + + it('should update value after a timeout', () => { + const hook = renderHook((value) => useDebouncedValue(value, 1000), { + initialProps: 'something', + }); + + hook.rerender('something else'); + act(() => { + jest.advanceTimersByTime(1000); + }); + + const [value, isPending] = hook.result.current; + expect(value).toBe('something else'); + expect(isPending).toBe(false); + }); + + it('should throttle multiple value updates', () => { + const hook = renderHook((value) => useDebouncedValue(value, 1000), { + initialProps: 'something', + }); + + hook.rerender('something else'); + act(() => { + jest.advanceTimersByTime(500); + }); + + hook.rerender('another value'); + act(() => { + jest.advanceTimersByTime(1000); + }); + + const [value] = hook.result.current; + expect(value).toBe('another value'); + }); + + it('should update value immediately if there is no timeout', () => { + const hook = renderHook((value) => useDebouncedValue(value), { initialProps: 'something' }); + + hook.rerender('something else'); + + const [value, isPending] = hook.result.current; + expect(value).toBe('something else'); + expect(isPending).toBe(false); + }); +}); diff --git a/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts new file mode 100644 index 0000000000000..07ae13590174f --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/use_debounced_value.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { debounce } from 'lodash'; +import type { Cancelable } from 'lodash'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; + +export function useDebouncedValue(value: T, timeout?: number): [T, boolean] { + const [storedValue, setStoredValue] = useState(value); + const [isPending, setPending] = useState(false); + const setValue = useCallback( + (newValue: T) => { + setStoredValue(newValue); + setPending(false); + }, + [setStoredValue, setPending] + ); + const setDebouncedValue = useMemo>( + () => (timeout ? debounce(setValue, timeout) : setValue), + [setValue, timeout] + ); + + useEffect(() => () => setDebouncedValue.cancel?.(), [setDebouncedValue]); + useUpdateEffect(() => { + setPending(true); + setDebouncedValue(value); + + return () => setDebouncedValue.cancel?.(); + }, [value]); + + return [storedValue, isPending]; +} diff --git a/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts new file mode 100644 index 0000000000000..581fb4b182b41 --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.test.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { RefObject } from 'react'; +import { act, renderHook, RenderHookResult } from '@testing-library/react-hooks'; +import { Subject } from 'rxjs'; +import type { IInterpreterRenderHandlers } from '../../common'; +import { ExpressionRendererParams, useExpressionRenderer } from './use_expression_renderer'; +import * as loader from '../loader'; + +describe('useExpressionRenderer', () => { + const expressionLoaderSpy = jest.spyOn(loader, 'ExpressionLoader'); + let nodeRef: RefObject; + let expressionLoader: jest.Mocked & { + data$: Subject; + events$: Subject; + loading$: Subject; + render$: Subject; + }; + let hook: RenderHookResult>; + + beforeEach(() => { + nodeRef = { current: document.createElement('div') }; + expressionLoader = { + data$: new Subject(), + events$: new Subject(), + loading$: new Subject(), + render$: new Subject(), + destroy: jest.fn(), + inspect: jest.fn(), + update: jest.fn(), + } as unknown as typeof expressionLoader; + + expressionLoaderSpy.mockImplementation(() => expressionLoader); + hook = renderHook( + (params: ExpressionRendererParams) => useExpressionRenderer(nodeRef, params), + { initialProps: { expression: 'something' } } + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should return default state', () => { + expect(hook.result.current).toEqual({ + isEmpty: true, + isLoading: false, + error: null, + }); + }); + + it('should update loader options on properties change', () => { + expect(expressionLoader.update).not.toHaveBeenCalled(); + + hook.rerender({ expression: 'something else', partial: true }); + + expect(expressionLoader.update).toHaveBeenCalledWith('something else', { partial: true }); + }); + + it('should debounce property changes', () => { + jest.useFakeTimers('modern'); + + hook.rerender({ debounce: 1000, expression: 'something else' }); + expect(expressionLoader.update).not.toHaveBeenCalled(); + + expect(hook.result.current).toEqual(expect.objectContaining({ isLoading: true })); + + act(() => void jest.advanceTimersByTime(1000)); + expect(hook.result.current).toEqual(expect.objectContaining({ isLoading: false })); + expect(expressionLoader.update).toHaveBeenCalledWith('something else', {}); + + jest.useRealTimers(); + }); + + it('should not debounce if loader optaions are not changed', () => { + jest.useFakeTimers('modern'); + + hook.rerender({ expression: 'something else', partial: true }); + hook.rerender({ + expression: 'something else', + debounce: 1000, + hasCustomErrorRenderer: true, + partial: true, + }); + + expect(hook.result.current).toEqual(expect.objectContaining({ isLoading: false })); + expect(expressionLoader.update).toHaveBeenCalledTimes(1); + + jest.useRealTimers(); + }); + + it('should handle rendering errors', () => { + expressionLoaderSpy.mockClear(); + const onRenderError = jest.fn(); + const done = jest.fn(); + hook.rerender({ onRenderError, expression: 'something' }); + + expect(expressionLoaderSpy).toHaveBeenCalledTimes(1); + + const [[, , loaderParams]] = expressionLoaderSpy.mock.calls; + act(() => + loaderParams?.onRenderError?.(document.createElement('div'), new Error('something'), { + done, + } as unknown as IInterpreterRenderHandlers) + ); + + expect(hook.result.current).toEqual({ + isEmpty: false, + isLoading: false, + error: new Error('something'), + }); + expect(onRenderError).toHaveBeenCalled(); + expect(done).not.toHaveBeenCalled(); + }); + + it('should notify loader handlers on custom error rendering', () => { + const done = jest.fn(); + hook.rerender({ expression: 'something', hasCustomErrorRenderer: true }); + + expect(expressionLoaderSpy).toHaveBeenCalledTimes(1); + + const [[, , loaderParams]] = expressionLoaderSpy.mock.calls; + act(() => + loaderParams?.onRenderError?.(document.createElement('div'), new Error('something'), { + done, + } as unknown as IInterpreterRenderHandlers) + ); + + expect(done).toHaveBeenCalled(); + }); + + it('should update loading state', () => { + expect(hook.result.current).toHaveProperty('isLoading', false); + act(() => expressionLoader.loading$.next()); + expect(hook.result.current).toHaveProperty('isLoading', true); + }); + + it('should call the event handler', () => { + const onEvent = jest.fn(); + hook.rerender({ onEvent, expression: 'something' }); + act(() => expressionLoader.events$.next('event')); + + expect(onEvent).toHaveBeenCalledWith('event'); + }); + + it('should call the data handler', () => { + const adapters = {}; + const onData$ = jest.fn(); + hook.rerender({ onData$, expression: 'something' }); + expressionLoader.inspect.mockReturnValueOnce(adapters); + act(() => expressionLoader.data$.next({ partial: true, result: 'something' })); + + expect(onData$).toHaveBeenCalledWith('something', adapters, true); + }); + + it('should update on loader options changes', () => { + const adapters = {}; + const onData$ = jest.fn(); + hook.rerender({ onData$, expression: 'something' }); + expressionLoader.inspect.mockReturnValueOnce(adapters); + act(() => expressionLoader.data$.next({ partial: true, result: 'something' })); + + expect(onData$).toHaveBeenCalledWith('something', adapters, true); + }); + + it('should call the render handler', () => { + const onRender$ = jest.fn(); + hook.rerender({ onRender$, expression: 'something' }); + act(() => expressionLoader.render$.next(1)); + + expect(hook.result.current).toEqual({ + isEmpty: false, + isLoading: false, + error: null, + }); + expect(onRender$).toHaveBeenCalledWith(1); + }); + + it('should not call the render handler when there is a custom error renderer', () => { + const onRender$ = jest.fn(); + hook.rerender({ onRender$, expression: 'something', hasCustomErrorRenderer: true }); + + expect(expressionLoaderSpy).toHaveBeenCalledTimes(1); + + const [[, , loaderParams]] = expressionLoaderSpy.mock.calls; + act(() => + loaderParams?.onRenderError?.(document.createElement('div'), new Error('something'), { + done: jest.fn(), + } as unknown as IInterpreterRenderHandlers) + ); + act(() => expressionLoader.render$.next(1)); + + expect(hook.result.current).toEqual({ + isEmpty: false, + isLoading: false, + error: new Error('something'), + }); + expect(onRender$).not.toHaveBeenCalled(); + }); + + it('should update on reload', () => { + const reload$ = new Subject(); + hook.rerender({ reload$, expression: 'something' }); + + expect(expressionLoader.update).not.toHaveBeenCalled(); + reload$.next(); + expect(expressionLoader.update).toHaveBeenCalledWith('something', {}); + }); +}); diff --git a/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts new file mode 100644 index 0000000000000..49af9f19b3b88 --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/use_expression_renderer.ts @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { RefObject } from 'react'; +import { useRef, useEffect, useState, useLayoutEffect } from 'react'; +import { Observable } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; +import { ExpressionAstExpression, IInterpreterRenderHandlers } from '../../common'; +import { ExpressionLoader } from '../loader'; +import { IExpressionLoaderParams, ExpressionRenderError, ExpressionRendererEvent } from '../types'; +import { useDebouncedValue } from './use_debounced_value'; +import { useShallowMemo } from './use_shallow_memo'; + +export interface ExpressionRendererParams extends IExpressionLoaderParams { + debounce?: number; + expression: string | ExpressionAstExpression; + hasCustomErrorRenderer?: boolean; + onData$?( + data: TData, + adapters?: TInspectorAdapters, + partial?: boolean + ): void; + onEvent?(event: ExpressionRendererEvent): void; + onRender$?(item: number): void; + /** + * An observable which can be used to re-run the expression without destroying the component + */ + reload$?: Observable; +} + +interface ExpressionRendererState { + isEmpty: boolean; + isLoading: boolean; + error: null | ExpressionRenderError; +} + +export function useExpressionRenderer( + nodeRef: RefObject, + { + debounce, + expression, + hasCustomErrorRenderer, + onData$, + onEvent, + onRender$, + reload$, + ...loaderParams + }: ExpressionRendererParams +): ExpressionRendererState { + const [isEmpty, setEmpty] = useState(true); + const [isLoading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const memoizedOptions = useShallowMemo({ expression, params: useShallowMemo(loaderParams) }); + const [{ expression: debouncedExpression, params: debouncedLoaderParams }, isDebounced] = + useDebouncedValue(memoizedOptions, debounce); + + const expressionLoaderRef = useRef(null); + + // flag to skip next render$ notification, + // because of just handled error + const hasHandledErrorRef = useRef(false); + // will call done() in LayoutEffect when done with rendering custom error state + const errorRenderHandlerRef = useRef(null); + + /* eslint-disable react-hooks/exhaustive-deps */ + // OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() + useEffect(() => { + expressionLoaderRef.current = + nodeRef.current && + new ExpressionLoader(nodeRef.current, debouncedExpression, { + ...debouncedLoaderParams, + // react component wrapper provides different + // error handling api which is easier to work with from react + // if custom renderError is not provided then we fallback to default error handling from ExpressionLoader + onRenderError: (domNode, newError, handlers) => { + errorRenderHandlerRef.current = handlers; + setEmpty(false); + setError(newError); + setLoading(false); + + return debouncedLoaderParams.onRenderError?.(domNode, newError, handlers); + }, + }); + + const subscription = expressionLoaderRef.current?.loading$.subscribe(() => { + hasHandledErrorRef.current = false; + setLoading(true); + }); + + return () => { + subscription?.unsubscribe(); + expressionLoaderRef.current?.destroy(); + expressionLoaderRef.current = null; + errorRenderHandlerRef.current = null; + }; + }, [ + debouncedLoaderParams.onRenderError, + debouncedLoaderParams.interactive, + debouncedLoaderParams.renderMode, + debouncedLoaderParams.syncColors, + ]); + + useEffect(() => { + const subscription = onEvent && expressionLoaderRef.current?.events$.subscribe(onEvent); + + return () => subscription?.unsubscribe(); + }, [expressionLoaderRef.current, onEvent]); + + useEffect(() => { + const subscription = + onData$ && + expressionLoaderRef.current?.data$.subscribe(({ partial, result }) => { + onData$(result, expressionLoaderRef.current?.inspect(), partial); + }); + + return () => subscription?.unsubscribe(); + }, [expressionLoaderRef.current, onData$]); + + useEffect(() => { + const subscription = expressionLoaderRef.current?.render$ + .pipe(filter(() => !hasHandledErrorRef.current)) + .subscribe((item) => { + setEmpty(false); + setError(null); + setLoading(false); + onRender$?.(item); + }); + + return () => subscription?.unsubscribe(); + }, [expressionLoaderRef.current, onRender$]); + /* eslint-enable react-hooks/exhaustive-deps */ + + useEffect(() => { + const subscription = reload$?.subscribe(() => { + expressionLoaderRef.current?.update(debouncedExpression, debouncedLoaderParams); + }); + + return () => subscription?.unsubscribe(); + }, [reload$, debouncedExpression, debouncedLoaderParams]); + + useUpdateEffect(() => { + expressionLoaderRef.current?.update(debouncedExpression, debouncedLoaderParams); + }, [debouncedExpression, debouncedLoaderParams]); + + // call expression loader's done() handler when finished rendering custom error state + useLayoutEffect(() => { + if (error && hasCustomErrorRenderer) { + hasHandledErrorRef.current = true; + errorRenderHandlerRef.current?.done(); + } + + errorRenderHandlerRef.current = null; + }, [error, hasCustomErrorRenderer]); + + return { + error, + isEmpty, + isLoading: isLoading || isDebounced, + }; +} diff --git a/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts b/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts new file mode 100644 index 0000000000000..57ffe707befa0 --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useShallowMemo } from './use_shallow_memo'; + +describe('useShallowMemo', () => { + it('should return the initial value', () => { + const value = { a: 'b' }; + const { result } = renderHook(useShallowMemo, { initialProps: value }); + + expect(result.current).toBe(value); + }); + + it('should return the same value for a shallow copy', () => { + const value = { a: 'b', c: 'd' }; + const newValue = { a: 'b', c: 'd' }; + const hook = renderHook(useShallowMemo, { initialProps: value }); + hook.rerender(newValue); + + expect(hook.result.current).toBe(value); + }); + + it('should return the updated value', () => { + const value = { a: { b: 'c' } }; + const newValue = { a: { b: 'c' } }; + const hook = renderHook(useShallowMemo, { initialProps: value }); + hook.rerender(newValue); + + expect(hook.result.current).toBe(newValue); + }); +}); diff --git a/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.ts b/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.ts new file mode 100644 index 0000000000000..97b5540232c02 --- /dev/null +++ b/src/plugins/expressions/public/react_expression_renderer/use_shallow_memo.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line @typescript-eslint/triple-slash-reference, spaced-comment +/// + +import { useRef } from 'react'; +import shallowEqual from 'react-redux/lib/utils/shallowEqual'; + +export function useShallowMemo(value: T): T { + const previousRef = useRef(value); + + if (!shallowEqual(previousRef.current, value)) { + previousRef.current = value; + } + + return previousRef.current; +} diff --git a/src/plugins/expressions/public/react_expression_renderer_wrapper.tsx b/src/plugins/expressions/public/react_expression_renderer_wrapper.tsx index 45295da0a9ae8..fe0bfcc42d602 100644 --- a/src/plugins/expressions/public/react_expression_renderer_wrapper.tsx +++ b/src/plugins/expressions/public/react_expression_renderer_wrapper.tsx @@ -10,7 +10,11 @@ import React, { lazy, Suspense } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import type { ReactExpressionRendererProps } from './react_expression_renderer'; -const ReactExpressionRendererComponent = lazy(() => import('./react_expression_renderer')); +const ReactExpressionRendererComponent = lazy(async () => { + const { ReactExpressionRenderer } = await import('./react_expression_renderer'); + + return { default: ReactExpressionRenderer }; +}); export const ReactExpressionRenderer = (props: ReactExpressionRendererProps) => ( }> diff --git a/src/plugins/home/public/application/components/add_data/add_data.test.tsx b/src/plugins/home/public/application/components/add_data/add_data.test.tsx index 3aa51f89c7d67..009b72791263b 100644 --- a/src/plugins/home/public/application/components/add_data/add_data.test.tsx +++ b/src/plugins/home/public/application/components/add_data/add_data.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { AddData } from './add_data'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ApplicationStart } from 'kibana/public'; jest.mock('../app_navigation_handler', () => { diff --git a/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx b/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx index fb222be657786..f602ee5aa4439 100644 --- a/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx +++ b/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ManageData } from './manage_data'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ApplicationStart } from 'kibana/public'; import { FeatureCatalogueEntry, FeatureCatalogueCategory } from '../../../services'; diff --git a/src/plugins/home/public/application/components/recently_accessed.test.js b/src/plugins/home/public/application/components/recently_accessed.test.js index 14611d26aa8e5..95f151923f425 100644 --- a/src/plugins/home/public/application/components/recently_accessed.test.js +++ b/src/plugins/home/public/application/components/recently_accessed.test.js @@ -10,7 +10,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { RecentlyAccessed, NUM_LONG_LINKS } from './recently_accessed'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; const createRecentlyAccessed = (length) => { const recentlyAccessed = []; diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.test.js b/src/plugins/home/public/application/components/tutorial/instruction_set.test.js index 6faadf275bea3..8c0ce306d9c05 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.test.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.test.js @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { InstructionSet } from './instruction_set'; import * as StatusCheckStates from './status_check_states'; diff --git a/src/plugins/home/public/application/components/tutorial/introduction.test.js b/src/plugins/home/public/application/components/tutorial/introduction.test.js index 70b4856a8b2fd..c599c8748e925 100644 --- a/src/plugins/home/public/application/components/tutorial/introduction.test.js +++ b/src/plugins/home/public/application/components/tutorial/introduction.test.js @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { Introduction } from './introduction'; import { httpServiceMock } from '../../../../../../core/public/mocks'; diff --git a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js index 0efcd9bf9df9c..67ae2d1dd2eed 100644 --- a/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js +++ b/src/plugins/home/public/application/components/tutorial/saved_objects_installer.test.js @@ -8,7 +8,7 @@ import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { SavedObjectsInstaller } from './saved_objects_installer'; diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.test.js b/src/plugins/home/public/application/components/tutorial/tutorial.test.js index 73499c0dcb75f..9bfe100c4ce60 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.test.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.test.js @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { Tutorial } from './tutorial'; diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 25923f247ca8b..c2f56c1d6049d 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -544,7 +544,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Tx. last week', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', timeShift: '1w', }, 'ddc92e50-4d5c-413e-b91b-3e504889fa65': { @@ -554,7 +554,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Transactions', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', }, 'eadae280-2da3-4d1d-a0e1-f9733f89c15b': { customLabel: true, @@ -743,7 +743,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Count of records', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', }, '9f61a7df-198e-4754-b34c-81ed544136ba': { dataType: 'string', @@ -1070,7 +1070,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Items', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', }, 'd77cdd24-dedc-48dd-9a4b-d34c6f1a6c46': { customLabel: true, @@ -1186,7 +1186,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Items', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', }, 'd77cdd24-dedc-48dd-9a4b-d34c6f1a6c46': { customLabel: true, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index 840fc6e2c175c..aa2a0d077e30a 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -249,7 +249,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Part of count() / overall_sum(count())', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', }, 'b5f3dc78-dba8-4db8-87b6-24a0b9cca260X1': { customLabel: true, @@ -258,7 +258,7 @@ export const getSavedObjects = (): SavedObject[] => [ label: 'Part of count() / overall_sum(count())', operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: '___records___', }, 'b5f3dc78-dba8-4db8-87b6-24a0b9cca260X2': { customLabel: true, diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index 3257b7dab0825..2182843d30975 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { getDepsMock, getIndexPatternMock } from '../../test_utils'; import ControlsTab, { ControlsTabProps } from './controls_tab'; diff --git a/src/plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx b/src/plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx index 27597bb3a1e52..3192cd3db54ad 100644 --- a/src/plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/list_control_editor.test.tsx @@ -12,7 +12,7 @@ import { shallow } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { getIndexPatternMock } from '../../test_utils/get_index_pattern_mock'; import { ListControlEditor } from './list_control_editor'; import { ControlParams } from '../../editor_utils'; diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx index 8b886134c1173..45907fc08e1c2 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { Vis } from '../../../../visualizations/public'; import OptionsTab, { OptionsTabProps } from './options_tab'; diff --git a/src/plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx b/src/plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index 2494a0b57dbc5..67ee222b3bae9 100644 --- a/src/plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { SinonSpy, spy, assert } from 'sinon'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx b/src/plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx index 3eb78cfe81544..6d9f2f9b05ade 100644 --- a/src/plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { InputControlVis } from './input_control_vis'; diff --git a/src/plugins/input_control_vis/public/components/vis/list_control.test.tsx b/src/plugins/input_control_vis/public/components/vis/list_control.test.tsx index 5f102b58a7b42..cf90ad9837bfe 100644 --- a/src/plugins/input_control_vis/public/components/vis/list_control.test.tsx +++ b/src/plugins/input_control_vis/public/components/vis/list_control.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import sinon from 'sinon'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ListControl } from './list_control'; diff --git a/src/plugins/input_control_vis/public/components/vis/range_control.test.tsx b/src/plugins/input_control_vis/public/components/vis/range_control.test.tsx index 4396da3df827b..d51db16952f1e 100644 --- a/src/plugins/input_control_vis/public/components/vis/range_control.test.tsx +++ b/src/plugins/input_control_vis/public/components/vis/range_control.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { RangeControl, ceilWithPrecision, floorWithPrecision } from './range_control'; import { RangeControl as RangeControlClass } from '../../control/range_control_factory'; diff --git a/src/plugins/inspector/public/ui/inspector_panel.test.tsx b/src/plugins/inspector/public/ui/inspector_panel.test.tsx index 03b71219b1e9f..254afca11c1da 100644 --- a/src/plugins/inspector/public/ui/inspector_panel.test.tsx +++ b/src/plugins/inspector/public/ui/inspector_panel.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { InspectorPanel } from './inspector_panel'; import { InspectorViewDescription } from '../types'; import { Adapters } from '../../common'; diff --git a/src/plugins/interactive_setup/public/theme/kibana_theme_provider.test.tsx b/src/plugins/interactive_setup/public/theme/kibana_theme_provider.test.tsx index 21059bd4a8236..5d1bc9798ee17 100644 --- a/src/plugins/interactive_setup/public/theme/kibana_theme_provider.test.tsx +++ b/src/plugins/interactive_setup/public/theme/kibana_theme_provider.test.tsx @@ -13,7 +13,7 @@ import React, { useEffect } from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject, of } from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { CoreTheme } from 'src/core/public'; import { KibanaThemeProvider } from './kibana_theme_provider'; diff --git a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts index 5956bf3a2f64a..e7b326a1d019d 100644 --- a/src/plugins/interactive_setup/server/elasticsearch_service.test.ts +++ b/src/plugins/interactive_setup/server/elasticsearch_service.test.ts @@ -10,7 +10,7 @@ import { errors } from '@elastic/elasticsearch'; import { BehaviorSubject } from 'rxjs'; import tls from 'tls'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; import { pollEsNodesVersion } from '../../../../src/core/server'; diff --git a/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx b/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx index 4c58b034186c4..ed091ff96c5f2 100644 --- a/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx +++ b/src/plugins/kibana_overview/public/components/add_data/add_data.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { AddData } from './add_data'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { FeatureCatalogueCategory } from 'src/plugins/home/public'; const mockFeatures = [ diff --git a/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx b/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx index 2a028a7a53a11..f5e72a3e0a867 100644 --- a/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx +++ b/src/plugins/kibana_overview/public/components/manage_data/manage_data.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ManageData } from './manage_data'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { FeatureCatalogueCategory } from 'src/plugins/home/public'; const mockFeatures = [ diff --git a/src/plugins/kibana_overview/public/components/news_feed/news_feed.test.tsx b/src/plugins/kibana_overview/public/components/news_feed/news_feed.test.tsx index 0215242d0c078..420230e7caef6 100644 --- a/src/plugins/kibana_overview/public/components/news_feed/news_feed.test.tsx +++ b/src/plugins/kibana_overview/public/components/news_feed/news_feed.test.tsx @@ -9,7 +9,7 @@ import moment from 'moment'; import React from 'react'; import { NewsFeed } from './news_feed'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; const mockNewsFetchResult = { error: null, diff --git a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx index b4804fe56f470..85aee7411d7ad 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx @@ -13,7 +13,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { Overview } from './overview'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { FeatureCatalogueCategory } from 'src/plugins/home/public'; const mockNewsFetchResult = { diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx index 62488809dd59b..b66d136c50710 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; -import { mountWithIntl, findTestSubject } from '@kbn/test/jest'; +import { mountWithIntl, findTestSubject } from '@kbn/test-jest-helpers'; import { monaco } from '@kbn/monaco'; import { keys } from '@elastic/eui'; diff --git a/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx b/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx index 1fbde1bb8bfe9..ec48d3fda4c73 100644 --- a/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx +++ b/src/plugins/kibana_react/public/overview_page/overview_page_footer/overview_page_footer.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { OverviewPageFooter } from './overview_page_footer'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; jest.mock('../../app_links', () => ({ RedirectAppLinks: jest.fn((element: JSX.Element) => element), diff --git a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.test.tsx b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.test.tsx index 59d6e8280af98..2c9f91be339f6 100644 --- a/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.test.tsx +++ b/src/plugins/kibana_react/public/page_template/no_data_page/no_data_page.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { NoDataPage } from './no_data_page'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; describe('NoDataPage', () => { test('render', () => { diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx index bdc5ca30216bc..cf8ff8ac8ec53 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.test.tsx @@ -7,7 +7,7 @@ */ import { EuiEmptyPrompt } from '@elastic/eui'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ToastsStart } from 'kibana/public'; import React from 'react'; import { themeServiceMock } from '../../../../../src/core/public/mocks'; diff --git a/src/plugins/kibana_react/public/theme/kibana_theme_provider.test.tsx b/src/plugins/kibana_react/public/theme/kibana_theme_provider.test.tsx index ef7f79cdaa64e..10c1f32e6ec26 100644 --- a/src/plugins/kibana_react/public/theme/kibana_theme_provider.test.tsx +++ b/src/plugins/kibana_react/public/theme/kibana_theme_provider.test.tsx @@ -12,7 +12,7 @@ import type { ReactWrapper } from 'enzyme'; import { of, BehaviorSubject } from 'rxjs'; import { useEuiTheme } from '@elastic/eui'; import type { UseEuiTheme } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { CoreTheme } from 'src/core/public'; import { KibanaThemeProvider } from './kibana_theme_provider'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts index b1cf0ecd2213e..a208832baf719 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/core/core_usage_collector.ts @@ -355,14 +355,14 @@ export function getCoreUsageCollector( type: 'long', _meta: { description: - 'The number of documents in the index, including hidden nested documents.', + 'The number of lucene documents in the index, including hidden nested documents.', }, }, docsDeleted: { type: 'long', _meta: { description: - 'The number of deleted documents in the index, including hidden nested documents.', + 'The number of deleted lucene documents in the index, including hidden nested documents.', }, }, alias: { @@ -382,6 +382,12 @@ export function getCoreUsageCollector( description: 'The size in bytes of the index, for primaries and replicas.', }, }, + savedObjectsDocsCount: { + type: 'long', + _meta: { + description: 'The number of saved objects documents in the index.', + }, + }, }, }, legacyUrlAliases: { diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts index 8d22665fdf2cc..3c748fab8e0c1 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { createMemoryHistory, History } from 'history'; import { createKbnUrlTracker, KbnUrlTracker } from './kbn_url_tracker'; import { BehaviorSubject, Subject } from 'rxjs'; diff --git a/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts b/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts index 2bc1f1e57f117..42a12c3fd0c5d 100644 --- a/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts @@ -7,7 +7,7 @@ */ import { createUrlTracker, IUrlTracker } from './url_tracker'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { createMemoryHistory, History } from 'history'; describe('urlTracker', () => { diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts index 6fac5731d50a3..f752c6e25c8ab 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -22,7 +22,7 @@ import { IKbnUrlStateStorage, ISessionStorageStateStorage, } from './state_sync_state_storage'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { createBrowserHistory, History } from 'history'; import { INullableBaseStateContainer } from './types'; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts index cad2fc2733f3c..a0ab2b3f9da3d 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts @@ -10,7 +10,7 @@ import { createSessionStorageStateStorage, ISessionStorageStateStorage, } from './create_session_storage_state_storage'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; describe('SessionStorageStateStorage', () => { let browserStorage: StubBrowserStorage; diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts index b7ee08044b323..20cc81bc3b75b 100644 --- a/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/hashed_item_store.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { HashedItemStore } from './hashed_item_store'; describe('hashedItemStore', () => { diff --git a/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.ts b/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.ts index 56408d2cbdb5b..0f2176b5d4ba9 100644 --- a/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.ts +++ b/src/plugins/kibana_utils/public/storage/hashed_item_store/mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { HashedItemStore } from './hashed_item_store'; /** diff --git a/src/plugins/kibana_utils/public/theme/kibana_theme_provider.test.tsx b/src/plugins/kibana_utils/public/theme/kibana_theme_provider.test.tsx index 21059bd4a8236..5d1bc9798ee17 100644 --- a/src/plugins/kibana_utils/public/theme/kibana_theme_provider.test.tsx +++ b/src/plugins/kibana_utils/public/theme/kibana_theme_provider.test.tsx @@ -13,7 +13,7 @@ import React, { useEffect } from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject, of } from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { CoreTheme } from 'src/core/public'; import { KibanaThemeProvider } from './kibana_theme_provider'; diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 45b9b4c7a885b..682154dd5d5db 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -12,7 +12,7 @@ import { act } from 'react-dom/test-utils'; import { MountPoint } from 'kibana/public'; import { TopNavMenu } from './top_nav_menu'; import { TopNavMenuData } from './top_nav_menu_data'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; const dataShim = { ui: { diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx index 8d62a3ebd9633..2211ae5db1052 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { TopNavMenuItem } from './top_nav_menu_item'; import { TopNavMenuData } from './top_nav_menu_data'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; describe('TopNavMenu', () => { const ensureMenuItemDisabled = (data: TopNavMenuData) => { diff --git a/src/plugins/presentation_util/public/i18n/labs.tsx b/src/plugins/presentation_util/public/i18n/labs.tsx index ee8f15f421487..c7fafcc89f060 100644 --- a/src/plugins/presentation_util/public/i18n/labs.tsx +++ b/src/plugins/presentation_util/public/i18n/labs.tsx @@ -89,7 +89,7 @@ export const LabsStrings = { }), getDescriptionMessage: () => i18n.translate('presentationUtil.labs.components.descriptionMessage', { - defaultMessage: 'Try out our features that are in progress or experimental.', + defaultMessage: 'Try out features that are in progress or in technical preview.', }), getResetToDefaultLabel: () => i18n.translate('presentationUtil.labs.components.resetToDefaultLabel', { diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx index 2c530df7b93ae..8e939ec58a792 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx @@ -10,7 +10,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { SavedObjectSaveModal } from './saved_object_save_modal'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; describe('SavedObjectSaveModal', () => { it('should render matching snapshot', () => { diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx index 843468b78307c..4e852de7ee731 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/inspect.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ShallowWrapper } from 'enzyme'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { Inspect, InspectProps } from './inspect'; import { SavedObjectWithMetadata } from '../../../../common'; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx index 554e34a9fc55a..ba0a584a94074 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/saved_object_view.test.tsx @@ -10,7 +10,7 @@ import { bulkGetObjectsMock } from './saved_object_view.test.mocks'; import React from 'react'; import { ShallowWrapper } from 'enzyme'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { httpServiceMock, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx index 396ef209cc396..3c24d68007222 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/delete_confirm_modal.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { SavedObjectWithMetadata, SavedObjectManagementTypeInfo } from '../../../../common'; import { DeleteConfirmModal } from './delete_confirm_modal'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx index eb364dbf69102..609338f7aed38 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/export_modal.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExportModal } from './export_modal'; describe('ExportModal', () => { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx index a084c26b04d2d..04d4556548720 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.test.tsx @@ -9,7 +9,7 @@ import { importFileMock, resolveImportErrorsMock } from './flyout.test.mocks'; import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { coreMock, httpServiceMock } from '../../../../../../core/public/mocks'; import { Flyout, FlyoutProps, FlyoutState } from './flyout'; import { ShallowWrapper } from 'enzyme'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_mode_control.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_mode_control.test.tsx index fbf50e0ee0c86..481e8cd91903f 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_mode_control.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_mode_control.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; -import { shallowWithI18nProvider, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithIntl } from '@kbn/test-jest-helpers'; import { ImportModeControl, ImportModeControlProps } from './import_mode_control'; describe('ImportModeControl', () => { diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx index 4cbfcb06b3595..03d3631398cda 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/import_summary.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; -import { mountWithI18nProvider } from '@kbn/test/jest'; +import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { httpServiceMock } from '../../../../../../core/public/mocks'; import { ImportSummary, ImportSummaryProps } from './import_summary'; import { FailedImport } from '../../../lib'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.test.tsx index ef9020d231f31..c07ab70079ccf 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/overwrite_modal.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithIntl } from '@kbn/test-jest-helpers'; import { OverwriteModalProps, OverwriteModal } from './overwrite_modal'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx index a3db8af627d2f..751335e4670d9 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { httpServiceMock } from '../../../../../../core/public/mocks'; import type { SavedObjectManagementTypeInfo } from '../../../../common/types'; import { Relationships, RelationshipsProps } from './relationships'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx index 6fb153cb980ff..c0b795f9e9610 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { keys } from '@elastic/eui'; import { httpServiceMock } from '../../../../../../core/public/mocks'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index 173d8af0de04d..5382cc39989ed 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -19,7 +19,7 @@ import { import React from 'react'; import { Query } from '@elastic/eui'; import { ShallowWrapper } from 'enzyme'; -import { shallowWithI18nProvider } from '@kbn/test/jest'; +import { shallowWithI18nProvider } from '@kbn/test-jest-helpers'; import { httpServiceMock, overlayServiceMock, diff --git a/src/plugins/telemetry/public/components/opt_in_banner.test.tsx b/src/plugins/telemetry/public/components/opt_in_banner.test.tsx index b892633b66b9a..1c4467db803c4 100644 --- a/src/plugins/telemetry/public/components/opt_in_banner.test.tsx +++ b/src/plugins/telemetry/public/components/opt_in_banner.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { OptInBanner } from './opt_in_banner'; describe('OptInDetailsComponent', () => { diff --git a/src/plugins/telemetry/public/components/opt_in_message.test.tsx b/src/plugins/telemetry/public/components/opt_in_message.test.tsx index c27e4326c6625..1196e36a1b424 100644 --- a/src/plugins/telemetry/public/components/opt_in_message.test.tsx +++ b/src/plugins/telemetry/public/components/opt_in_message.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { OptInMessage } from './opt_in_message'; describe('OptInMessage', () => { diff --git a/src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx b/src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx index ce5a42ed17ad8..e4ec8e32c5456 100644 --- a/src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx +++ b/src/plugins/telemetry/public/components/opted_in_notice_banner.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { OptedInNoticeBanner } from './opted_in_notice_banner'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { httpServiceMock } from '../../../../core/public/http/http_service.mock'; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 3269452ca8cc3..dcbf919698243 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -6248,13 +6248,13 @@ "docsCount": { "type": "long", "_meta": { - "description": "The number of documents in the index, including hidden nested documents." + "description": "The number of lucene documents in the index, including hidden nested documents." } }, "docsDeleted": { "type": "long", "_meta": { - "description": "The number of deleted documents in the index, including hidden nested documents." + "description": "The number of deleted lucene documents in the index, including hidden nested documents." } }, "alias": { @@ -6274,6 +6274,12 @@ "_meta": { "description": "The size in bytes of the index, for primaries and replicas." } + }, + "savedObjectsDocsCount": { + "type": "long", + "_meta": { + "description": "The number of saved objects documents in the index." + } } } } diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts new file mode 100644 index 0000000000000..736367446d3c0 --- /dev/null +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { registerTelemetryUsageStatsRoutes } from './telemetry_usage_stats'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import type { RequestHandlerContext, IRouter } from 'kibana/server'; +import { telemetryCollectionManagerPluginMock } from '../../../telemetry_collection_manager/server/mocks'; + +async function runRequest( + mockRouter: IRouter, + body?: { unencrypted?: boolean; refreshCache?: boolean } +) { + expect(mockRouter.post).toBeCalled(); + const [, handler] = (mockRouter.post as jest.Mock).mock.calls[0]; + const mockResponse = httpServerMock.createResponseFactory(); + const mockRequest = httpServerMock.createKibanaRequest({ body }); + await handler(null, mockRequest, mockResponse); + + return { mockResponse, mockRequest }; +} + +describe('registerTelemetryUsageStatsRoutes', () => { + const router = { + handler: undefined, + config: undefined, + post: jest.fn().mockImplementation((config, handler) => { + router.config = config; + router.handler = handler; + }), + }; + const telemetryCollectionManager = telemetryCollectionManagerPluginMock.createSetupContract(); + const mockCoreSetup = coreMock.createSetup(); + const mockRouter = mockCoreSetup.http.createRouter(); + const mockStats = [{ clusterUuid: 'text', stats: 'enc_str' }]; + telemetryCollectionManager.getStats.mockResolvedValue(mockStats); + + describe('clusters/_stats POST route', () => { + it('registers _stats POST route and accepts body configs', () => { + registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true); + expect(mockRouter.post).toBeCalledTimes(1); + const [routeConfig, handler] = (mockRouter.post as jest.Mock).mock.calls[0]; + expect(routeConfig.path).toMatchInlineSnapshot(`"/api/telemetry/v2/clusters/_stats"`); + expect(Object.keys(routeConfig.validate.body.props)).toEqual(['unencrypted', 'refreshCache']); + expect(handler).toBeInstanceOf(Function); + }); + + it('responds with encrypted stats with no cache refresh by default', async () => { + registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true); + + const { mockRequest, mockResponse } = await runRequest(mockRouter); + expect(telemetryCollectionManager.getStats).toBeCalledWith({ + request: mockRequest, + unencrypted: undefined, + refreshCache: undefined, + }); + expect(mockResponse.ok).toBeCalled(); + expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: mockStats }); + }); + + it('when unencrypted is set getStats is called with unencrypted and refreshCache', async () => { + registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true); + + const { mockRequest } = await runRequest(mockRouter, { unencrypted: true }); + expect(telemetryCollectionManager.getStats).toBeCalledWith({ + request: mockRequest, + unencrypted: true, + refreshCache: true, + }); + }); + + it('calls getStats with refreshCache when set in body', async () => { + registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true); + const { mockRequest } = await runRequest(mockRouter, { refreshCache: true }); + expect(telemetryCollectionManager.getStats).toBeCalledWith({ + request: mockRequest, + unencrypted: undefined, + refreshCache: true, + }); + }); + + it('calls getStats with refreshCache:true even if set to false in body when unencrypted is set to true', async () => { + registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true); + const { mockRequest } = await runRequest(mockRouter, { + refreshCache: false, + unencrypted: true, + }); + expect(telemetryCollectionManager.getStats).toBeCalledWith({ + request: mockRequest, + unencrypted: true, + refreshCache: true, + }); + }); + + it.todo('always returns an empty array on errors on encrypted payload'); + it.todo('returns the actual request error object when in development mode'); + it.todo('returns forbidden on unencrypted and ES returns 403 in getStats'); + }); +}); diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index e3ce8cbc5190a..2f72ae818f112 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -35,7 +35,7 @@ export function registerTelemetryUsageStatsRoutes( const statsConfig: StatsGetterConfig = { request: req, unencrypted, - refreshCache, + refreshCache: unencrypted || refreshCache, }; const stats = await telemetryCollectionManager.getStats(statsConfig); diff --git a/src/plugins/telemetry_collection_manager/server/mocks.ts b/src/plugins/telemetry_collection_manager/server/mocks.ts new file mode 100644 index 0000000000000..cbd9dac9cfaa6 --- /dev/null +++ b/src/plugins/telemetry_collection_manager/server/mocks.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { + TelemetryCollectionManagerPluginSetup, + TelemetryCollectionManagerPluginStart, +} from './types'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +export const telemetryCollectionManagerPluginMock = { + createSetupContract, + createStartContract, +}; + +function createSetupContract(): Setup { + const setupContract: Setup = { + getStats: jest.fn(), + getOptInStats: jest.fn(), + setCollectionStrategy: jest.fn(), + }; + + return setupContract; +} + +function createStartContract(): Start { + const startContract: Start = { + getOptInStats: jest.fn(), + getStats: jest.fn(), + }; + + return startContract; +} diff --git a/src/plugins/telemetry_collection_manager/server/plugin.test.ts b/src/plugins/telemetry_collection_manager/server/plugin.test.ts index 77cc2ac9ca510..ca932e92d98bd 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.test.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.test.ts @@ -135,6 +135,45 @@ describe('Telemetry Collection Manager', () => { collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient ).toBeInstanceOf(TelemetrySavedObjectsClient); }); + + it('calls getStats with passed refreshCache config', async () => { + const getStatsCollectionConfig: jest.SpyInstance< + TelemetryCollectionManagerPlugin['getStatsCollectionConfig'] + // @ts-expect-error spying on private method. + > = jest.spyOn(telemetryCollectionManager, 'getStatsCollectionConfig'); + await setupApi.getStats(config); + await setupApi.getStats({ ...config, refreshCache: false }); + await setupApi.getStats({ ...config, refreshCache: true }); + + expect(getStatsCollectionConfig).toBeCalledTimes(3); + expect(getStatsCollectionConfig).toHaveBeenNthCalledWith(1, config, usageCollection); + expect(getStatsCollectionConfig).toHaveNthReturnedWith( + 1, + expect.objectContaining({ refreshCache: false }) + ); + + expect(getStatsCollectionConfig).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ refreshCache: false }), + usageCollection + ); + expect(getStatsCollectionConfig).toHaveNthReturnedWith( + 2, + expect.objectContaining({ refreshCache: false }) + ); + + expect(getStatsCollectionConfig).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ refreshCache: true }), + usageCollection + ); + expect(getStatsCollectionConfig).toHaveNthReturnedWith( + 3, + expect.objectContaining({ refreshCache: true }) + ); + + getStatsCollectionConfig.mockRestore(); + }); }); describe('getOptInStats', () => { @@ -178,9 +217,10 @@ describe('Telemetry Collection Manager', () => { }); }); describe('unencrypted: true', () => { + const mockRequest = httpServerMock.createKibanaRequest(); const config: StatsGetterConfig = { unencrypted: true, - request: httpServerMock.createKibanaRequest(), + request: mockRequest, }; describe('getStats', () => { @@ -212,23 +252,26 @@ describe('Telemetry Collection Manager', () => { ).not.toBeInstanceOf(TelemetrySavedObjectsClient); }); - test('returns cached object on multiple calls', async () => { - collectionStrategy.clusterDetailsGetter.mockResolvedValue([ - { clusterUuid: 'clusterUuid' }, - ]); - collectionStrategy.statsGetter.mockResolvedValue([basicStats]); + it('calls getStats with config { refreshCache: true } even if set to false', async () => { + const getStatsCollectionConfig: jest.SpyInstance< + TelemetryCollectionManagerPlugin['getStatsCollectionConfig'] + // @ts-expect-error spying on private method. + > = jest.spyOn(telemetryCollectionManager, 'getStatsCollectionConfig'); await setupApi.getStats(config); - await expect(setupApi.getStats(config)).resolves.toStrictEqual([ - { - clusterUuid: 'clusterUuid', - stats: { - ...basicStats, - cacheDetails: { updatedAt: expect.any(String), fetchedAt: expect.any(String) }, - collectionSource: 'test_collection', - }, - }, - ]); + expect(getStatsCollectionConfig).toBeCalledTimes(1); + expect(getStatsCollectionConfig).toBeCalledWith( + expect.not.objectContaining({ refreshCache: true }), + usageCollection + ); + expect(getStatsCollectionConfig).toReturnWith( + expect.objectContaining({ + refreshCache: true, + kibanaRequest: mockRequest, + }) + ); + + getStatsCollectionConfig.mockRestore(); }); }); diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index bdce2c8be31d8..fad51ca1dbfde 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -127,7 +127,7 @@ export class TelemetryCollectionManagerPlugin const soClient = this.getSavedObjectsClient(config); // Provide the kibanaRequest so opted-in plugins can scope their custom clients only if the request is not encrypted const kibanaRequest = config.unencrypted ? config.request : void 0; - const refreshCache = !!config.refreshCache; + const refreshCache = config.unencrypted ? true : !!config.refreshCache; if (esClient && soClient) { return { usageCollection, esClient, soClient, kibanaRequest, refreshCache }; diff --git a/src/plugins/telemetry_management_section/public/components/opt_in_example_flyout.test.tsx b/src/plugins/telemetry_management_section/public/components/opt_in_example_flyout.test.tsx index 52c6ecfefbacf..845d7d3fa0c8a 100644 --- a/src/plugins/telemetry_management_section/public/components/opt_in_example_flyout.test.tsx +++ b/src/plugins/telemetry_management_section/public/components/opt_in_example_flyout.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { OptInExampleFlyout } from './opt_in_example_flyout'; describe('OptInDetailsComponent', () => { diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx index 8f27c340720a1..4b474f0cc7bdd 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import TelemetryManagementSection from './telemetry_management_section'; import { TelemetryService } from '../../../telemetry/public/services'; import { coreMock } from '../../../../core/public/mocks'; @@ -257,7 +257,7 @@ describe('TelemetryManagementSectionComponent', () => { await expect( toggleOptInComponent.prop('handleChange')() ).resolves.toBe(true); - // TODO: Fix `mountWithIntl` types in @kbn/test/jest to make testing easier + // TODO: Fix `mountWithIntl` types in @kbn/test-jest-helpers to make testing easier expect((component.state() as { enabled: boolean }).enabled).toBe(true); await expect( toggleOptInComponent.prop('handleChange')() diff --git a/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx index af363dbacaff7..87c74d0c497a7 100644 --- a/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { NumberList, NumberListProps } from './number_list'; import { NumberRow } from './number_row'; diff --git a/src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx b/src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx index 02cc0aadfff61..00da70d7687f8 100644 --- a/src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DateRangesParamEditor } from './date_ranges'; import { KibanaContextProvider } from '../../../../kibana_react/public'; import { docLinksServiceMock } from '../../../../../core/public/mocks'; diff --git a/src/plugins/vis_default_editor/public/components/controls/palette_picker.test.tsx b/src/plugins/vis_default_editor/public/components/controls/palette_picker.test.tsx index 21fdde725cd91..a02bdc6cdb6b4 100644 --- a/src/plugins/vis_default_editor/public/components/controls/palette_picker.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/palette_picker.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { PalettePicker, PalettePickerProps } from './palette_picker'; import { chartPluginMock } from '../../../../charts/public/mocks'; diff --git a/src/plugins/vis_default_editor/public/components/controls/percentiles.test.tsx b/src/plugins/vis_default_editor/public/components/controls/percentiles.test.tsx index c009196c20d8c..a94c68c944d12 100644 --- a/src/plugins/vis_default_editor/public/components/controls/percentiles.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/percentiles.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { AggParamEditorProps } from '../agg_param_props'; import { IAggConfig } from 'src/plugins/data/public'; -import { mountWithIntl as mount } from '@kbn/test/jest'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { PercentilesEditor } from './percentiles'; import { EditorVisState } from '../sidebar/state/reducers'; diff --git a/src/plugins/vis_default_editor/public/components/controls/size.test.tsx b/src/plugins/vis_default_editor/public/components/controls/size.test.tsx index 0d86b6074bce5..9081ed5745254 100644 --- a/src/plugins/vis_default_editor/public/components/controls/size.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/size.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { EuiIconTip } from '@elastic/eui'; import { SizeParamEditor, SizeParamEditorProps } from './size'; import { aggParamCommonPropsMock } from './test_utils'; diff --git a/src/plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx b/src/plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx index 9c83403c60cb4..95e3a74a66d70 100644 --- a/src/plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { AggregateValueProp, TopAggregateParamEditor, diff --git a/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx b/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx index a95a47bf7af88..0db6473d74d0e 100644 --- a/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx +++ b/src/plugins/vis_default_editor/public/components/options/long_legend_options.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { LongLegendOptions, LongLegendOptionsProps } from './long_legend_options'; import { EuiFieldNumber } from '@elastic/eui'; diff --git a/src/plugins/vis_default_editor/public/components/options/percentage_mode.test.tsx b/src/plugins/vis_default_editor/public/components/options/percentage_mode.test.tsx index 05d321a7b465c..5f45f619d2e4d 100644 --- a/src/plugins/vis_default_editor/public/components/options/percentage_mode.test.tsx +++ b/src/plugins/vis_default_editor/public/components/options/percentage_mode.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { PercentageModeOption, PercentageModeOptionProps } from './percentage_mode'; import { EuiFieldText } from '@elastic/eui'; diff --git a/src/plugins/vis_types/heatmap/public/editor/components/heatmap.test.tsx b/src/plugins/vis_types/heatmap/public/editor/components/heatmap.test.tsx index 5f57083072202..bb1467c658f76 100644 --- a/src/plugins/vis_types/heatmap/public/editor/components/heatmap.test.tsx +++ b/src/plugins/vis_types/heatmap/public/editor/components/heatmap.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import type { PersistedState } from '../../../../../visualizations/public'; import HeatmapOptions, { HeatmapOptionsProps } from './heatmap'; diff --git a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx index f69a47beae280..775d689e3ec68 100644 --- a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx +++ b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx @@ -26,7 +26,7 @@ export const getHeatmapVisTypeDefinition = ({ title: i18n.translate('visTypeHeatmap.heatmap.heatmapTitle', { defaultMessage: 'Heat map' }), icon: 'heatmap', description: i18n.translate('visTypeHeatmap.heatmap.heatmapDescription', { - defaultMessage: 'Shade data in cells in a matrix.', + defaultMessage: 'Display values as colors in a matrix.', }), toExpressionAst, getSupportedTriggers: () => [VIS_EVENT_TO_TRIGGER.filter], diff --git a/src/plugins/vis_types/pie/kibana.json b/src/plugins/vis_types/pie/kibana.json index fb310d8afd82d..abed576cc6732 100644 --- a/src/plugins/vis_types/pie/kibana.json +++ b/src/plugins/vis_types/pie/kibana.json @@ -3,7 +3,7 @@ "version": "kibana", "ui": true, "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPie"], + "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis"], "requiredBundles": ["visDefaultEditor"], "extraPublicDirs": ["common/index"], "owner": { diff --git a/src/plugins/vis_types/pie/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/vis_types/pie/public/__snapshots__/to_ast.test.ts.snap index 2edf2fff72a38..904dff6ee1192 100644 --- a/src/plugins/vis_types/pie/public/__snapshots__/to_ast.test.ts.snap +++ b/src/plugins/vis_types/pie/public/__snapshots__/to_ast.test.ts.snap @@ -34,9 +34,6 @@ Object { }, Object { "arguments": Object { - "addLegend": Array [ - true, - ], "addTooltip": Array [ true, ], @@ -70,7 +67,7 @@ Object { "chain": Array [ Object { "arguments": Object { - "lastLevel": Array [ + "last_level": Array [ true, ], "show": Array [ @@ -83,13 +80,16 @@ Object { true, ], }, - "function": "pie_labels", + "function": "partitionLabels", "type": "function", }, ], "type": "expression", }, ], + "legendDisplay": Array [ + "show", + ], "legendPosition": Array [ "right", ], @@ -112,6 +112,9 @@ Object { "type": "expression", }, ], + "nestedLegend": Array [ + false, + ], "palette": Array [ Object { "chain": Array [ @@ -124,8 +127,11 @@ Object { "type": "expression", }, ], + "startFromSecondLargestSlice": Array [ + false, + ], }, - "function": "pie_vis", + "function": "pieVis", "type": "function", }, ], diff --git a/src/plugins/vis_types/pie/public/editor/collections.ts b/src/plugins/vis_types/pie/public/editor/collections.ts index 16e6bd9372897..dd9d3fb3737b0 100644 --- a/src/plugins/vis_types/pie/public/editor/collections.ts +++ b/src/plugins/vis_types/pie/public/editor/collections.ts @@ -11,7 +11,7 @@ import { LabelPositions, ValueFormats, EmptySizeRatios, -} from '../../../../chart_expressions/expression_pie/common'; +} from '../../../../chart_expressions/expression_partition_vis/common'; export const getLabelPositions = [ { diff --git a/src/plugins/vis_types/pie/public/editor/components/index.tsx b/src/plugins/vis_types/pie/public/editor/components/index.tsx index c61e2724a466c..591eaba64ecc5 100644 --- a/src/plugins/vis_types/pie/public/editor/components/index.tsx +++ b/src/plugins/vis_types/pie/public/editor/components/index.tsx @@ -9,13 +9,13 @@ import React, { lazy } from 'react'; import { VisEditorOptionsProps } from '../../../../../visualizations/public'; import { PieTypeProps } from '../../types'; -import { PieVisParams } from '../../../../../chart_expressions/expression_pie/common'; +import { PartitionVisParams } from '../../../../../chart_expressions/expression_partition_vis/common'; const PieOptionsLazy = lazy(() => import('./pie')); export const getPieOptions = ({ showElasticChartsOptions, palettes, trackUiMetric }: PieTypeProps) => - (props: VisEditorOptionsProps) => + (props: VisEditorOptionsProps) => ( , PieTypeProps {} +export interface PieOptionsProps extends VisEditorOptionsProps, PieTypeProps {} const emptySizeRatioLabel = i18n.translate('visTypePie.editors.pie.emptySizeRatioLabel', { defaultMessage: 'Inner area size', @@ -82,19 +83,24 @@ function DecimalSlider({ const PieOptions = (props: PieOptionsProps) => { const { stateParams, setValue, aggs } = props; - const setLabels = ( + const setLabels = ( paramName: T, - value: PieVisParams['labels'][T] + value: PartitionVisParams['labels'][T] ) => setValue('labels', { ...stateParams.labels, [paramName]: value }); const legendUiStateValue = props.uiState?.get('vis.legendOpen'); const [palettesRegistry, setPalettesRegistry] = useState(undefined); const [legendVisibility, setLegendVisibility] = useState(() => { - const bwcLegendStateDefault = stateParams.addLegend == null ? false : stateParams.addLegend; - return props.uiState?.get('vis.legendOpen', bwcLegendStateDefault) as boolean; + const bwcLegendStateDefault = stateParams.legendDisplay === LegendDisplay.SHOW; + return props.uiState?.get('vis.legendOpen', bwcLegendStateDefault); }); const hasSplitChart = Boolean(aggs?.aggs?.find((agg) => agg.schema === 'split' && agg.enabled)); const segments = aggs?.aggs?.filter((agg) => agg.schema === 'segment' && agg.enabled) ?? []; + const getLegendDisplay = useCallback( + (isVisible: boolean) => (isVisible ? LegendDisplay.SHOW : LegendDisplay.HIDE), + [] + ); + useEffect(() => { setLegendVisibility(legendUiStateValue); }, [legendUiStateValue]); @@ -115,6 +121,21 @@ const PieOptions = (props: PieOptionsProps) => { [setValue] ); + const handleLegendDisplayChange = useCallback( + (name: keyof PartitionVisParams, show: boolean) => { + setLegendVisibility(show); + + const legendDisplay = getLegendDisplay(show); + if (legendDisplay === stateParams[name]) { + setValue(name, getLegendDisplay(!show)); + } + setValue(name, legendDisplay); + + props.uiState?.set('vis.legendOpen', show); + }, + [getLegendDisplay, props.uiState, setValue, stateParams] + ); + return ( <> @@ -180,15 +201,12 @@ const PieOptions = (props: PieOptionsProps) => { { - setLegendVisibility(value); - setValue(paramName, value); - }} + setValue={handleLegendDisplayChange} data-test-subj="visTypePieAddLegendSwitch" /> { })} paramName="nestedLegend" value={stateParams.nestedLegend} - disabled={!stateParams.addLegend} + disabled={stateParams.legendDisplay === LegendDisplay.HIDE} setValue={(paramName, value) => { if (props.trackUiMetric) { props.trackUiMetric(METRIC_TYPE.CLICK, 'nested_legend_switched'); diff --git a/src/plugins/vis_types/pie/public/editor/components/truncate_labels.test.tsx b/src/plugins/vis_types/pie/public/editor/components/truncate_labels.test.tsx index 1d4bb238dcb50..fc5731dc6839f 100644 --- a/src/plugins/vis_types/pie/public/editor/components/truncate_labels.test.tsx +++ b/src/plugins/vis_types/pie/public/editor/components/truncate_labels.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { TruncateLabelsOption, TruncateLabelsOptionProps } from './truncate_labels'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts index 56f0620787886..53140c822bd9b 100644 --- a/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/pie/public/sample_vis.test.mocks.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { LegendDisplay } from '../../../chart_expressions/expression_partition_vis/common'; + export const samplePieVis = { type: { name: 'pie', @@ -24,7 +26,7 @@ export const samplePieVis = { defaults: { type: 'pie', addTooltip: true, - addLegend: true, + legendDisplay: LegendDisplay.SHOW, legendPosition: 'right', isDonut: true, nestedLegend: true, @@ -138,7 +140,7 @@ export const samplePieVis = { params: { type: 'pie', addTooltip: true, - addLegend: true, + legendDisplay: LegendDisplay.SHOW, legendPosition: 'right', isDonut: true, labels: { diff --git a/src/plugins/vis_types/pie/public/to_ast.test.ts b/src/plugins/vis_types/pie/public/to_ast.test.ts index 87e279e787b32..62421d30c2ac0 100644 --- a/src/plugins/vis_types/pie/public/to_ast.test.ts +++ b/src/plugins/vis_types/pie/public/to_ast.test.ts @@ -8,12 +8,12 @@ import { Vis } from '../../../visualizations/public'; -import { PieVisParams } from '../../../chart_expressions/expression_pie/common'; +import { PartitionVisParams } from '../../../chart_expressions/expression_partition_vis/common'; import { samplePieVis } from './sample_vis.test.mocks'; import { toExpressionAst } from './to_ast'; describe('vis type pie vis toExpressionAst function', () => { - let vis: Vis; + let vis: Vis; const params = { timefilter: {}, timeRange: {}, diff --git a/src/plugins/vis_types/pie/public/to_ast.ts b/src/plugins/vis_types/pie/public/to_ast.ts index 09e00918d47d5..3879980bbf85c 100644 --- a/src/plugins/vis_types/pie/public/to_ast.ts +++ b/src/plugins/vis_types/pie/public/to_ast.ts @@ -11,11 +11,11 @@ import { buildExpression, buildExpressionFunction } from '../../../expressions/p import { PaletteOutput } from '../../../charts/common'; import { PIE_VIS_EXPRESSION_NAME, - PIE_LABELS_FUNCTION, + PARTITION_LABELS_FUNCTION, PieVisExpressionFunctionDefinition, - PieVisParams, + PartitionVisParams, LabelsParams, -} from '../../../chart_expressions/expression_pie/common'; +} from '../../../chart_expressions/expression_partition_vis/common'; import { getEsaggsFn } from './to_ast_esaggs'; const prepareDimension = (params: SchemaConfig) => { @@ -37,9 +37,9 @@ const preparePalette = (palette?: PaletteOutput) => { }; const prepareLabels = (params: LabelsParams) => { - const pieLabels = buildExpressionFunction(PIE_LABELS_FUNCTION, { + const pieLabels = buildExpressionFunction(PARTITION_LABELS_FUNCTION, { show: params.show, - lastLevel: params.last_level, + last_level: params.last_level, values: params.values, truncate: params.truncate, }); @@ -55,18 +55,18 @@ const prepareLabels = (params: LabelsParams) => { return buildExpression([pieLabels]); }; -export const toExpressionAst: VisToExpressionAst = async (vis, params) => { +export const toExpressionAst: VisToExpressionAst = async (vis, params) => { const schemas = getVisSchemas(vis, params); const args = { // explicitly pass each param to prevent extra values trapping addTooltip: vis.params.addTooltip, - addLegend: vis.params.addLegend, + legendDisplay: vis.params.legendDisplay, legendPosition: vis.params.legendPosition, - nestedLegend: vis.params?.nestedLegend, + nestedLegend: vis.params?.nestedLegend ?? false, truncateLegend: vis.params.truncateLegend, maxLegendLines: vis.params.maxLegendLines, distinctColors: vis.params?.distinctColors, - isDonut: vis.params.isDonut, + isDonut: vis.params.isDonut ?? false, emptySizeRatio: vis.params.emptySizeRatio, palette: preparePalette(vis.params?.palette), labels: prepareLabels(vis.params.labels), @@ -74,6 +74,7 @@ export const toExpressionAst: VisToExpressionAst = async (vis, par buckets: schemas.segment?.map(prepareDimension), splitColumn: schemas.split_column?.map(prepareDimension), splitRow: schemas.split_row?.map(prepareDimension), + startFromSecondLargestSlice: false, }; const visTypePie = buildExpressionFunction( diff --git a/src/plugins/vis_types/pie/public/to_ast_esaggs.ts b/src/plugins/vis_types/pie/public/to_ast_esaggs.ts index 41eddedd6fa2c..3f41a59e3aa8e 100644 --- a/src/plugins/vis_types/pie/public/to_ast_esaggs.ts +++ b/src/plugins/vis_types/pie/public/to_ast_esaggs.ts @@ -12,13 +12,13 @@ import { EsaggsExpressionFunctionDefinition, IndexPatternLoadExpressionFunctionDefinition, } from '../../../data/public'; -import { PieVisParams } from '../../../chart_expressions/expression_pie/common'; +import { PartitionVisParams } from '../../../chart_expressions/expression_partition_vis/common'; /** * Get esaggs expressions function * @param vis */ -export function getEsaggsFn(vis: Vis) { +export function getEsaggsFn(vis: Vis) { return buildExpressionFunction('esaggs', { index: buildExpression([ buildExpressionFunction('indexPatternLoad', { diff --git a/src/plugins/vis_types/pie/public/vis_type/pie.ts b/src/plugins/vis_types/pie/public/vis_type/pie.ts index 827b25d541c9e..c9cc573e27781 100644 --- a/src/plugins/vis_types/pie/public/vis_type/pie.ts +++ b/src/plugins/vis_types/pie/public/vis_type/pie.ts @@ -13,11 +13,12 @@ import { VIS_EVENT_TO_TRIGGER, VisTypeDefinition } from '../../../../visualizati import { DEFAULT_PERCENT_DECIMALS } from '../../common'; import { PieTypeProps } from '../types'; import { - PieVisParams, + PartitionVisParams, LabelPositions, ValueFormats, EmptySizeRatios, -} from '../../../../chart_expressions/expression_pie/common'; + LegendDisplay, +} from '../../../../chart_expressions/expression_partition_vis/common'; import { toExpressionAst } from '../to_ast'; import { getPieOptions } from '../editor/components'; @@ -25,7 +26,7 @@ export const getPieVisTypeDefinition = ({ showElasticChartsOptions = false, palettes, trackUiMetric, -}: PieTypeProps): VisTypeDefinition => ({ +}: PieTypeProps): VisTypeDefinition => ({ name: 'pie', title: i18n.translate('visTypePie.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', @@ -38,7 +39,7 @@ export const getPieVisTypeDefinition = ({ defaults: { type: 'pie', addTooltip: true, - addLegend: !showElasticChartsOptions, + legendDisplay: !showElasticChartsOptions ? LegendDisplay.SHOW : LegendDisplay.HIDE, legendPosition: Position.Right, nestedLegend: false, truncateLegend: true, diff --git a/src/plugins/vis_types/pie/tsconfig.json b/src/plugins/vis_types/pie/tsconfig.json index 9ad4e8efe907b..ed052af072f2a 100644 --- a/src/plugins/vis_types/pie/tsconfig.json +++ b/src/plugins/vis_types/pie/tsconfig.json @@ -21,6 +21,6 @@ { "path": "../../usage_collection/tsconfig.json" }, { "path": "../../vis_default_editor/tsconfig.json" }, { "path": "../../field_formats/tsconfig.json" }, - { "path": "../../chart_expressions/expression_pie/tsconfig.json" } + { "path": "../../chart_expressions/expression_partition_vis/tsconfig.json" } ] } \ No newline at end of file diff --git a/src/plugins/vis_types/table/public/table_vis_fn.ts b/src/plugins/vis_types/table/public/table_vis_fn.ts index 861923ef5086e..99c8992ff5984 100644 --- a/src/plugins/vis_types/table/public/table_vis_fn.ts +++ b/src/plugins/vis_types/table/public/table_vis_fn.ts @@ -126,6 +126,11 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({ }, fn(input, args, handlers) { const convertedData = tableVisResponseHandler(input, args); + const inspectorData = { + rows: convertedData?.table?.rows ?? input.rows, + columns: convertedData?.table?.columns ?? input.columns, + type: 'datatable', + } as Datatable; if (handlers?.inspectorAdapters?.tables) { const argsTable: Dimension[] = [ @@ -158,7 +163,7 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({ }), ]); } - const logTable = prepareLogTable(input, argsTable); + const logTable = prepareLogTable(inspectorData, argsTable); handlers.inspectorAdapters.tables.logDatatable('default', logTable); } return { diff --git a/src/plugins/vis_types/timelion/server/ui_settings.ts b/src/plugins/vis_types/timelion/server/ui_settings.ts index 40907b0271487..2dff4f013c25c 100644 --- a/src/plugins/vis_types/timelion/server/ui_settings.ts +++ b/src/plugins/vis_types/timelion/server/ui_settings.ts @@ -14,7 +14,7 @@ import { UI_SETTINGS } from '../common/constants'; import { configSchema } from '../config'; const experimentalLabel = i18n.translate('timelion.uiSettings.experimentalLabel', { - defaultMessage: 'experimental', + defaultMessage: 'technical preview', }); export function getUiSettings( diff --git a/src/plugins/vis_types/timeseries/common/constants.ts b/src/plugins/vis_types/timeseries/common/constants.ts index 30fb814990925..cbaf275cc0092 100644 --- a/src/plugins/vis_types/timeseries/common/constants.ts +++ b/src/plugins/vis_types/timeseries/common/constants.ts @@ -11,6 +11,7 @@ export const UI_SETTINGS = { ALLOW_STRING_INDICES: 'metrics:allowStringIndices', ALLOW_CHECKING_FOR_FAILED_SHARDS: 'metrics:allowCheckingForFailedShards', }; +export const SERIES_SEPARATOR = '╰┄►'; export const INDEXES_SEPARATOR = ','; export const AUTO_INTERVAL = 'auto'; export const ROUTES = { diff --git a/src/plugins/vis_types/timeseries/public/application/_variables.scss b/src/plugins/vis_types/timeseries/public/application/_variables.scss index 0107c5569d730..b3334c93a9bbe 100644 --- a/src/plugins/vis_types/timeseries/public/application/_variables.scss +++ b/src/plugins/vis_types/timeseries/public/application/_variables.scss @@ -1,7 +1,7 @@ $tvbLineColor: transparentize($euiColorFullShade, .8); $tvbLineColorReversed: transparentize($euiColorEmptyShade, .6); -$tvbTextColor: transparentize($euiColorFullShade, .6); +$tvbTextColor: transparentize($euiColorFullShade, .4); $tvbTextColorReversed: transparentize($euiColorEmptyShade, .4); $tvbValueColor: transparentize($euiColorFullShade, .3); diff --git a/src/plugins/vis_types/timeseries/public/application/components/add_delete_buttons.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/add_delete_buttons.test.tsx index 43e59222e0e1e..d6bd687d60055 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/add_delete_buttons.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/add_delete_buttons.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { AddDeleteButtons } from './add_delete_buttons'; describe('AddDeleteButtons', () => { diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/agg_select.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/agg_select.test.tsx index 3dbeb257900b7..ccdf60ea9cf98 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/agg_select.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/agg_select.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { AggSelect } from './agg_select'; import { METRIC, SERIES } from '../../../test_utils'; import { EuiComboBox } from '@elastic/eui'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/filter_ratio.test.js b/src/plugins/vis_types/timeseries/public/application/components/aggs/filter_ratio.test.js index bd9ceeeb74028..38305395bfbb6 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/filter_ratio.test.js +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/filter_ratio.test.js @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { FilterRatioAgg } from './filter_ratio'; import { FIELDS, METRIC, SERIES, PANEL } from '../../../test_utils'; import { EuiComboBox } from '@elastic/eui'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js b/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js index ff96e476814ff..b97968ff62241 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/histogram_support.test.js @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { Agg } from './agg'; import { FieldSelect } from './field_select'; import { FIELDS, METRIC, SERIES, PANEL } from '../../../test_utils'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_rank/multi_value_row.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_rank/multi_value_row.test.tsx index 7b08715ba1a93..d0a140a15b488 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_rank/multi_value_row.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_rank/multi_value_row.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { MultiValueRow } from './multi_value_row'; import { ColorPicker } from '../../color_picker'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_ui.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_ui.test.tsx index b143c0bf6ceab..edb0ae4d79884 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_ui.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/aggs/percentile_ui.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; // @ts-ignore not-typed yet import { Percentiles } from './percentile_ui'; import { ColorPicker } from '../color_picker'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/color_rules.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/color_rules.test.tsx index d6009a214941f..6dafe5cd456fb 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/color_rules.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/color_rules.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { keys } from '@elastic/eui'; import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { collectionActions } from './lib/collection_actions'; import { diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts index 3df52223c253a..15151a9e21bc5 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.test.ts @@ -210,5 +210,26 @@ describe('convert series to datatables', () => { }); expect(tables.series1.rows).toEqual([...expected1, ...expected2]); }); + + test('for series aggregation split by terms, no column is added', async () => { + const updatedModel = { + ...model, + series: [ + { + ...model.series[0], + metrics: [ + { + field: 'test2', + id: 'series1', + function: 'sum', + type: 'series_agg', + }, + ], + }, + ], + } as TimeseriesVisParams; + const tables = await convertSeriesToDataTable(updatedModel, series, indexPattern); + expect(tables.series1.columns.length).toEqual(2); + }); }); }); diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.ts index 19a1910afbe2f..8e7c1694357c8 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/convert_series_to_datatable.ts @@ -9,8 +9,8 @@ import { IndexPattern } from 'src/plugins/data/public'; import { DatatableRow, DatatableColumn, DatatableColumnType } from 'src/plugins/expressions/public'; import { Query } from 'src/plugins/data/common'; import { TimeseriesVisParams } from '../../../types'; -import type { PanelData } from '../../../../common/types'; -import { BUCKET_TYPES } from '../../../../common/enums'; +import type { PanelData, Metric } from '../../../../common/types'; +import { BUCKET_TYPES, TSVB_METRIC_TYPES } from '../../../../common/enums'; import { fetchIndexPattern } from '../../../../common/index_patterns_utils'; import { getDataStart } from '../../../services'; import { X_ACCESSOR_INDEX } from '../../visualizations/constants'; @@ -78,6 +78,10 @@ export const addMetaToColumns = ( }); }; +const hasSeriesAgg = (metrics: Metric[]) => { + return metrics.some((metric) => metric.type === TSVB_METRIC_TYPES.SERIES_AGG); +}; + export const convertSeriesToDataTable = async ( model: TimeseriesVisParams, series: PanelData[], @@ -96,7 +100,9 @@ export const convertSeriesToDataTable = async ( usedIndexPattern = indexPattern; } } - const isGroupedByTerms = layer.split_mode === BUCKET_TYPES.TERMS; + // series aggregation is a special case, splitting by terms doesn't create multiple series per term + const isGroupedByTerms = + layer.split_mode === BUCKET_TYPES.TERMS && !hasSeriesAgg(layer.metrics); const isGroupedByFilters = layer.split_mode === BUCKET_TYPES.FILTERS; const seriesPerLayer = series.filter((s) => s.seriesId === layer.id); let id = X_ACCESSOR_INDEX; diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.test.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.test.ts index 80c84ff125817..f79665fdca5ec 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.test.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.test.ts @@ -9,6 +9,7 @@ import { XYChartSeriesIdentifier, GeometryValue } from '@elastic/charts'; import { getClickFilterData } from './get_click_filter_data'; import type { TSVBTables } from './types'; import { TimeseriesVisParams } from '../../../types'; +import { SERIES_SEPARATOR } from '../../../../common/constants'; describe('getClickFilterData', () => { test('gets the correct data for a group by everything timeseries chart', () => { @@ -102,7 +103,7 @@ describe('getClickFilterData', () => { }, { key: 'groupId{yaxis_6e0353a0-ad9b-11eb-b112-89cce8e43380_main_group}spec{61ca57f1-469d-11e7-af02-69e470af7417:1}yAccessor{1}splitAccessors{}', - specId: '61ca57f1-469d-11e7-af02-69e470af7417:1', + specId: '61ca57f1-469d-11e7-af02-69e470af7417╰┄►1', }, ], ] as Array<[GeometryValue, XYChartSeriesIdentifier]>; @@ -199,7 +200,7 @@ describe('getClickFilterData', () => { expect(data[1].column).toEqual(2); expect(data[1].row).toEqual(10); // expect(data).toEqual([]); - const splitValue = points[0][1].specId.split(':'); + const splitValue = points[0][1].specId.split(SERIES_SEPARATOR); expect(data[1].value).toEqual(parseInt(splitValue[1], 10)); }); }); diff --git a/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.ts b/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.ts index a6e35aa6e1032..a3da470f6155c 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.ts +++ b/src/plugins/vis_types/timeseries/public/application/components/lib/get_click_filter_data.ts @@ -11,6 +11,7 @@ import { X_ACCESSOR_INDEX } from '../../visualizations/constants'; import { BUCKET_TYPES } from '../../../../common/enums'; import { TimeseriesVisParams } from '../../../types'; import type { TSVBTables } from './types'; +import { SERIES_SEPARATOR } from '../../../../common/constants'; export const getClickFilterData = ( points: Array<[GeometryValue, XYChartSeriesIdentifier]>, @@ -23,7 +24,7 @@ export const getClickFilterData = ( const { specId } = point[1]; // specId for a split series has the format // 61ca57f1-469d-11e7-af02-69e470af7417:Men's Accessories, : - const [layerId, splitLabel] = specId.split(':'); + const [layerId, splitLabel] = specId.split(SERIES_SEPARATOR); const table = tables[layerId]; const layer = model.series.filter(({ id }) => id === layerId); diff --git a/src/plugins/vis_types/timeseries/public/application/components/palette_picker.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/palette_picker.test.tsx index 81b33943f8b04..489896662033b 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/palette_picker.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/palette_picker.test.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { PalettePicker, PalettePickerProps } from './palette_picker'; import { chartPluginMock } from '../../../../../charts/public/mocks'; diff --git a/src/plugins/vis_types/timeseries/public/application/components/panel_config/timeseries.test.tsx b/src/plugins/vis_types/timeseries/public/application/components/panel_config/timeseries.test.tsx index b8af2e7ef8539..9a23d8513500c 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/panel_config/timeseries.test.tsx +++ b/src/plugins/vis_types/timeseries/public/application/components/panel_config/timeseries.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; jest.mock('../lib/get_default_query_language', () => ({ getDefaultQueryLanguage: () => 'kuery', diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/gauge/series.test.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/gauge/series.test.js index 32904ba682768..b2fb7bfff0b3e 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/gauge/series.test.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/gauge/series.test.js @@ -8,7 +8,7 @@ import React from 'react'; import { GaugeSeries } from './series'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; const defaultProps = { disableAdd: true, diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/metric/series.test.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/metric/series.test.js index c33fa2d3f442d..709ea8b23ca33 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/metric/series.test.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/metric/series.test.js @@ -8,7 +8,7 @@ import React from 'react'; import { MetricSeries } from './series'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; const defaultProps = { disableAdd: false, diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js b/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js index 5eb850a753384..20caa14ea8cb6 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_types/top_n/vis.js @@ -20,6 +20,7 @@ import { sortBy, first, get } from 'lodash'; import { DATA_FORMATTERS } from '../../../../../common/enums'; import { getOperator, shouldOperate } from '../../../../../common/operators_utils'; import { ExternalUrlErrorModal } from '../../lib/external_url_error_modal'; +import { SERIES_SEPARATOR } from '../../../../../common/constants'; function sortByDirection(data, direction, fn) { if (direction === 'desc') { @@ -32,7 +33,7 @@ function sortSeries(visData, model) { const series = get(visData, `${model.id}.series`, []); return model.series.reduce((acc, item) => { const itemSeries = series.filter((s) => { - const id = first(s.id.split(/:/)); + const id = first(s.id.split(SERIES_SEPARATOR)); return id === item.id; }); const direction = item.terms_direction || 'desc'; @@ -47,7 +48,7 @@ function TopNVisualization(props) { const { backgroundColor, model, visData, fieldFormatMap, getConfig } = props; const series = sortSeries(visData, model).map((item) => { - const id = first(item.id.split(/:/)); + const id = first(item.id.split(SERIES_SEPARATOR)); const seriesConfig = model.series.find((s) => s.id === id); if (seriesConfig) { const tickFormatter = diff --git a/src/plugins/vis_types/timeseries/public/application/components/vis_with_splits.js b/src/plugins/vis_types/timeseries/public/application/components/vis_with_splits.js index b8ce63bd35dca..8f700228cc71e 100644 --- a/src/plugins/vis_types/timeseries/public/application/components/vis_with_splits.js +++ b/src/plugins/vis_types/timeseries/public/application/components/vis_with_splits.js @@ -13,6 +13,7 @@ import { labelDateFormatter } from './lib/label_date_formatter'; import { findIndex, first } from 'lodash'; import { getValueOrEmpty } from '../../../common/empty_label'; import { getSplitByTermsColor } from '../lib/get_split_by_terms_color'; +import { SERIES_SEPARATOR } from '../../../common/constants'; export function visWithSplits(WrappedComponent) { function SplitVisComponent(props) { @@ -43,12 +44,12 @@ export function visWithSplits(WrappedComponent) { ); if (!model || !visData || !visData[model.id]) return ; - if (visData[model.id].series.every((s) => s.id.split(':').length === 1)) { + if (visData[model.id].series.every((s) => s.id.split(SERIES_SEPARATOR).length === 1)) { return ; } const splitsVisData = visData[model.id].series.reduce((acc, series) => { - const [seriesId, splitId] = series.id.split(':'); + const [seriesId, splitId] = series.id.split(SERIES_SEPARATOR); const seriesModel = model.series.find((s) => s.id === seriesId); if (!seriesModel) return acc; diff --git a/src/plugins/vis_types/timeseries/public/application/visualizations/views/top_n.js b/src/plugins/vis_types/timeseries/public/application/visualizations/views/top_n.js index e9ffcd8bc5a86..6b2b25eed2a03 100644 --- a/src/plugins/vis_types/timeseries/public/application/visualizations/views/top_n.js +++ b/src/plugins/vis_types/timeseries/public/application/visualizations/views/top_n.js @@ -74,10 +74,10 @@ export class TopN extends Component { return {}; }; - static calcInnerBarDivStyles = (item, width, isPositive) => { + static calcInnerBarDivStyles = (item, widthWithUnit, isPositive) => { return { backgroundColor: item.color, - width: width + '%', + width: widthWithUnit, float: isPositive ? 'left' : 'right', }; }; @@ -97,8 +97,7 @@ export class TopN extends Component { const renderMode = TopN.getRenderMode(min, max); const key = `${item.id || item.label}`; const lastValue = getLastValue(item.data); - // if result is empty, all bar need to be colored. - const lastValueFormatted = isEmptyValue(lastValue) ? 1 : lastValue; + const lastValueFormatted = isEmptyValue(lastValue) ? 0 : lastValue; const formatter = item.tickFormatter || this.props.tickFormatter; const isPositiveValue = lastValueFormatted >= 0; @@ -106,6 +105,7 @@ export class TopN extends Component { // if both are 0, the division returns NaN causing unexpected behavior. // For this it defaults to 0 const width = 100 * (Math.abs(lastValueFormatted) / intervalLength) || 0; + const widthWithUnit = isEmptyValue(lastValue) ? '1px' : `${width}%`; const label = item.labelFormatted ? labelDateFormatter(item.labelFormatted) : item.label; const styles = reactcss( { @@ -114,7 +114,7 @@ export class TopN extends Component { ...TopN.calcInnerBarStyles(renderMode, isPositiveValue), }, innerBarValue: { - ...TopN.calcInnerBarDivStyles(item, width, isPositiveValue), + ...TopN.calcInnerBarDivStyles(item, widthWithUnit, isPositiveValue), }, label: { maxWidth: this.state.labelMaxWidth, @@ -154,7 +154,7 @@ export class TopN extends Component { const intervalSettings = this.props.series.reduce( (acc, series, index) => { - const value = getLastValue(series.data) ?? 1; + const value = getLastValue(series.data) ?? 0; return { min: !index || value < acc.min ? value : acc.min, diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.test.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.test.ts index 5dbeb915df28c..d6bdd452825fd 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.test.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.test.ts @@ -79,7 +79,7 @@ describe('getSplits(resp, panel, series)', () => { expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([ { - id: 'SERIES:example-01', + id: 'SERIES╰┄►example-01', key: 'example-01', label: 'example-01', labelFormatted: '', @@ -90,7 +90,7 @@ describe('getSplits(resp, panel, series)', () => { SIBAGG: { value: 1 }, }, { - id: 'SERIES:example-02', + id: 'SERIES╰┄►example-02', key: 'example-02', label: 'example-02', labelFormatted: '', @@ -138,7 +138,7 @@ describe('getSplits(resp, panel, series)', () => { const panel = { type: 'top_n' } as Panel; expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([ { - id: 'SERIES:example-01', + id: 'SERIES╰┄►example-01', key: 'example-01', label: '--example-01--', labelFormatted: '', @@ -149,7 +149,7 @@ describe('getSplits(resp, panel, series)', () => { SIBAGG: { value: 1 }, }, { - id: 'SERIES:example-02', + id: 'SERIES╰┄►example-02', key: 'example-02', label: '--example-02--', labelFormatted: '', @@ -200,7 +200,7 @@ describe('getSplits(resp, panel, series)', () => { expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([ { - id: 'SERIES:example-01', + id: 'SERIES╰┄►example-01', key: 'example-01', key_as_string: 'false', label: '--example-01--', @@ -212,7 +212,7 @@ describe('getSplits(resp, panel, series)', () => { SIBAGG: { value: 1 }, }, { - id: 'SERIES:example-02', + id: 'SERIES╰┄►example-02', key: 'example-02', key_as_string: 'true', label: '--example-02--', @@ -256,7 +256,7 @@ describe('getSplits(resp, panel, series)', () => { expect(await getSplits(resp, panel, series, undefined, () => [])).toEqual([ { - id: 'SERIES:filter-1', + id: 'SERIES╰┄►filter-1', key: 'filter-1', label: '200s', meta: { bucketSize: 10 }, @@ -265,7 +265,7 @@ describe('getSplits(resp, panel, series)', () => { timeseries: { buckets: [] }, }, { - id: 'SERIES:filter-2', + id: 'SERIES╰┄►filter-2', key: 'filter-2', label: '300s', splitByLabel: 'Count', diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts index 6be53ef3f03ff..1754fa6569dcd 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/helpers/get_splits.ts @@ -11,6 +11,7 @@ import { get, isPlainObject } from 'lodash'; import { overwrite } from '../helpers'; import { calculateLabel } from '../../../../common/calculate_label'; +import { SERIES_SEPARATOR } from '../../../../common/constants'; import { getLastMetric } from './get_last_metric'; import { formatKey } from './format_key'; @@ -66,7 +67,7 @@ export async function getSplits { const bucket = get(resp, `aggregations.${series.id}.buckets.${filter.id}`); - bucket.id = `${series.id}:${filter.id}`; + bucket.id = `${series.id}${SERIES_SEPARATOR}${filter.id}`; bucket.key = filter.id; bucket.splitByLabel = splitByLabel; bucket.color = filter.color; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.js index a6addc8ba0e53..46dde6653647d 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.js @@ -7,6 +7,7 @@ */ import { convertIntervalToUnit } from '../../helpers/unit_to_seconds'; +import { SERIES_SEPARATOR } from '../../../../../common/constants'; const percentileValueMatch = /\[([0-9\.]+)\]$/; import { startsWith, flatten, values, first, last } from 'lodash'; @@ -20,7 +21,7 @@ export function mathAgg(resp, panel, series, meta, extractFields) { // Filter the results down to only the ones that match the series.id. Sometimes // there will be data from other series mixed in. results = results.filter((s) => { - if (s.id.split(/:/)[0] === series.id) { + if (s.id.split(SERIES_SEPARATOR)[0] === series.id) { return false; } return true; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js index d8508f6b735a0..14c66866d323a 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js @@ -93,7 +93,7 @@ describe('math(resp, panel, series)', () => { expect(results).toHaveLength(1); expect(results[0]).toEqual({ - id: 'test:example-01', + id: 'test╰┄►example-01', label: 'example-01', color: 'rgb(255, 0, 0)', stack: false, diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.js index fe8a6ff9cd2f6..a0fe57ea5cfcf 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.js @@ -11,6 +11,7 @@ import { getDefaultDecoration } from '../../helpers/get_default_decoration'; import { getSplits } from '../../helpers/get_splits'; import { getLastMetric } from '../../helpers/get_last_metric'; import { TSVB_METRIC_TYPES } from '../../../../../common/enums'; +import { SERIES_SEPARATOR } from '../../../../../common/constants'; export function percentile(resp, panel, series, meta, extractFields) { return (next) => async (results) => { @@ -23,7 +24,7 @@ export function percentile(resp, panel, series, meta, extractFields) { (await getSplits(resp, panel, series, meta, extractFields)).forEach((split) => { metric.percentiles.forEach((percentile) => { const percentileValue = percentile.value ? percentile.value : 0; - const id = `${split.id}:${percentile.id}`; + const id = `${split.id}${SERIES_SEPARATOR}${percentile.id}`; const data = split.timeseries.buckets.map((bucket) => { const higherMetric = { ...metric, percent: percentileValue }; const serieData = [bucket.key, getAggValue(bucket, higherMetric)]; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.test.js index de304913d6c69..ea1efdcd0c41b 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile.test.js @@ -83,7 +83,7 @@ describe('percentile(resp, panel, series)', () => { expect(results).toHaveLength(2); - expect(results[0]).toHaveProperty('id', 'test:10-90'); + expect(results[0]).toHaveProperty('id', 'test╰┄►10-90'); expect(results[0]).toHaveProperty('color', '#000028'); expect(results[0]).toHaveProperty('label', 'Percentile of cpu'); expect(results[0]).toHaveProperty('lines'); @@ -100,7 +100,7 @@ describe('percentile(resp, panel, series)', () => { [2, 1.2, 5.3], ]); - expect(results[1]).toHaveProperty('id', 'test:50'); + expect(results[1]).toHaveProperty('id', 'test╰┄►50'); expect(results[1]).toHaveProperty('color', 'rgb(255, 0, 0)'); expect(results[1]).toHaveProperty('label', '(50) Percentile of cpu'); expect(results[1]).toHaveProperty('stack', false); diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.js index ce81ec46693e2..0720c6e19a9c0 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.js @@ -12,6 +12,7 @@ import { getSplits } from '../../helpers/get_splits'; import { getLastMetric } from '../../helpers/get_last_metric'; import { toPercentileNumber } from '../../../../../common/to_percentile_number'; import { TSVB_METRIC_TYPES } from '../../../../../common/enums'; +import { SERIES_SEPARATOR } from '../../../../../common/constants'; export function percentileRank(resp, panel, series, meta, extractFields) { return (next) => async (results) => { @@ -33,7 +34,7 @@ export function percentileRank(resp, panel, series, meta, extractFields) { results.push({ data, - id: `${split.id}:${percentileRank}:${index}`, + id: `${split.id}${SERIES_SEPARATOR}${percentileRank}${SERIES_SEPARATOR}${index}`, label: `(${percentileRank || 0}) ${split.label}`, color: series.split_mode === 'everything' && metric.colors diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.test.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.test.ts index 79c3eab91c3c9..758c37130cdd4 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.test.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/percentile_rank.test.ts @@ -75,7 +75,7 @@ describe('percentile_rank(resp, panel, series, meta, extractFields)', () => { expect(results).toHaveLength(2); - expect(results[0]).toHaveProperty('id', 'test:1000:0'); + expect(results[0]).toHaveProperty('id', 'test╰┄►1000╰┄►0'); expect(results[0]).toHaveProperty('color', '#000028'); expect(results[0]).toHaveProperty('label', '(1000) Percentile Rank of cpu'); expect(results[0].data).toEqual([ @@ -83,7 +83,7 @@ describe('percentile_rank(resp, panel, series, meta, extractFields)', () => { [2, 1], ]); - expect(results[1]).toHaveProperty('id', 'test:500:1'); + expect(results[1]).toHaveProperty('id', 'test╰┄►500╰┄►1'); expect(results[1]).toHaveProperty('color', '#0000FF'); expect(results[1]).toHaveProperty('label', '(500) Percentile Rank of cpu'); expect(results[1].data).toEqual([ diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/series_agg.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/series_agg.js index a803439c7581f..532f5fd07f597 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/series_agg.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/series_agg.js @@ -9,6 +9,7 @@ import { last, first } from 'lodash'; import { SeriesAgg } from './_series_agg'; import { getDefaultDecoration } from '../../helpers/get_default_decoration'; import { calculateLabel } from '../../../../../common/calculate_label'; +import { SERIES_SEPARATOR } from '../../../../../common/constants'; export function seriesAgg(resp, panel, series, meta, extractFields) { return (next) => async (results) => { @@ -19,7 +20,7 @@ export function seriesAgg(resp, panel, series, meta, extractFields) { // Filter out the seires with the matching metric and store them // in targetSeries results = results.filter((s) => { - if (s.id.split(/:/)[0] === series.id) { + if (s.id.split(SERIES_SEPARATOR)[0] === series.id) { targetSeries.push(s.data); return false; } diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/table/series_agg.ts b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/table/series_agg.ts index d360bb97333c7..b4bc082bab849 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/table/series_agg.ts +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/table/series_agg.ts @@ -9,6 +9,7 @@ import { last } from 'lodash'; import { calculateLabel } from '../../../../../common/calculate_label'; +import { SERIES_SEPARATOR } from '../../../../../common/constants'; // @ts-expect-error no typed yet import { SeriesAgg } from './_series_agg'; @@ -26,7 +27,7 @@ export const seriesAgg: TableResponseProcessorsFunction = // Filter out the seires with the matching metric and store them // in targetSeries results = results.filter((s) => { - if (s.id && s.id.split(/:/)[0] === series.id) { + if (s.id && s.id.split(SERIES_SEPARATOR)[0] === series.id) { targetSeries.push(s.data!); return false; } diff --git a/src/plugins/vis_types/vega/public/components/experimental_map_vis_info.tsx b/src/plugins/vis_types/vega/public/components/experimental_map_vis_info.tsx index 6d6ed0a6c1cc8..e98e0d65bd796 100644 --- a/src/plugins/vis_types/vega/public/components/experimental_map_vis_info.tsx +++ b/src/plugins/vis_types/vega/public/components/experimental_map_vis_info.tsx @@ -20,7 +20,9 @@ export const ExperimentalMapLayerInfo = () => ( title={ & { type: 'pie'; isDonut: boolean; labels: { @@ -20,7 +26,8 @@ export interface PieVisParams extends CommonVislibParams { last_level: boolean; truncate: number | null; }; -} + legendDisplay: LegendDisplay; +}; export const pieVisTypeDefinition = { ...pieVisType({}), diff --git a/src/plugins/vis_types/vislib/public/pie_fn.test.ts b/src/plugins/vis_types/vislib/public/pie_fn.test.ts index 9c317f9e72dc1..42061397d0ac7 100644 --- a/src/plugins/vis_types/vislib/public/pie_fn.test.ts +++ b/src/plugins/vis_types/vislib/public/pie_fn.test.ts @@ -39,7 +39,7 @@ describe('interpreter/functions#pie', () => { const visConfig = { type: 'pie', addTooltip: true, - addLegend: true, + legendDisplay: 'show', legendPosition: 'right', isDonut: true, labels: { diff --git a/src/plugins/vis_types/vislib/public/vis_controller.tsx b/src/plugins/vis_types/vislib/public/vis_controller.tsx index 1e940d23e83da..94a7e819f16f0 100644 --- a/src/plugins/vis_types/vislib/public/vis_controller.tsx +++ b/src/plugins/vis_types/vislib/public/vis_controller.tsx @@ -17,7 +17,7 @@ import { IInterpreterRenderHandlers } from '../../../expressions/public'; import { VisTypeVislibCoreSetup } from './plugin'; import { VisLegend, CUSTOM_LEGEND_VIS_TYPES } from './vislib/components/legend'; import { BasicVislibParams } from './types'; -import { PieVisParams } from './pie'; +import { LegendDisplay, PieVisParams } from './pie'; const legendClassName = { top: 'vislib--legend-top', @@ -94,7 +94,7 @@ export const createVislibVisController = ( this.vislibVis.initVisConfig(esResponse, uiState); - if (visParams.addLegend) { + if (this.showLegend(visParams)) { $(this.container) .attr('class', (i, cls) => { return cls.replace(/vislib--legend-\S+/g, ''); @@ -110,7 +110,7 @@ export const createVislibVisController = ( // this is necessary because some visualizations // provide data necessary for the legend only after a render cycle. if ( - visParams.addLegend && + this.showLegend(visParams) && CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) ) { this.unmountLegend?.(); @@ -121,10 +121,11 @@ export const createVislibVisController = ( mountLegend( visData: unknown, - { legendPosition, addLegend }: BasicVislibParams | PieVisParams, + visParams: BasicVislibParams | PieVisParams, fireEvent: IInterpreterRenderHandlers['event'], uiState?: PersistedState ) { + const { legendPosition } = visParams; this.unmountLegend = mountReactNode( )(this.legendEl); @@ -151,5 +152,16 @@ export const createVislibVisController = ( delete this.vislibVis; } } + + showLegend(visParams: BasicVislibParams | PieVisParams) { + if (this.arePieVisParams(visParams)) { + return visParams.legendDisplay === LegendDisplay.SHOW; + } + return visParams.addLegend ?? false; + } + + arePieVisParams(visParams: BasicVislibParams | PieVisParams): visParams is PieVisParams { + return Object.values(LegendDisplay).includes((visParams as PieVisParams).legendDisplay); + } }; }; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js index 54b326a292845..5c3fd271fc6ca 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/chart_title.test.js @@ -12,7 +12,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; import { ChartTitle } from './chart_title'; import { VisConfig } from './vis_config'; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js index 21a3dc069d8c6..a500aa00e6d21 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/dispatch.test.js @@ -13,7 +13,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; // Data import data from '../../fixtures/mock_data/date_histogram/_series'; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js index 60ffaf3f3d19c..d82dbb1ae77d7 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/handler.test.js @@ -11,7 +11,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; // Data import series from '../../fixtures/mock_data/date_histogram/_series'; diff --git a/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js b/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js index af59f011515d0..50fcdba6aa360 100644 --- a/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/lib/layout/layout.test.js @@ -12,7 +12,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; // Data import series from '../../../fixtures/mock_data/date_histogram/_series'; diff --git a/src/plugins/vis_types/vislib/public/vislib/vis.test.js b/src/plugins/vis_types/vislib/public/vislib/vis.test.js index 46afbd1c62f69..b20bbfc700c8b 100644 --- a/src/plugins/vis_types/vislib/public/vislib/vis.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/vis.test.js @@ -12,7 +12,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; import series from '../fixtures/mock_data/date_histogram/_series'; import columns from '../fixtures/mock_data/date_histogram/_columns'; import rows from '../fixtures/mock_data/date_histogram/_rows'; diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js index 21ec67707126c..12fc0e9a5e4b8 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/chart.test.js @@ -12,7 +12,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; import { Chart } from './_chart'; import { getMockUiState } from '../../fixtures/mocks'; import { getVis } from './_vis_fixture'; diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/gauge_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/gauge_chart.test.js index 76830c66f3aa5..47cd2833464e8 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/gauge_chart.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/gauge_chart.test.js @@ -8,7 +8,7 @@ import $ from 'jquery'; import _ from 'lodash'; -import { setHTMLElementClientSizes, setSVGElementGetBBox } from '@kbn/test/jest'; +import { setHTMLElementClientSizes, setSVGElementGetBBox } from '@kbn/test-jest-helpers'; import data from '../../fixtures/mock_data/terms/_series_multiple'; import { getMockUiState } from '../../fixtures/mocks'; diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js index 8ad9bf56cb020..6705875ce1405 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/pie_chart.test.js @@ -13,7 +13,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; import { getMockUiState } from '../../fixtures/mocks'; import { getVis } from './_vis_fixture'; import { pieChartMockData } from './pie_chart_mock_data'; @@ -32,7 +32,7 @@ describe('No global chart settings', function () { const vislibParams1 = { el: '
', type: 'pie', - addLegend: true, + legendDisplay: 'show', addTooltip: true, }; let chart1; @@ -144,7 +144,7 @@ describe('Vislib PieChart Class Test Suite', function () { const vislibParams = { type: 'pie', - addLegend: true, + legendDisplay: 'show', addTooltip: true, }; let vis; diff --git a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js index d9bac77acf5ad..c7dbaacfdae9e 100644 --- a/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js +++ b/src/plugins/vis_types/vislib/public/vislib/visualizations/point_series/heatmap_chart.test.js @@ -13,7 +13,7 @@ import { setHTMLElementClientSizes, setSVGElementGetBBox, setSVGElementGetComputedTextLength, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; // Data import series from '../../../fixtures/mock_data/date_histogram/_series'; diff --git a/src/plugins/vis_types/xy/public/editor/components/common/truncate_labels.test.tsx b/src/plugins/vis_types/xy/public/editor/components/common/truncate_labels.test.tsx index 902167cb24642..a688ddb722620 100644 --- a/src/plugins/vis_types/xy/public/editor/components/common/truncate_labels.test.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/common/truncate_labels.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { TruncateLabelsOption, TruncateLabelsOptionProps } from './truncate_labels'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx index 3e1a44993235b..53acb302e9889 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/metrics_axes/value_axes_panel.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { Position } from '@elastic/charts'; import { ValueAxis, SeriesParam } from '../../../../types'; diff --git a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx index 5bf9486bbc4ee..dbd1bed390574 100644 --- a/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx +++ b/src/plugins/vis_types/xy/public/editor/components/options/point_series/point_series.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { PointSeriesOptions } from './point_series'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/src/plugins/vis_types/xy/public/to_ast.ts b/src/plugins/vis_types/xy/public/to_ast.ts index 9e2c7554aaf7c..9dec2f9af5ba4 100644 --- a/src/plugins/vis_types/xy/public/to_ast.ts +++ b/src/plugins/vis_types/xy/public/to_ast.ts @@ -34,6 +34,7 @@ import { XyVisType } from '../common'; import { getEsaggsFn } from './to_ast_esaggs'; import { TimeRangeBounds } from '../../../data/common'; import { getSeriesParams } from './utils/get_series_params'; +import { getSafeId } from './utils/accessors'; const prepareLabel = (data: Labels) => { const label = buildExpressionFunction('label', { @@ -189,8 +190,9 @@ export const toExpressionAst: VisToExpressionAst = async (vis, params (dimensions.y || []).forEach((yDimension) => { const yAgg = responseAggs[yDimension.accessor]; + const aggId = getSafeId(yAgg.id); const seriesParam = (vis.params.seriesParams || []).find( - (param: any) => param.data.id === yAgg.id + (param: any) => param.data.id === aggId ); if (seriesParam) { const usedValueAxis = (vis.params.valueAxes || []).find( diff --git a/src/plugins/vis_types/xy/public/utils/get_color_picker.test.tsx b/src/plugins/vis_types/xy/public/utils/get_color_picker.test.tsx index 352cff0d4c4a9..89cb8d50d4b8e 100644 --- a/src/plugins/vis_types/xy/public/utils/get_color_picker.test.tsx +++ b/src/plugins/vis_types/xy/public/utils/get_color_picker.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { LegendColorPickerProps, XYChartSeriesIdentifier } from '@elastic/charts'; import { EuiPopover } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ComponentType, ReactWrapper } from 'enzyme'; import { getColorPicker } from './get_color_picker'; import { ColorPicker } from '../../../../charts/public'; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index 22c39ad1d596a..2868950867ad5 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -103,15 +103,19 @@ export class VisualizeEmbeddableFactory })`; }, showSavedObject: (savedObject) => { - const typeName: string = JSON.parse(savedObject.attributes.visState).type; - const visType = getTypes().get(typeName); - if (!visType) { + try { + const typeName: string = JSON.parse(savedObject.attributes.visState).type; + const visType = getTypes().get(typeName); + if (!visType) { + return false; + } + if (getUISettings().get(VISUALIZE_ENABLE_LABS_SETTING)) { + return true; + } + return visType.stage !== 'experimental'; + } catch { return false; } - if (getUISettings().get(VISUALIZE_ENABLE_LABS_SETTING)) { - return true; - } - return visType.stage !== 'experimental'; }, getSavedObjectSubType: (savedObject) => { return JSON.parse(savedObject.attributes.visState).type; diff --git a/src/plugins/visualizations/public/visualize_app/components/experimental_vis_info.tsx b/src/plugins/visualizations/public/visualize_app/components/experimental_vis_info.tsx index 5a591f4518cf4..3cb91537fc91d 100644 --- a/src/plugins/visualizations/public/visualize_app/components/experimental_vis_info.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/experimental_vis_info.tsx @@ -15,7 +15,9 @@ export const InfoComponent = () => { <> { className="visListingTable__experimentalIcon" label="E" title={i18n.translate('visualizations.listing.experimentalTitle', { - defaultMessage: 'Experimental', + defaultMessage: 'Technical preview', })} tooltipContent={i18n.translate('visualizations.listing.experimentalTooltip', { defaultMessage: - 'This visualization might be changed or removed in a future release and is not subject to the support SLA.', + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', })} /> ); diff --git a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx index e3707a7aace79..b776068ab9c49 100644 --- a/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx +++ b/src/plugins/visualizations/public/wizard/agg_based_selection/agg_based_selection.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TypesStart, BaseVisType, VisGroups } from '../../vis_types'; import { AggBasedSelection } from './agg_based_selection'; diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx index 04824ae25704d..ae9d13b879cf7 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TypesStart, BaseVisType, VisGroups } from '../../vis_types'; import { GroupSelection } from './group_selection'; import { DocLinksStart } from '../../../../../core/public'; diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx index a8080cac2c06c..bd6e64597ec8f 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx @@ -238,10 +238,10 @@ const ToolsGroup = ({ visType, onVisTypeSelected, showExperimental }: VisCardPro iconType="beaker" tooltipContent={i18n.translate('visualizations.newVisWizard.experimentalTooltip', { defaultMessage: - 'This visualization might be changed or removed in a future release and is not subject to the support SLA.', + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', })} label={i18n.translate('visualizations.newVisWizard.experimentalTitle', { - defaultMessage: 'Experimental', + defaultMessage: 'Technical preview', })} /> diff --git a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx index 7514f467840e5..ae65251409c20 100644 --- a/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx +++ b/src/plugins/visualizations/public/wizard/new_vis_modal.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TypesStart, VisGroups, BaseVisType } from '../vis_types'; import NewVisModal from './new_vis_modal'; import { ApplicationStart, SavedObjectsStart, DocLinksStart } from '../../../../core/public'; diff --git a/src/plugins/visualizations/server/embeddable/make_visualize_embeddable_factory.ts b/src/plugins/visualizations/server/embeddable/make_visualize_embeddable_factory.ts index fa41cf6354d65..4b72cc0320c14 100644 --- a/src/plugins/visualizations/server/embeddable/make_visualize_embeddable_factory.ts +++ b/src/plugins/visualizations/server/embeddable/make_visualize_embeddable_factory.ts @@ -25,6 +25,7 @@ import { commonAddDropLastBucketIntoTSVBModel, commonAddDropLastBucketIntoTSVBModel714Above, commonRemoveMarkdownLessFromTSVB, + commonUpdatePieVisApi, } from '../migrations/visualization_common_migrations'; import { SerializedVis } from '../../common'; @@ -91,6 +92,11 @@ const byValueRemoveMarkdownLessFromTSVB = (state: SerializableRecord) => { }; }; +const byValueUpdatePieVisApi = (state: SerializableRecord) => ({ + ...state, + savedVis: commonUpdatePieVisApi(state.savedVis), +}); + const getEmbeddedVisualizationSearchSourceMigrations = ( searchSourceMigrations: MigrateFunctionsObject ) => @@ -137,6 +143,7 @@ export const makeVisualizeEmbeddableFactory = )(state), '7.17.0': (state) => flow(byValueAddDropLastBucketIntoTSVBModel714Above)(state), '8.0.0': (state) => flow(byValueRemoveMarkdownLessFromTSVB)(state), + '8.1.0': (state) => flow(byValueUpdatePieVisApi)(state), } ), }; diff --git a/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts index 65e61c4cd81aa..aec452e356abe 100644 --- a/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts +++ b/src/plugins/visualizations/server/migrations/visualization_common_migrations.ts @@ -199,3 +199,19 @@ export const commonRemoveMarkdownLessFromTSVB = (visState: any) => { return visState; }; + +export const commonUpdatePieVisApi = (visState: any) => { + if (visState && visState.type === 'pie') { + const { addLegend, ...restParams } = visState.params; + + return { + ...visState, + params: { + ...restParams, + legendDisplay: addLegend ? 'show' : 'hide', + }, + }; + } + + return visState; +}; diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts index 093990eab8584..083e1ae51575b 100644 --- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts +++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.test.ts @@ -2469,4 +2469,74 @@ describe('migration visualization', () => { }, }); }); + + describe('8.1.0 pie - labels and addLegend migration', () => { + const getDoc = (addLegend: boolean, lastLevel: boolean = false) => ({ + attributes: { + title: 'Pie Vis', + description: 'Pie vis', + visState: JSON.stringify({ + type: 'pie', + title: 'Pie vis', + params: { + addLegend, + addTooltip: true, + isDonut: true, + labels: { + position: 'default', + show: true, + truncate: 100, + values: true, + valuesFormat: 'percent', + percentDecimals: 2, + last_level: lastLevel, + }, + legendPosition: 'right', + nestedLegend: false, + maxLegendLines: 1, + truncateLegend: true, + distinctColors: false, + palette: { + name: 'default', + type: 'palette', + }, + dimensions: { + metric: { + type: 'vis_dimension', + accessor: 1, + format: { + id: 'number', + params: { + id: 'number', + }, + }, + }, + buckets: [], + }, + }, + }), + }, + }); + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['8.1.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + + it('should migrate addLegend to legendDisplay', () => { + const pie = getDoc(true); + const migrated = migrate(pie); + const params = JSON.parse(migrated.attributes.visState).params; + + expect(params.legendDisplay).toBe('show'); + expect(params.addLegend).toBeUndefined(); + + const otherPie = getDoc(false); + const otherMigrated = migrate(otherPie); + const otherParams = JSON.parse(otherMigrated.attributes.visState).params; + + expect(otherParams.legendDisplay).toBe('hide'); + expect(otherParams.addLegend).toBeUndefined(); + }); + }); }); diff --git a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts index d168e82f69739..4855b2589bed3 100644 --- a/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts +++ b/src/plugins/visualizations/server/migrations/visualization_saved_object_migrations.ts @@ -26,6 +26,7 @@ import { commonAddDropLastBucketIntoTSVBModel, commonAddDropLastBucketIntoTSVBModel714Above, commonRemoveMarkdownLessFromTSVB, + commonUpdatePieVisApi, } from './visualization_common_migrations'; import { VisualizationSavedObjectAttributes } from '../../common'; @@ -1132,6 +1133,30 @@ export const removeMarkdownLessFromTSVB: SavedObjectMigrationFn = (doc return doc; }; +export const updatePieVisApi: SavedObjectMigrationFn = (doc) => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + const newVisState = commonUpdatePieVisApi(visState); + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(newVisState), + }, + }; + } + + return doc; +}; + const visualizationSavedObjectTypeMigrations = { /** * We need to have this migration twice, once with a version prior to 7.0.0 once with a version @@ -1187,6 +1212,7 @@ const visualizationSavedObjectTypeMigrations = { ), '7.17.0': flow(addDropLastBucketIntoTSVBModel714Above), '8.0.0': flow(removeMarkdownLessFromTSVB), + '8.1.0': flow(updatePieVisApi), }; /** diff --git a/src/plugins/visualizations/server/plugin.ts b/src/plugins/visualizations/server/plugin.ts index 97c3e4bc19387..7ad8a44200bc1 100644 --- a/src/plugins/visualizations/server/plugin.ts +++ b/src/plugins/visualizations/server/plugin.ts @@ -55,12 +55,12 @@ export class VisualizationsPlugin core.uiSettings.register({ [VISUALIZE_ENABLE_LABS_SETTING]: { name: i18n.translate('visualizations.advancedSettings.visualizeEnableLabsTitle', { - defaultMessage: 'Enable experimental visualizations', + defaultMessage: 'Enable technical preview visualizations', }), value: true, description: i18n.translate('visualizations.advancedSettings.visualizeEnableLabsText', { - defaultMessage: `Allows users to create, view, and edit experimental visualizations. If disabled, - only visualizations that are considered production-ready are available to the user.`, + defaultMessage: `Allows users to create, view, and edit visualizations that are in technical preview. + If disabled, only visualizations that are considered production-ready are available to the user.`, }), category: ['visualization'], schema: schema.boolean(), diff --git a/test/common/services/elasticsearch.ts b/test/common/services/elasticsearch.ts index baa4050ee10f7..2f19bfe9105d0 100644 --- a/test/common/services/elasticsearch.ts +++ b/test/common/services/elasticsearch.ts @@ -6,12 +6,9 @@ * Side Public License, v 1. */ -import { format as formatUrl } from 'url'; -import fs from 'fs'; -import { Client, HttpConnection } from '@elastic/elasticsearch'; -import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { Client } from '@elastic/elasticsearch'; -import { systemIndicesSuperuser } from '@kbn/test'; +import { systemIndicesSuperuser, createEsClientForFtrConfig } from '@kbn/test'; import { FtrProviderContext } from '../ftr_provider_context'; /* @@ -20,26 +17,8 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function ElasticsearchProvider({ getService }: FtrProviderContext): Client { const config = getService('config'); - const esUrl = formatUrl({ - ...config.get('servers.elasticsearch'), + return createEsClientForFtrConfig(config, { // Use system indices user so tests can write to system indices - auth: `${systemIndicesSuperuser.username}:${systemIndicesSuperuser.password}`, + authOverride: systemIndicesSuperuser, }); - - if (process.env.TEST_CLOUD) { - return new Client({ - nodes: [esUrl], - requestTimeout: config.get('timeouts.esRequestTimeout'), - Connection: HttpConnection, - }); - } else { - return new Client({ - tls: { - ca: fs.readFileSync(CA_CERT_PATH, 'utf-8'), - }, - nodes: [esUrl], - requestTimeout: config.get('timeouts.esRequestTimeout'), - Connection: HttpConnection, - }); - } } diff --git a/test/common/services/index_patterns.ts b/test/common/services/index_patterns.ts index 5b6d20990b6d1..549137c79e9a2 100644 --- a/test/common/services/index_patterns.ts +++ b/test/common/services/index_patterns.ts @@ -7,7 +7,7 @@ */ import { FtrService } from '../ftr_provider_context'; -import { IndexPatternSpec } from '../../../src/plugins/data/common'; +import { DataViewSpec } from '../../../src/plugins/data/common'; export class IndexPatternsService extends FtrService { private readonly kibanaServer = this.ctx.getService('kibanaServer'); @@ -18,9 +18,9 @@ export class IndexPatternsService extends FtrService { async create( indexPattern: { title: string }, { override = false }: { override: boolean } = { override: false } - ): Promise { + ): Promise { const response = await this.kibanaServer.request<{ - index_pattern: IndexPatternSpec; + index_pattern: DataViewSpec; }>({ path: '/api/index_patterns/index_pattern', method: 'POST', diff --git a/test/common/services/security/security.ts b/test/common/services/security/security.ts index b8fea0a0c59b2..a182f225f2388 100644 --- a/test/common/services/security/security.ts +++ b/test/common/services/security/security.ts @@ -11,6 +11,7 @@ import { User } from './user'; import { RoleMappings } from './role_mappings'; import { FtrProviderContext } from '../../ftr_provider_context'; import { createTestUserService, TestUserSupertestProvider, TestUser } from './test_user'; +import { createSystemIndicesUser } from './system_indices_user'; export class SecurityService { constructor( @@ -28,6 +29,7 @@ export async function SecurityServiceProvider(ctx: FtrProviderContext) { const role = new Role(log, kibanaServer); const user = new User(log, kibanaServer); + await createSystemIndicesUser(ctx); const testUser = await createTestUserService(ctx, role, user); const testUserSupertest = TestUserSupertestProvider(ctx); const roleMappings = new RoleMappings(log, kibanaServer); diff --git a/test/common/services/security/system_indices_user.ts b/test/common/services/security/system_indices_user.ts new file mode 100644 index 0000000000000..c1ab6b1e0abfa --- /dev/null +++ b/test/common/services/security/system_indices_user.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { systemIndicesSuperuser, createEsClientForFtrConfig } from '@kbn/test'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const SYSTEM_INDICES_SUPERUSER_ROLE = 'system_indices_superuser'; + +export async function createSystemIndicesUser(ctx: FtrProviderContext) { + const log = ctx.getService('log'); + const config = ctx.getService('config'); + + const enabled = !config + .get('esTestCluster.serverArgs') + .some((arg: string) => arg === 'xpack.security.enabled=false'); + + if (!enabled) { + return; + } + + const es = createEsClientForFtrConfig(config); + + log.debug('===============creating system indices role and user==============='); + + await es.security.putRole({ + name: SYSTEM_INDICES_SUPERUSER_ROLE, + refresh: 'wait_for', + cluster: ['all'], + indices: [ + { + names: ['*'], + privileges: ['all'], + allow_restricted_indices: true, + }, + ], + applications: [ + { + application: '*', + privileges: ['*'], + resources: ['*'], + }, + ], + run_as: ['*'], + }); + + await es.security.putUser({ + username: systemIndicesSuperuser.username, + refresh: 'wait_for', + password: systemIndicesSuperuser.password, + roles: [SYSTEM_INDICES_SUPERUSER_ROLE], + }); + + await es.close(); +} diff --git a/test/functional/services/common/screenshots.ts b/test/functional/services/common/screenshots.ts index 0f2ab8e6edfbe..d5f901300941f 100644 --- a/test/functional/services/common/screenshots.ts +++ b/test/functional/services/common/screenshots.ts @@ -22,7 +22,7 @@ const writeFileAsync = promisify(writeFile); export class ScreenshotsService extends FtrService { private readonly log = this.ctx.getService('log'); private readonly config = this.ctx.getService('config'); - private readonly failureMetadata = this.ctx.getService('failureMetadata'); + private readonly testMetadata = this.ctx.getService('testMetadata'); private readonly browser = this.ctx.getService('browser'); private readonly SESSION_DIRECTORY = resolve(this.config.get('screenshots.directory'), 'session'); @@ -51,11 +51,17 @@ export class ScreenshotsService extends FtrService { async compareAgainstBaseline(name: string, updateBaselines: boolean, el?: WebElementWrapper) { this.log.debug('compareAgainstBaseline'); const sessionPath = resolve(this.SESSION_DIRECTORY, `${name}.png`); - await this.capture(sessionPath, el); - const baselinePath = resolve(this.BASELINE_DIRECTORY, `${name}.png`); const failurePath = resolve(this.FAILURE_DIRECTORY, `${name}.png`); + await this.capture({ + path: sessionPath, + name, + el, + baselinePath, + failurePath, + }); + if (updateBaselines) { this.log.debug('Updating baseline snapshot'); // Make the directory if it doesn't exist @@ -76,22 +82,42 @@ export class ScreenshotsService extends FtrService { async take(name: string, el?: WebElementWrapper, subDirectories: string[] = []) { const path = resolve(this.SESSION_DIRECTORY, ...subDirectories, `${name}.png`); - await this.capture(path, el); - this.failureMetadata.addScreenshot(name, path); + await this.capture({ path, name, el }); } async takeForFailure(name: string, el?: WebElementWrapper) { const path = resolve(this.FAILURE_DIRECTORY, `${name}.png`); - await this.capture(path, el); - this.failureMetadata.addScreenshot(`failure[${name}]`, path); + await this.capture({ + path, + name: `failure[${name}]`, + el, + }); } - private async capture(path: string, el?: WebElementWrapper) { + private async capture({ + path, + el, + name, + baselinePath, + failurePath, + }: { + path: string; + name: string; + el?: WebElementWrapper; + baselinePath?: string; + failurePath?: string; + }) { try { this.log.info(`Taking screenshot "${path}"`); const screenshot = await (el ? el.takeScreenshot() : this.browser.takeScreenshot()); await mkdirAsync(dirname(path), { recursive: true }); await writeFileAsync(path, screenshot, 'base64'); + this.testMetadata.addScreenshot({ + name, + base64Png: Buffer.isBuffer(screenshot) ? screenshot.toString('base64') : screenshot, + baselinePath, + failurePath, + }); } catch (err) { this.log.error('SCREENSHOT FAILED'); this.log.error(err); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index aac29086fe53d..71ff1032819ff 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -60,6 +60,7 @@ "xpack.triggersActionsUI": "plugins/triggers_actions_ui", "xpack.upgradeAssistant": "plugins/upgrade_assistant", "xpack.uptime": ["plugins/uptime"], + "xpack.ux": ["plugins/ux"], "xpack.urlDrilldown": "plugins/drilldowns/url_drilldown", "xpack.watcher": "plugins/watcher", "xpack.observability": "plugins/observability", diff --git a/x-pack/examples/third_party_vis_lens_example/.eslintrc.json b/x-pack/examples/third_party_vis_lens_example/.eslintrc.json new file mode 100644 index 0000000000000..2aab6c2d9093b --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "rules": { + "@typescript-eslint/consistent-type-definitions": 0 + } +} diff --git a/x-pack/examples/third_party_vis_lens_example/README.md b/x-pack/examples/third_party_vis_lens_example/README.md new file mode 100644 index 0000000000000..f2fa563f33393 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/README.md @@ -0,0 +1,23 @@ +# Third party Lens visualization + +To run this example plugin, use the command `yarn start --run-examples`. + +This example shows how to register a visualization to Lens which lives along the regular visualizations (xy, table and so on). + +The following parts can be seen in this example: +* Registering the visualization type so it shows up in the Lens editor along with custom edit UI and hooks to update state on user interactions (add dimension, delete dimension). +* Registering the used expression functions and expression renderers to actually render the expression into a DOM element. +* Providing a sample migration on the Kibana server which allows to update existing stored visualizations and change their state on Kibana upgrade / import of old saved objects. + + +To test the migration, you can import the following ndjson file via saved object import (requires installed logs sample data): +
+ Click to expand + +``` +{"attributes":{"fieldFormatMap":"{\"hour_of_day\":{}}","runtimeFieldMap":"{\"hour_of_day\":{\"type\":\"long\",\"script\":{\"source\":\"emit(doc['timestamp'].value.getHour());\"}}}","timeFieldName":"timestamp","title":"kibana_sample_data_logs"},"coreMigrationVersion":"8.0.0","id":"90943e30-9a47-11e8-b64d-95841ca0b247","migrationVersion":{"index-pattern":"8.0.0"},"references":[],"type":"index-pattern","updated_at":"2022-01-24T10:54:24.209Z","version":"WzQzMTQ3LDFd"} +{"attributes":{"description":"","state":{"datasourceStates":{"indexpattern":{"layers":{"f2700077-50bf-48e4-829c-f695f87e226d":{"columnOrder":["5e704cac-8490-457a-b635-01f3a5a132b7"],"columns":{"5e704cac-8490-457a-b635-01f3a5a132b7":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"}},"incompleteColumns":{}}}}},"filters":[],"query":{"language":"kuery","query":""},"visualization":{"column":"5e704cac-8490-457a-b635-01f3a5a132b7","layerId":"f2700077-50bf-48e4-829c-f695f87e226d"}},"title":"Rotating number test","visualizationType":"rotatingNumber"},"coreMigrationVersion":"8.0.0","id":"468f0be0-7e86-11ec-9739-d570ffd3fbe4","migrationVersion":{"lens":"8.0.0"},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f2700077-50bf-48e4-829c-f695f87e226d","type":"index-pattern"}],"type":"lens","updated_at":"2022-01-26T08:59:31.618Z","version":"WzQzNjUzLDFd"} +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]} +``` + +
\ No newline at end of file diff --git a/x-pack/examples/third_party_vis_lens_example/common/constants.ts b/x-pack/examples/third_party_vis_lens_example/common/constants.ts new file mode 100644 index 0000000000000..216ad8b1d0e34 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/common/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const DEFAULT_COLOR = '#000000'; diff --git a/x-pack/examples/third_party_vis_lens_example/common/types.ts b/x-pack/examples/third_party_vis_lens_example/common/types.ts new file mode 100644 index 0000000000000..b4afef06fb482 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/common/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface RotatingNumberState { + accessor?: string; + color: string; + layerId: string; +} diff --git a/x-pack/examples/third_party_vis_lens_example/kibana.json b/x-pack/examples/third_party_vis_lens_example/kibana.json new file mode 100644 index 0000000000000..858466f7f7640 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/kibana.json @@ -0,0 +1,24 @@ +{ + "id": "thirdPartyVisLensExample", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["third_part_vis_lens_example"], + "server": true, + "ui": true, + "requiredPlugins": [ + "lens", + "dataViews", + "embeddable", + "developerExamples", + "expressions", + "fieldFormats" + ], + "optionalPlugins": [], + "requiredBundles": [ + "kibanaReact" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + } +} diff --git a/x-pack/examples/third_party_vis_lens_example/package.json b/x-pack/examples/third_party_vis_lens_example/package.json new file mode 100644 index 0000000000000..5892f4e508f2d --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/package.json @@ -0,0 +1,14 @@ +{ + "name": "third_party_vis_lens_example", + "version": "1.0.0", + "main": "target/examples/third_party_vis_lens_example", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Elastic License 2.0", + "scripts": { + "kbn": "node ../../../scripts/kbn.js", + "build": "rm -rf './target' && ../../../node_modules/.bin/tsc" + } +} \ No newline at end of file diff --git a/x-pack/examples/third_party_vis_lens_example/public/expression.tsx b/x-pack/examples/third_party_vis_lens_example/public/expression.tsx new file mode 100644 index 0000000000000..6e38f2e582209 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/public/expression.tsx @@ -0,0 +1,133 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { css, keyframes } from '@emotion/css'; +import type { + Datatable, + ExpressionFunctionDefinition, + ExpressionRenderDefinition, + IInterpreterRenderHandlers, +} from '../../../../src/plugins/expressions/public'; +import { RotatingNumberState } from '../common/types'; +import { FormatFactory } from '../../../../src/plugins/field_formats/common'; + +export const getRotatingNumberRenderer = ( + formatFactory: Promise +): ExpressionRenderDefinition => ({ + name: 'rotating_number', + displayName: 'Rotating number', + help: 'Rotating number renderer', + validate: () => undefined, + reuseDomNode: true, + render: async ( + domNode: Element, + config: RotatingNumberChartProps, + handlers: IInterpreterRenderHandlers + ) => { + ReactDOM.render( + , + domNode, + () => { + handlers.done(); + } + ); + handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); + }, +}); + +const rotating = keyframes` + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +`; + +function RotatingNumberChart({ + data, + args, + formatFactory, +}: RotatingNumberChartProps & { formatFactory: FormatFactory }) { + const { accessor, color } = args; + const column = data.columns.find((col) => col.id === accessor); + const rawValue = accessor && data.rows[0]?.[accessor]; + + const value = + column && column.meta?.params + ? formatFactory(column.meta?.params).convert(rawValue) + : Number(Number(rawValue).toFixed(3)).toString(); + + return ( +
+
+ {value} +
+
+ ); +} +export interface RotatingNumberChartProps { + data: Datatable; + args: RotatingNumberState; +} + +interface RotatingNumberRender { + type: 'render'; + as: 'rotating_number'; + value: RotatingNumberChartProps; +} + +export const rotatingNumberFunction: ExpressionFunctionDefinition< + 'rotating_number', + Datatable, + Omit, + RotatingNumberRender +> = { + name: 'rotating_number', + type: 'render', + help: 'A rotating number', + args: { + accessor: { + types: ['string'], + help: 'The column whose value is being displayed', + }, + color: { + types: ['string'], + help: 'Color of the number', + }, + }, + inputTypes: ['datatable'], + fn(data, args) { + return { + type: 'render', + as: 'rotating_number', + value: { + data, + args, + }, + } as RotatingNumberRender; + }, +}; diff --git a/x-pack/examples/third_party_vis_lens_example/public/index.ts b/x-pack/examples/third_party_vis_lens_example/public/index.ts new file mode 100644 index 0000000000000..afe76682f6123 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/public/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EmbeddedLensExamplePlugin } from './plugin'; + +export const plugin = () => new EmbeddedLensExamplePlugin(); diff --git a/x-pack/examples/third_party_vis_lens_example/public/plugin.ts b/x-pack/examples/third_party_vis_lens_example/public/plugin.ts new file mode 100644 index 0000000000000..f185f0ee28efb --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/public/plugin.ts @@ -0,0 +1,138 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExpressionsSetup } from 'src/plugins/expressions/public'; +import { FieldFormatsStart } from 'src/plugins/field_formats/public'; +import { Plugin, CoreSetup, AppNavLinkStatus } from '../../../../src/core/public'; +import { DataViewsPublicPluginStart, DataView } from '../../../../src/plugins/data_views/public'; +import { LensPublicSetup, LensPublicStart } from '../../../plugins/lens/public'; +import { DeveloperExamplesSetup } from '../../../../examples/developer_examples/public'; +import { TypedLensByValueInput, PersistedIndexPatternLayer } from '../../../plugins/lens/public'; +import { getRotatingNumberRenderer, rotatingNumberFunction } from './expression'; +import { getRotatingNumberVisualization } from './visualization'; +import { RotatingNumberState } from '../common/types'; + +export interface SetupDependencies { + developerExamples: DeveloperExamplesSetup; + lens: LensPublicSetup; + expressions: ExpressionsSetup; +} + +export interface StartDependencies { + dataViews: DataViewsPublicPluginStart; + lens: LensPublicStart; + fieldFormats: FieldFormatsStart; +} + +function getLensAttributes(defaultDataView: DataView): TypedLensByValueInput['attributes'] { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['col1'], + columns: { + col1: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + }, + }; + + const rotatingNumberConfig: RotatingNumberState = { + accessor: 'col1', + color: '#ff0000', + layerId: 'layer1', + }; + + return { + visualizationType: 'rotatingNumber', + title: 'Prefilled from example app', + references: [ + { + id: defaultDataView.id!, + name: 'indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: defaultDataView.id!, + name: 'indexpattern-datasource-layer-layer1', + type: 'index-pattern', + }, + ], + state: { + datasourceStates: { + indexpattern: { + layers: { + layer1: dataLayer, + }, + }, + }, + filters: [], + query: { language: 'kuery', query: '' }, + visualization: rotatingNumberConfig, + }, + }; +} + +export class EmbeddedLensExamplePlugin + implements Plugin +{ + public setup( + core: CoreSetup, + { developerExamples, lens, expressions }: SetupDependencies + ) { + core.application.register({ + id: 'third_party_lens_vis_example', + title: 'Third party Lens vis example', + navLinkStatus: AppNavLinkStatus.hidden, + mount: (params) => { + (async () => { + const [, { lens: lensStart, dataViews }] = await core.getStartServices(); + const defaultDataView = await dataViews.getDefault(); + lensStart.navigateToPrefilledEditor({ + id: '', + timeRange: { + from: 'now-5d', + to: 'now', + }, + attributes: getLensAttributes(defaultDataView!), + }); + })(); + return () => {}; + }, + }); + + developerExamples.register({ + appId: 'third_party_lens_vis_example', + title: 'Third party Lens visualization', + description: 'Add custom visualization types to the Lens editor', + links: [ + { + label: 'README', + href: 'https://github.com/elastic/kibana/tree/main/x-pack/examples/third_party_lens_vis_example', + iconType: 'logoGithub', + size: 's', + target: '_blank', + }, + ], + }); + + expressions.registerRenderer(() => + getRotatingNumberRenderer( + core.getStartServices().then(([, { fieldFormats }]) => fieldFormats.deserialize) + ) + ); + expressions.registerFunction(() => rotatingNumberFunction); + + lens.registerVisualization(async () => getRotatingNumberVisualization({ theme: core.theme })); + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/examples/third_party_vis_lens_example/public/visualization.tsx b/x-pack/examples/third_party_vis_lens_example/public/visualization.tsx new file mode 100644 index 0000000000000..e7a6c60ad5b2e --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/public/visualization.tsx @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFormRow, EuiColorPicker } from '@elastic/eui'; +import { render } from 'react-dom'; +import { Ast } from '@kbn/interpreter'; +import { ThemeServiceStart } from '../../../../src/core/public'; +import { KibanaThemeProvider } from '../../../../src/plugins/kibana_react/public'; +import { Visualization, OperationMetadata } from '../../../plugins/lens/public'; +import type { RotatingNumberState } from '../common/types'; +import { DEFAULT_COLOR } from '../common/constants'; +import { layerTypes } from '../../../plugins/lens/public'; + +const toExpression = (state: RotatingNumberState): Ast | null => { + if (!state.accessor) { + return null; + } + + return { + type: 'expression', + chain: [ + { + type: 'function', + function: 'rotating_number', + arguments: { + accessor: [state.accessor], + color: [state?.color || 'black'], + }, + }, + ], + }; +}; +export const getRotatingNumberVisualization = ({ + theme, +}: { + theme: ThemeServiceStart; +}): Visualization => ({ + id: 'rotatingNumber', + + visualizationTypes: [ + { + id: 'rotatingNumber', + icon: 'refresh', + label: 'Rotating number', + groupLabel: 'Goal and single value', + sortPriority: 3, + }, + ], + + getVisualizationTypeId() { + return 'rotatingNumber'; + }, + + clearLayer(state) { + return { + ...state, + accessor: undefined, + }; + }, + + getLayerIds(state) { + return [state.layerId]; + }, + + getDescription() { + return { + icon: 'refresh', + label: 'A number that rotates', + }; + }, + + getSuggestions: ({ state, table }) => { + if (table.columns.length > 1) { + return []; + } + if (state && table.changeType === 'unchanged') { + return []; + } + const column = table.columns[0]; + if (column.operation.isBucketed || column.operation.dataType !== 'number') { + return []; + } + return [ + { + previewIcon: 'refresh', + score: 0.5, + title: `Rotating ${table.label}` || 'Rotating number', + state: { + layerId: table.layerId, + color: state?.color || DEFAULT_COLOR, + accessor: column.columnId, + }, + }, + ]; + }, + + initialize(addNewLayer, state) { + return ( + state || { + layerId: addNewLayer(), + accessor: undefined, + color: DEFAULT_COLOR, + } + ); + }, + + getConfiguration(props) { + return { + groups: [ + { + groupId: 'metric', + groupLabel: 'Rotating number', + layerId: props.state.layerId, + accessors: props.state.accessor + ? [ + { + columnId: props.state.accessor, + triggerIcon: 'color', + color: props.state.color, + }, + ] + : [], + supportsMoreColumns: !props.state.accessor, + filterOperations: (op: OperationMetadata) => !op.isBucketed && op.dataType === 'number', + enableDimensionEditor: true, + required: true, + }, + ], + }; + }, + + getSupportedLayers() { + return [ + { + type: layerTypes.DATA, + label: 'Add visualization layer', + }, + ]; + }, + + getLayerType(layerId, state) { + if (state?.layerId === layerId) { + return layerTypes.DATA; + } + }, + + toExpression: (state) => toExpression(state), + toPreviewExpression: (state) => toExpression(state), + + setDimension({ prevState, columnId }) { + return { ...prevState, accessor: columnId }; + }, + + removeDimension({ prevState }) { + return { ...prevState, accessor: undefined }; + }, + + renderDimensionEditor(domElement, props) { + render( + + + { + props.setState({ ...props.state, color: newColor }); + }} + color={props.state.color} + /> + + , + domElement + ); + }, + + getErrorMessages(state) { + // Is it possible to break it? + return undefined; + }, +}); diff --git a/x-pack/examples/third_party_vis_lens_example/server/index.ts b/x-pack/examples/third_party_vis_lens_example/server/index.ts new file mode 100644 index 0000000000000..cab22f4ad29c9 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializer } from 'kibana/server'; +import { ThirdPartyVisLensExamplePlugin } from './plugin'; + +export const plugin: PluginInitializer = () => new ThirdPartyVisLensExamplePlugin(); diff --git a/x-pack/examples/third_party_vis_lens_example/server/plugin.ts b/x-pack/examples/third_party_vis_lens_example/server/plugin.ts new file mode 100644 index 0000000000000..b3e99b665c494 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/server/plugin.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Plugin, CoreSetup } from 'kibana/server'; +import { LensServerPluginSetup } from '../../../plugins/lens/server'; +import { DEFAULT_COLOR } from '../common/constants'; +import { RotatingNumberState as Post81RotatingNumberState } from '../common/types'; + +// Old versions of this visualization had a slightly different shape of state +interface Pre81RotatingNumberState { + column?: string; + layerId: string; +} + +// this plugin's dependencies +export interface Dependencies { + lens: LensServerPluginSetup; +} + +export class ThirdPartyVisLensExamplePlugin implements Plugin { + public setup(core: CoreSetup, { lens }: Dependencies) { + lens.registerVisualizationMigration('rotatingNumber', () => ({ + // Example state migration which will be picked by all the places Lens visualizations are stored + '8.1.0': (oldState: Pre81RotatingNumberState): Post81RotatingNumberState => { + return { + // column gets renamed to accessor + accessor: oldState.column, + // layer id just gets copied over + layerId: oldState.layerId, + // color gets pre-set with default color + color: DEFAULT_COLOR, + }; + }, + })); + } + + public start() {} + public stop() {} +} diff --git a/x-pack/examples/third_party_vis_lens_example/tsconfig.json b/x-pack/examples/third_party_vis_lens_example/tsconfig.json new file mode 100644 index 0000000000000..d9d1af39a2b98 --- /dev/null +++ b/x-pack/examples/third_party_vis_lens_example/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types" + }, + "include": [ + "index.ts", + "public/**/*", + "server/**/*", + "common/**/*", + "../../../typings/**/*" + ], + "exclude": [], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/expressions/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/data_views/tsconfig.json" }, + { "path": "../../../src/plugins/field_formats/tsconfig.json" }, + { "path": "../../../src/plugins/embeddable/tsconfig.json" }, + { "path": "../../plugins/lens/tsconfig.json" }, + { "path": "../../../examples/developer_examples/tsconfig.json" }, + ] +} diff --git a/x-pack/package.json b/x-pack/package.json index 8fb7a3483e5ef..ccfad71a4f7b3 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -1,6 +1,6 @@ { "name": "x-pack", - "version": "8.1.0", + "version": "8.2.0", "author": "Elastic", "private": true, "license": "Elastic-License", diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 3565a978b5f07..18e405f70c859 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -19,7 +19,7 @@ import { getActionsConfigurationUtilities } from './actions_config'; import { licenseStateMock } from './lib/license_state.mock'; import { licensingMock } from '../../licensing/server/mocks'; import { httpServerMock, loggingSystemMock } from '../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../security/server/audit/mocks'; import { usageCountersServiceMock } from 'src/plugins/usage_collection/server/usage_counters/usage_counters_service.mock'; import { @@ -72,7 +72,7 @@ const authorization = actionsAuthorizationMock.create(); const executionEnqueuer = jest.fn(); const ephemeralExecutionEnqueuer = jest.fn(); const request = httpServerMock.createKibanaRequest(); -const auditLogger = auditServiceMock.create().asScoped(request); +const auditLogger = auditLoggerMock.create(); const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract(); const mockUsageCounter = mockUsageCountersSetup.createUsageCounter('test'); const logger = loggingSystemMock.create().get() as jest.Mocked; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index cd452ce6df571..fc38d540e0274 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { RegistryRuleType } from '../../rule_type_registry'; @@ -27,7 +26,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index 4061917b15197..1485d8b639159 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -16,8 +16,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization, ActionsClient } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { getDefaultRuleMonitoring } from '../../task_runner/task_runner'; @@ -34,7 +33,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v8.0.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts index 4847314a2ceae..cfb684f4241ee 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/delete.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup } from './lib'; const taskManager = taskManagerMock.createStart(); @@ -24,7 +23,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts index 9c3e5872c76e1..afb67ef47c62f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/disable.test.ts @@ -15,8 +15,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { InvalidatePendingApiKey } from '../../types'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { eventLoggerMock } from '../../../../event_log/server/event_logger.mock'; import { TaskStatus } from '../../../../task_manager/server'; @@ -31,7 +30,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const eventLogger = eventLoggerMock.create(); const kibanaVersion = 'v7.10.0'; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts index afa7db98cab08..c2826974fb8e8 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/enable.test.ts @@ -15,8 +15,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { InvalidatePendingApiKey } from '../../types'; import { getBeforeSetup, setGlobalDate } from './lib'; @@ -26,7 +25,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 2729b42490d9f..c0079d7787281 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -16,8 +16,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; import { RegistryRuleType } from '../../rule_type_registry'; @@ -28,7 +27,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index d61796431a1ee..c704c3eec1241 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; @@ -25,7 +24,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 883885c28aff7..76bc313bed024 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); @@ -24,7 +23,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts index cb19066500bde..4a77bfd38ef83 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_instance.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); @@ -24,7 +23,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 8102dcedbd780..bfa2a10189a05 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; import { RecoveredActionGroup } from '../../../common'; @@ -25,7 +24,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index 4d08da7289e4c..e05ea363452e8 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); @@ -24,7 +23,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts index 8319c486e965f..fa228478a39d8 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_instance.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); @@ -24,7 +23,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 55ffc49fd3394..f59b69c0b3fbf 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -20,8 +20,7 @@ import { AlertingAuthorization } from '../../authorization/alerting_authorizatio import { resolvable } from '../../test_utils'; import { ActionsAuthorization, ActionsClient } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { getBeforeSetup, setGlobalDate } from './lib'; jest.mock('../../../../../../src/core/server/saved_objects/service/lib/utils', () => ({ @@ -36,7 +35,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts index 0ee6d1162eca8..0b335a13d07e3 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update_api_key.test.ts @@ -14,8 +14,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { httpServerMock } from '../../../../../../src/core/server/mocks'; -import { auditServiceMock } from '../../../../security/server/audit/index.mock'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { InvalidatePendingApiKey } from '../../types'; import { getBeforeSetup, setGlobalDate } from './lib'; @@ -25,7 +24,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); -const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); +const auditLogger = auditLoggerMock.create(); const kibanaVersion = 'v7.10.0'; const rulesClientParams: jest.Mocked = { diff --git a/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts b/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts index daac81298008e..695cf38bf2da2 100644 --- a/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts +++ b/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts @@ -257,10 +257,10 @@ export const createJsAgentInstructions = (apmServerUrl = '') => [ { defaultMessage: 'APM Server disables RUM support by default. See the [documentation]({documentationLink}) \ -for details on how to enable RUM support.', +for details on how to enable RUM support. When using the APM integration with Fleet, RUM support is automatically enabled.', values: { documentationLink: - '{config.docs.base_url}guide/en/apm/server/{config.docs.version}/configuration-rum.html', + '{config.docs.base_url}guide/en/apm/guide/{config.docs.version}/configuration-rum.html', }, } ), diff --git a/x-pack/plugins/apm/common/ux_ui_filter.ts b/x-pack/plugins/apm/common/ux_ui_filter.ts index 4b2ded2fd50ad..f1dd0394de62a 100644 --- a/x-pack/plugins/apm/common/ux_ui_filter.ts +++ b/x-pack/plugins/apm/common/ux_ui_filter.ts @@ -118,3 +118,9 @@ export const uxLocalUIFilters = uxLocalUIFilterNames.reduce((acc, key) => { }, }; }, {} as UxLocalUIFilterMap); + +export type UxUIFilters = { + environment?: string; +} & { + [key in UxLocalUIFilterName]?: string[]; +}; diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts index 90cf964691274..0c924e70a1fdf 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts @@ -4,10 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import Fs from 'fs'; -import { Client, HttpConnection } from '@elastic/elasticsearch'; import { apm, createLogger, LogLevel } from '@elastic/apm-synthtrace'; -import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { createEsClientForTesting } from '@kbn/test'; // *********************************************************** // This example plugins/index.ts can be used to load plugins @@ -29,15 +27,10 @@ const plugin: Cypress.PluginConfig = (on, config) => { // `on` is used to hook into various events Cypress emits // `config` is the resolved Cypress config - const node = config.env.ES_NODE; - const requestTimeout = config.env.ES_REQUEST_TIMEOUT; - const isCloud = config.env.TEST_CLOUD; - - const client = new Client({ - node, - requestTimeout, - Connection: HttpConnection, - ...(isCloud ? { tls: { ca: Fs.readFileSync(CA_CERT_PATH, 'utf-8') } } : {}), + const client = createEsClientForTesting({ + esUrl: config.env.ES_NODE, + requestTimeout: config.env.ES_REQUEST_TIMEOUT, + isCloud: !!config.env.TEST_CLOUD, }); const synthtraceEsClient = new apm.ApmSynthtraceEsClient( diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 846847dcd7e05..8b5ac68f07a22 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -24,7 +24,6 @@ "cloud", "fleet", "home", - "maps", "ml", "security", "spaces", @@ -39,7 +38,6 @@ "home", "kibanaReact", "kibanaUtils", - "maps", "ml", "observability" ] diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx index 5ea9b6999483e..5a7df0708caed 100644 --- a/x-pack/plugins/apm/public/application/application.test.tsx +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -7,29 +7,21 @@ import React from 'react'; import { act } from '@testing-library/react'; -import { EuiErrorBoundary } from '@elastic/eui'; -import { mount } from 'enzyme'; import { createMemoryHistory } from 'history'; import { Observable } from 'rxjs'; import { AppMountParameters, DocLinksStart, HttpStart } from 'src/core/public'; import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context'; import { createCallApmApi } from '../services/rest/create_call_apm_api'; import { renderApp as renderApmApp } from './'; -import { UXAppRoot } from './ux_app'; import { disableConsoleWarning } from '../utils/test_helpers'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; -import { RumHome } from '../components/app/rum_dashboard/rum_home'; jest.mock('../services/rest/data_view', () => ({ createStaticDataView: () => Promise.resolve(undefined), })); -jest.mock('../components/app/rum_dashboard/rum_home', () => ({ - RumHome: () =>

Home Mock

, -})); - describe('renderApp (APM)', () => { let mockConsole: jest.SpyInstance; beforeAll(() => { @@ -148,21 +140,3 @@ describe('renderApp (APM)', () => { }).not.toThrowError(); }); }); - -describe('renderUxApp', () => { - it('has an error boundary for the UXAppRoot', async () => { - const uxMountProps = mockApmPluginContextValue; - - const wrapper = mount(); - - wrapper - .find(RumHome) - .simulateError(new Error('Oh no, an unexpected error!')); - - expect(wrapper.find(RumHome)).toHaveLength(0); - expect(wrapper.find(EuiErrorBoundary)).toHaveLength(1); - expect(wrapper.find(EuiErrorBoundary).text()).toMatch( - /Error: Oh no, an unexpected error!/ - ); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx index f00487cdc4082..6d20faae89a10 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/failed_transactions_correlations.tsx @@ -16,7 +16,6 @@ import { EuiSpacer, EuiIcon, EuiTitle, - EuiBetaBadge, EuiBadge, EuiToolTip, EuiSwitch, @@ -451,31 +450,6 @@ export function FailedTransactionsCorrelations({ - - - - - diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_call_api.ts b/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_call_api.ts deleted file mode 100644 index a7a5dd0d2d5ff..0000000000000 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_call_api.ts +++ /dev/null @@ -1,19 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useMemo } from 'react'; -import { callApi } from '../../../../services/rest/call_api'; -import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { FetchOptions } from '../../../../../common/fetch_options'; - -export function useCallApi() { - const { core } = useApmPluginContext(); - - return useMemo(() => { - return (options: FetchOptions) => callApi(core, options); - }, [core]); -} diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/use_data_view.test.js b/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/use_data_view.test.js deleted file mode 100644 index d9eef896782ca..0000000000000 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/use_data_view.test.js +++ /dev/null @@ -1,44 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import * as dynamicDataView from '../../../../hooks/use_dynamic_data_view'; -import { useDataView } from './use_data_view'; -import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; -import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; - -describe('useDataView', () => { - const create = jest.fn(); - const mockDataService = { - data: { - dataViews: { - create, - }, - }, - }; - - const title = 'apm-*'; - jest - .spyOn(dynamicDataView, 'useDynamicDataViewFetcher') - .mockReturnValue({ dataView: { title } }); - - it('returns result as expected', async () => { - const { waitForNextUpdate } = renderHook(() => useDataView(), { - wrapper: ({ children }) => ( - - - {children} - - - ), - }); - - await waitForNextUpdate(); - - expect(create).toBeCalledWith({ title }); - }); -}); diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/translations.ts b/x-pack/plugins/apm/public/components/app/rum_dashboard/translations.ts deleted file mode 100644 index 1720c62be3a75..0000000000000 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/translations.ts +++ /dev/null @@ -1,188 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; - -export const I18LABELS = { - dataMissing: i18n.translate('xpack.apm.rum.dashboard.dataMissing', { - defaultMessage: 'N/A', - }), - totalPageLoad: i18n.translate('xpack.apm.rum.dashboard.totalPageLoad', { - defaultMessage: 'Total', - }), - backEnd: i18n.translate('xpack.apm.rum.dashboard.backend', { - defaultMessage: 'Backend', - }), - frontEnd: i18n.translate('xpack.apm.rum.dashboard.frontend', { - defaultMessage: 'Frontend', - }), - pageViews: i18n.translate('xpack.apm.rum.dashboard.pageViews', { - defaultMessage: 'Total page views', - }), - percPageLoaded: i18n.translate('xpack.apm.rum.dashboard.pagesLoaded.label', { - defaultMessage: 'Pages loaded', - }), - pageLoadTime: i18n.translate('xpack.apm.rum.dashboard.pageLoadTime.label', { - defaultMessage: 'Page load time (seconds)', - }), - pageLoadTimes: i18n.translate('xpack.apm.rum.dashboard.pageLoadTimes.label', { - defaultMessage: 'Page load times', - }), - pageLoadDuration: i18n.translate( - 'xpack.apm.rum.dashboard.pageLoadDuration.label', - { - defaultMessage: 'Page load duration', - } - ), - pageLoad: i18n.translate('xpack.apm.rum.dashboard.pageLoad.label', { - defaultMessage: 'Page load', - }), - pageLoadDistribution: i18n.translate( - 'xpack.apm.rum.dashboard.pageLoadDistribution.label', - { - defaultMessage: 'Page load distribution', - } - ), - jsErrors: i18n.translate( - 'xpack.apm.rum.dashboard.impactfulMetrics.jsErrors', - { - defaultMessage: 'JavaScript errors', - } - ), - highTrafficPages: i18n.translate( - 'xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages', - { - defaultMessage: 'High traffic pages', - } - ), - resetZoom: i18n.translate('xpack.apm.rum.dashboard.resetZoom.label', { - defaultMessage: 'Reset zoom', - }), - overall: i18n.translate('xpack.apm.rum.dashboard.overall.label', { - defaultMessage: 'Overall', - }), - selectBreakdown: i18n.translate('xpack.apm.rum.filterGroup.selectBreakdown', { - defaultMessage: 'Select breakdown', - }), - breakdown: i18n.translate('xpack.apm.rum.filterGroup.breakdown', { - defaultMessage: 'Breakdown', - }), - seconds: i18n.translate('xpack.apm.rum.filterGroup.seconds', { - defaultMessage: 'seconds', - }), - coreWebVitals: i18n.translate('xpack.apm.rum.filterGroup.coreWebVitals', { - defaultMessage: 'Core web vitals', - }), - browser: i18n.translate('xpack.apm.rum.visitorBreakdown.browser', { - defaultMessage: 'Browser', - }), - operatingSystem: i18n.translate( - 'xpack.apm.rum.visitorBreakdown.operatingSystem', - { - defaultMessage: 'Operating system', - } - ), - metrics: i18n.translate('xpack.apm.ux.metrics', { - defaultMessage: 'Metrics', - }), - median: i18n.translate('xpack.apm.ux.median', { - defaultMessage: 'median', - }), - avgPageLoadDuration: i18n.translate( - 'xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration', - { - defaultMessage: 'Average page load duration', - } - ), - pageLoadDurationByRegion: i18n.translate( - 'xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion', - { - defaultMessage: 'Page load duration by region (avg.)', - } - ), - filterByUrl: i18n.translate('xpack.apm.rum.filters.filterByUrl', { - defaultMessage: 'Filter by URL', - }), - getSearchResultsLabel: (total: number) => - i18n.translate('xpack.apm.rum.filters.searchResults', { - defaultMessage: '{total} Search results', - values: { total }, - }), - topPages: i18n.translate('xpack.apm.rum.filters.topPages', { - defaultMessage: 'Top pages', - }), - select: i18n.translate('xpack.apm.rum.filters.select', { - defaultMessage: 'Select', - }), - url: i18n.translate('xpack.apm.rum.filters.url', { - defaultMessage: 'Url', - }), - loadingResults: i18n.translate('xpack.apm.rum.filters.url.loadingResults', { - defaultMessage: 'Loading results', - }), - noResults: i18n.translate('xpack.apm.rum.filters.url.noResults', { - defaultMessage: 'No results available', - }), - totalErrors: i18n.translate('xpack.apm.rum.jsErrors.totalErrors', { - defaultMessage: 'Total errors', - }), - errorRate: i18n.translate('xpack.apm.rum.jsErrors.errorRate', { - defaultMessage: 'Error rate', - }), - errorMessage: i18n.translate('xpack.apm.rum.jsErrors.errorMessage', { - defaultMessage: 'Error message', - }), - impactedPageLoads: i18n.translate( - 'xpack.apm.rum.jsErrors.impactedPageLoads', - { - defaultMessage: 'Impacted page loads', - } - ), - percentile: i18n.translate('xpack.apm.ux.percentile.label', { - defaultMessage: 'Percentile', - }), - percentile50thMedian: i18n.translate('xpack.apm.ux.percentile.50thMedian', { - defaultMessage: '50th (Median)', - }), - percentile75th: i18n.translate('xpack.apm.ux.percentile.75th', { - defaultMessage: '75th', - }), - percentile90th: i18n.translate('xpack.apm.ux.percentile.90th', { - defaultMessage: '90th', - }), - percentile95th: i18n.translate('xpack.apm.ux.percentile.95th', { - defaultMessage: '95th', - }), - percentile99th: i18n.translate('xpack.apm.ux.percentile.99th', { - defaultMessage: '99th', - }), - noData: i18n.translate('xpack.apm.ux.visitorBreakdown.noData', { - defaultMessage: 'No data.', - }), - // Helper tooltips - totalPageLoadTooltip: i18n.translate( - 'xpack.apm.rum.dashboard.tooltips.totalPageLoad', - { - defaultMessage: 'Total represents the full page load duration', - } - ), - frontEndTooltip: i18n.translate('xpack.apm.rum.dashboard.tooltips.frontEnd', { - defaultMessage: - 'Frontend time represents the total page load duration minus the backend time', - }), - backEndTooltip: i18n.translate('xpack.apm.rum.dashboard.tooltips.backEnd', { - defaultMessage: - 'Backend time represents time to first byte (TTFB), which is when the first response packet is received after the request has been made', - }), -}; - -export const VisitorBreakdownLabel = i18n.translate( - 'xpack.apm.rum.visitorBreakdown', - { - defaultMessage: 'Visitor breakdown', - } -); diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts index ec9f740932376..8adda7b5983f1 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_policy_form/settings_definition/apm_settings.ts @@ -78,7 +78,7 @@ export function getApmSettings({ }, { key: 'idle_timeout', - type: 'text', + type: 'duration', labelAppend: OPTIONAL_LABEL, label: i18n.translate( 'xpack.apm.fleet_integration.settings.apm.idleTimeoutLabel', @@ -91,7 +91,7 @@ export function getApmSettings({ }, { key: 'read_timeout', - type: 'text', + type: 'duration', labelAppend: OPTIONAL_LABEL, label: i18n.translate( 'xpack.apm.fleet_integration.settings.apm.readTimeoutLabel', @@ -101,7 +101,7 @@ export function getApmSettings({ }, { key: 'shutdown_timeout', - type: 'text', + type: 'duration', labelAppend: OPTIONAL_LABEL, label: i18n.translate( 'xpack.apm.fleet_integration.settings.apm.shutdownTimeoutLabel', @@ -114,7 +114,7 @@ export function getApmSettings({ }, { key: 'write_timeout', - type: 'text', + type: 'duration', labelAppend: OPTIONAL_LABEL, label: i18n.translate( 'xpack.apm.fleet_integration.settings.apm.writeTimeoutLabel', diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 20f907e03fc37..8a2d837857060 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -302,14 +302,14 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { label={i18n.translate( 'xpack.apm.serviceDetails.profilingTabExperimentalLabel', { - defaultMessage: 'Experimental', + defaultMessage: 'Technical preview', } )} tooltipContent={i18n.translate( 'xpack.apm.serviceDetails.profilingTabExperimentalDescription', { defaultMessage: - 'Profiling is highly experimental and for internal use only.', + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', } )} /> diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx index b9f935caa98c0..6052edfd9ee77 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.test.tsx @@ -110,7 +110,7 @@ describe('getAlertAnnotations', () => { setSelectedAlertId, theme, })![0].props.dataValues[0].header - ).toEqual('Alert - Experimental'); + ).toEqual('Alert - Technical preview'); }); it('uses the reason in the annotation details', () => { @@ -191,7 +191,7 @@ describe('getAlertAnnotations', () => { setSelectedAlertId, theme, })![0].props.dataValues[0].header - ).toEqual('Warning Alert - Experimental'); + ).toEqual('Warning Alert - Technical preview'); }); }); @@ -224,7 +224,7 @@ describe('getAlertAnnotations', () => { setSelectedAlertId, theme, })![0].props.dataValues[0].header - ).toEqual('Critical Alert - Experimental'); + ).toEqual('Critical Alert - Technical preview'); }); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx index ca2d0351c8135..b4cd13d4615ed 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/helper/get_alert_annotations.tsx @@ -107,7 +107,7 @@ export function getAlertAnnotations({ const color = getAlertColor({ severityLevel, theme }); const experimentalLabel = i18n.translate( 'xpack.apm.alertAnnotationTooltipExperimentalText', - { defaultMessage: 'Experimental' } + { defaultMessage: 'Technical preview' } ); const header = `${getAlertHeader({ severityLevel, diff --git a/x-pack/plugins/apm/public/components/shared/environment_filter/index.tsx b/x-pack/plugins/apm/public/components/shared/environment_filter/index.tsx index a7a8c90c31c16..64d137cae0c27 100644 --- a/x-pack/plugins/apm/public/components/shared/environment_filter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/environment_filter/index.tsx @@ -14,9 +14,7 @@ import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../../../common/environment_filter_values'; -import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { fromQuery, toQuery } from '../links/url_helpers'; -import { useUxUrlParams } from '../../../context/url_params_context/use_ux_url_params'; import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { Environment } from '../../../../common/environment_rt'; import { useEnvironmentsContext } from '../../../context/environments_context/use_environments_context'; @@ -74,26 +72,6 @@ export function ApmEnvironmentFilter() { ); } -export function UxEnvironmentFilter() { - const { - urlParams: { start, end, environment, serviceName }, - } = useUxUrlParams(); - - const { environments, status } = useEnvironmentsFetcher({ - serviceName, - start, - end, - }); - - return ( - - ); -} - export function EnvironmentFilter({ environment, environments, diff --git a/x-pack/plugins/apm/public/components/shared/links/url_helpers.ts b/x-pack/plugins/apm/public/components/shared/links/url_helpers.ts index b0cadd50b3d61..bbde45ba9cd61 100644 --- a/x-pack/plugins/apm/public/components/shared/links/url_helpers.ts +++ b/x-pack/plugins/apm/public/components/shared/links/url_helpers.ts @@ -8,7 +8,6 @@ import { History } from 'history'; import { parse, stringify } from 'query-string'; import { url } from '../../../../../../../src/plugins/kibana_utils/public'; -import { UxLocalUIFilterName } from '../../../../common/ux_ui_filter'; export function toQuery(search?: string): APMQueryParamsRaw { return search ? parse(search.slice(1), { sort: false }) : {}; @@ -64,7 +63,7 @@ export function createHref( return history.createHref(location); } -export type APMQueryParams = { +export interface APMQueryParams { sampleRangeFrom?: number; sampleRangeTo?: number; transactionId?: string; @@ -96,7 +95,7 @@ export type APMQueryParams = { podName?: string; agentName?: string; serviceVersion?: string; -} & { [key in UxLocalUIFilterName]?: string }; +} // forces every value of T[K] to be type: string type StringifyAll = { [K in keyof T]: string }; diff --git a/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx index 75cf050fcb089..3cd522037bb11 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx @@ -35,7 +35,6 @@ export function MockUrlParamsContextProvider({ rangeId: 0, refreshTimeRange, urlParams, - uxUiFilters: {}, }} children={children} /> diff --git a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts index 4311a9c75de85..eb231741ad77e 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts @@ -7,10 +7,8 @@ import { Location } from 'history'; import { TimeRangeComparisonType } from '../../../common/runtime_types/comparison_type_rt'; -import { uxLocalUIFilterNames } from '../../../common/ux_ui_filter'; import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { pickKeys } from '../../../common/utils/pick_keys'; import { toQuery } from '../../components/shared/links/url_helpers'; import { getDateRange, @@ -57,8 +55,6 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { comparisonType, } = query; - const localUIFilters = pickKeys(query, ...uxLocalUIFilterNames); - return removeUndefinedProps({ // date params ...getDateRange({ state, rangeFrom, rangeTo }), @@ -91,7 +87,5 @@ export function resolveUrlParams(location: Location, state: TimeUrlParams) { ? toBoolean(comparisonEnabled) : undefined, comparisonType: comparisonType as TimeRangeComparisonType | undefined, - // ui filters - ...localUIFilters, }); } diff --git a/x-pack/plugins/apm/public/context/url_params_context/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts index 8f167fc0ab734..aaad2fac2da22 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/types.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/types.ts @@ -7,9 +7,8 @@ import { TimeRangeComparisonType } from '../../../common/runtime_types/comparison_type_rt'; import { LatencyAggregationType } from '../../../common/latency_aggregation_types'; -import { UxLocalUIFilterName } from '../../../common/ux_ui_filter'; -export type UrlParams = { +export interface UrlParams { detailTab?: string; end?: string; flyoutDetailTab?: string; @@ -38,7 +37,7 @@ export type UrlParams = { latencyAggregationType?: LatencyAggregationType; comparisonEnabled?: boolean; comparisonType?: TimeRangeComparisonType; -} & Partial>; +} export type UxUrlParams = UrlParams; export type ApmUrlParams = Omit; diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 4d96cb074172d..a128db6c2cd7a 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { mapValues } from 'lodash'; import React, { createContext, useCallback, @@ -14,13 +13,6 @@ import React, { useState, } from 'react'; import { withRouter } from 'react-router-dom'; -import { - UxLocalUIFilterName, - uxLocalUIFilterNames, -} from '../../../common/ux_ui_filter'; -import { pickKeys } from '../../../common/utils/pick_keys'; -import { UxUIFilters } from '../../../typings/ui_filters'; -import { useDeepObjectIdentity } from '../../hooks/use_deep_object_identity'; import { getDateRange } from './helpers'; import { resolveUrlParams } from './resolve_url_params'; import { UrlParams } from './types'; @@ -30,24 +22,11 @@ export interface TimeRange { rangeTo: string; } -function useUxUiFilters(params: UrlParams): UxUIFilters { - const localUiFilters = mapValues( - pickKeys(params, ...uxLocalUIFilterNames), - (val) => (val ? val.split(',') : []) - ) as Partial>; - - return useDeepObjectIdentity({ - environment: params.environment, - ...localUiFilters, - }); -} - const defaultRefresh = (_time: TimeRange) => {}; const UrlParamsContext = createContext({ rangeId: 0, refreshTimeRange: defaultRefresh, - uxUiFilters: {} as UxUIFilters, urlParams: {} as UrlParams, }); @@ -85,16 +64,13 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter( setRangeId((prevRangeId) => prevRangeId + 1); }, []); - const uxUiFilters = useUxUiFilters(urlParams); - const contextValue = useMemo(() => { return { rangeId, refreshTimeRange, urlParams, - uxUiFilters, }; - }, [rangeId, refreshTimeRange, uxUiFilters, urlParams]); + }, [rangeId, refreshTimeRange, urlParams]); return ( @@ -102,4 +78,4 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter( } ); -export { UrlParamsContext, UrlParamsProvider, useUxUiFilters }; +export { UrlParamsContext, UrlParamsProvider }; diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index b0686f8fc28cb..952df64da840a 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -11,7 +11,6 @@ import { UsageCollectionStart } from 'src/plugins/usage_collection/public'; import type { ConfigSchema } from '.'; import { AppMountParameters, - AppNavLinkStatus, CoreSetup, CoreStart, DEFAULT_APP_CATEGORIES, @@ -36,7 +35,6 @@ import type { MapsStartApi } from '../../maps/public'; import type { MlPluginSetup, MlPluginStart } from '../../ml/public'; import { FetchDataParams, - HasDataParams, METRIC_TYPE, ObservabilityPublicSetup, ObservabilityPublicStart, @@ -152,23 +150,6 @@ export class ApmPlugin implements Plugin { { label: serviceMapTitle, app: 'apm', path: '/service-map' }, ], }, - - // UX navigation - { - label: 'User Experience', - sortKey: 600, - entries: [ - { - label: i18n.translate('xpack.apm.ux.overview.heading', { - defaultMessage: 'Dashboard', - }), - app: 'ux', - path: '/', - matchFullPath: true, - ignoreTrailingSlash: true, - }, - ], - }, ]; } @@ -236,33 +217,8 @@ export class ApmPlugin implements Plugin { }, }); - const getUxDataHelper = async () => { - const { fetchUxOverviewDate, hasRumData } = await import( - './components/app/rum_dashboard/ux_overview_fetchers' - ); - const { createCallApmApi } = await import( - './services/rest/create_call_apm_api' - ); - // have to do this here as well in case app isn't mounted yet - createCallApmApi(core); - - return { fetchUxOverviewDate, hasRumData }; - }; - const { observabilityRuleTypeRegistry } = plugins.observability; - plugins.observability.dashboard.register({ - appName: 'ux', - hasData: async (params?: HasDataParams) => { - const dataHelper = await getUxDataHelper(); - return await dataHelper.hasRumData(params!); - }, - fetchData: async (params: FetchDataParams) => { - const dataHelper = await getUxDataHelper(); - return await dataHelper.fetchUxOverviewDate(params); - }, - }); - core.application.register({ id: 'apm', title: 'APM', @@ -298,49 +254,6 @@ export class ApmPlugin implements Plugin { registerApmAlerts(observabilityRuleTypeRegistry); - core.application.register({ - id: 'ux', - title: 'User Experience', - order: 8500, - euiIconType: 'logoObservability', - category: DEFAULT_APP_CATEGORIES.observability, - navLinkStatus: config.ui.enabled - ? AppNavLinkStatus.default - : AppNavLinkStatus.hidden, - keywords: [ - 'RUM', - 'Real User Monitoring', - 'DEM', - 'Digital Experience Monitoring', - 'EUM', - 'End User Monitoring', - 'UX', - 'Javascript', - 'APM', - 'Mobile', - 'digital', - 'performance', - 'web performance', - 'web perf', - ], - async mount(appMountParameters: AppMountParameters) { - // Load application bundle and Get start service - const [{ renderApp }, [coreStart, corePlugins]] = await Promise.all([ - import('./application/ux_app'), - core.getStartServices(), - ]); - - return renderApp({ - core: coreStart, - deps: pluginSetupDeps, - appMountParameters, - config, - corePlugins: corePlugins as ApmPluginStartDeps, - observabilityRuleTypeRegistry, - }); - }, - }); - return {}; } public start(core: CoreStart, plugins: ApmPluginStartDeps) { diff --git a/x-pack/plugins/apm/public/utils/test_helpers.tsx b/x-pack/plugins/apm/public/utils/test_helpers.tsx index b503c8b78d63c..042d84a75bf76 100644 --- a/x-pack/plugins/apm/public/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/public/utils/test_helpers.tsx @@ -25,7 +25,6 @@ import { } from '../../../../../src/core/types/elasticsearch'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../server'; -import { UxUIFilters } from '../../typings/ui_filters'; import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; @@ -119,7 +118,6 @@ interface MockSetup { apmEventClient: any; internalClient: any; config: APMConfig; - uiFilters: UxUIFilters; indices: { sourcemap: string; error: string; diff --git a/x-pack/plugins/apm/server/routes/rum_client/get_page_view_trends.ts b/x-pack/plugins/apm/server/routes/rum_client/get_page_view_trends.ts index 6bcc7f66d1dfd..243349c4b4425 100644 --- a/x-pack/plugins/apm/server/routes/rum_client/get_page_view_trends.ts +++ b/x-pack/plugins/apm/server/routes/rum_client/get_page_view_trends.ts @@ -8,7 +8,13 @@ import { getRumPageLoadTransactionsProjection } from '../../projections/rum_page_load_transactions'; import { mergeProjection } from '../../projections/util/merge_projection'; import { SetupUX } from './route'; -import { BreakdownItem } from '../../../typings/ui_filters'; + +export interface BreakdownItem { + name: string; + type: string; + fieldName: string; + selected?: boolean; +} export async function getPageViewTrends({ setup, diff --git a/x-pack/plugins/apm/server/routes/rum_client/route.ts b/x-pack/plugins/apm/server/routes/rum_client/route.ts index 660f1c6afc275..e3bee6da6722c 100644 --- a/x-pack/plugins/apm/server/routes/rum_client/route.ts +++ b/x-pack/plugins/apm/server/routes/rum_client/route.ts @@ -21,8 +21,8 @@ import { getWebCoreVitals } from './get_web_core_vitals'; import { hasRumData } from './has_rum_data'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { rangeRt } from '../default_api_types'; -import { UxUIFilters } from '../../../typings/ui_filters'; import { APMRouteHandlerResources } from '../typings'; +import { UxUIFilters } from '../../../common/ux_ui_filter'; export type SetupUX = Setup & { uiFilters: UxUIFilters; diff --git a/x-pack/plugins/apm/server/routes/rum_client/ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/routes/rum_client/ui_filters/get_es_filter.ts index 99a358f33cf5e..5259cd6e1ee61 100644 --- a/x-pack/plugins/apm/server/routes/rum_client/ui_filters/get_es_filter.ts +++ b/x-pack/plugins/apm/server/routes/rum_client/ui_filters/get_es_filter.ts @@ -5,14 +5,14 @@ * 2.0. */ +import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; +import { environmentQuery } from '../../../../common/utils/environment_query'; +import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { uxLocalUIFilterNames, uxLocalUIFilters, + UxUIFilters, } from '../../../../common/ux_ui_filter'; -import { ESFilter } from '../../../../../../../src/core/types/elasticsearch'; -import { UxUIFilters } from '../../../../typings/ui_filters'; -import { environmentQuery } from '../../../../common/utils/environment_query'; -import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; export function getEsFilter(uiFilters: UxUIFilters, exclude?: boolean) { const localFilterValues = uiFilters; diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index 6ec196b2a9b8c..b9c74068b3788 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -22,7 +22,7 @@ import { APMPluginStartDependencies, } from '../types'; import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/server'; -import { UxUIFilters } from '../../typings/ui_filters'; +import { UxUIFilters } from '../../common/ux_ui_filter'; export interface ApmPluginRequestHandlerContext extends RequestHandlerContext { licensing: LicensingApiRequestHandlerContext; diff --git a/x-pack/plugins/apm/server/utils/test_helpers.tsx b/x-pack/plugins/apm/server/utils/test_helpers.tsx index aba519bb3db98..c0abf3a69623e 100644 --- a/x-pack/plugins/apm/server/utils/test_helpers.tsx +++ b/x-pack/plugins/apm/server/utils/test_helpers.tsx @@ -10,8 +10,8 @@ import { ESSearchRequest, ESSearchResponse, } from '../../../../../src/core/types/elasticsearch'; -import { UxUIFilters } from '../../typings/ui_filters'; import { ApmIndicesConfig } from '../routes/settings/apm_indices/get_apm_indices'; +import { UxUIFilters } from '../../common/ux_ui_filter'; interface Options { mockResponse?: ( diff --git a/x-pack/plugins/canvas/server/ui_settings.ts b/x-pack/plugins/canvas/server/ui_settings.ts index 8c7dc9a095872..3e7de1dbb7d79 100644 --- a/x-pack/plugins/canvas/server/ui_settings.ts +++ b/x-pack/plugins/canvas/server/ui_settings.ts @@ -21,7 +21,7 @@ export const getUISettings = (): Record> => ({ }), description: i18n.translate('xpack.canvas.labs.enableLabsDescription', { defaultMessage: - 'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable experimental features in Canvas.', + 'This flag determines if the viewer has access to the Labs button, a quick way to enable and disable technical preview features in Canvas.', }), value: false, type: 'boolean', diff --git a/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx b/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx index e1dfc71867f6e..47e6ec259640c 100644 --- a/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx +++ b/x-pack/plugins/cases/public/components/empty_value/empty_value.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { defaultToEmptyTag, diff --git a/x-pack/plugins/cases/server/authorization/audit_logger.test.ts b/x-pack/plugins/cases/server/authorization/audit_logger.test.ts index c2f00e8cfff05..3c465a6f843c0 100644 --- a/x-pack/plugins/cases/server/authorization/audit_logger.test.ts +++ b/x-pack/plugins/cases/server/authorization/audit_logger.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AuditLogger } from '../../../../plugins/security/server'; +import { auditLoggerMock } from '../../../../plugins/security/server/audit/mocks'; import { Operations } from '.'; import { AuthorizationAuditLogger } from './audit_logger'; import { ReadOperations } from './types'; @@ -30,11 +30,7 @@ describe('audit_logger', () => { }); describe('log function', () => { - const mockLogger: jest.Mocked = { - log: jest.fn(), - enabled: true, - }; - + const mockLogger = auditLoggerMock.create(); let logger: AuthorizationAuditLogger; beforeEach(() => { diff --git a/x-pack/plugins/cases/server/authorization/authorization.test.ts b/x-pack/plugins/cases/server/authorization/authorization.test.ts index 693277161c330..650119bab0079 100644 --- a/x-pack/plugins/cases/server/authorization/authorization.test.ts +++ b/x-pack/plugins/cases/server/authorization/authorization.test.ts @@ -14,6 +14,7 @@ import { AuthorizationAuditLogger } from './audit_logger'; import { KibanaRequest } from 'kibana/server'; import { KibanaFeature } from '../../../../plugins/features/common'; import { AuditLogger, SecurityPluginStart } from '../../../security/server'; +import { auditLoggerMock } from '../../../security/server/audit/mocks'; import { PluginStartContract as FeaturesPluginStart } from '../../../features/server'; describe('authorization', () => { @@ -22,10 +23,7 @@ describe('authorization', () => { beforeEach(() => { request = httpServerMock.createKibanaRequest(); - mockLogger = { - log: jest.fn(), - enabled: true, - }; + mockLogger = auditLoggerMock.create(); }); describe('create', () => { diff --git a/x-pack/plugins/cases/server/client/attachments/add.ts b/x-pack/plugins/cases/server/client/attachments/add.ts index ca03381681796..c0999b25ebf7d 100644 --- a/x-pack/plugins/cases/server/client/attachments/add.ts +++ b/x-pack/plugins/cases/server/client/attachments/add.ts @@ -53,7 +53,6 @@ async function createCommentableCase({ lensEmbeddableFactory: LensServerPluginSetup['lensEmbeddableFactory']; }): Promise { const caseInfo = await caseService.getCase({ - unsecuredSavedObjectsClient, id, }); diff --git a/x-pack/plugins/cases/server/client/attachments/delete.ts b/x-pack/plugins/cases/server/client/attachments/delete.ts index 9d048162da7eb..6071570029d63 100644 --- a/x-pack/plugins/cases/server/client/attachments/delete.ts +++ b/x-pack/plugins/cases/server/client/attachments/delete.ts @@ -60,7 +60,6 @@ export async function deleteAll( try { const comments = await caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id: caseID, }); diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts index b9ac0f2eea000..4cd620ac5a772 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.ts +++ b/x-pack/plugins/cases/server/client/attachments/get.ts @@ -250,7 +250,7 @@ export async function getAll( { caseID }: GetAllArgs, clientArgs: CasesClientArgs ): Promise { - const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs; + const { caseService, logger, authorization } = clientArgs; try { const { filter, ensureSavedObjectsAreAuthorized } = await authorization.getAuthorizationFilter( @@ -258,7 +258,6 @@ export async function getAll( ); const comments = await caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id: caseID, options: { filter, diff --git a/x-pack/plugins/cases/server/client/attachments/update.ts b/x-pack/plugins/cases/server/client/attachments/update.ts index 1928057f17edf..bd91827a9c852 100644 --- a/x-pack/plugins/cases/server/client/attachments/update.ts +++ b/x-pack/plugins/cases/server/client/attachments/update.ts @@ -50,7 +50,6 @@ async function createCommentableCase({ lensEmbeddableFactory, }: CombinedCaseParams) { const caseInfo = await caseService.getCase({ - unsecuredSavedObjectsClient, id: caseID, }); diff --git a/x-pack/plugins/cases/server/client/cases/create.ts b/x-pack/plugins/cases/server/client/cases/create.ts index 30580ed493b63..23b295602c213 100644 --- a/x-pack/plugins/cases/server/client/cases/create.ts +++ b/x-pack/plugins/cases/server/client/cases/create.ts @@ -73,7 +73,6 @@ export const create = async ( }); const newCase = await caseService.postNewCase({ - unsecuredSavedObjectsClient, attributes: transformNewCase({ user, newCase: query, diff --git a/x-pack/plugins/cases/server/client/cases/delete.ts b/x-pack/plugins/cases/server/client/cases/delete.ts index d1c12219f2ef2..8d0a97079f1cc 100644 --- a/x-pack/plugins/cases/server/client/cases/delete.ts +++ b/x-pack/plugins/cases/server/client/cases/delete.ts @@ -30,7 +30,7 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P authorization, } = clientArgs; try { - const cases = await caseService.getCases({ unsecuredSavedObjectsClient, caseIds: ids }); + const cases = await caseService.getCases({ caseIds: ids }); const entities = new Map(); for (const theCase of cases.saved_objects) { @@ -52,7 +52,6 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P const deleteCasesMapper = async (id: string) => caseService.deleteCase({ - unsecuredSavedObjectsClient, id, }); @@ -63,7 +62,6 @@ export async function deleteCases(ids: string[], clientArgs: CasesClientArgs): P const getCommentsMapper = async (id: string) => caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id, }); diff --git a/x-pack/plugins/cases/server/client/cases/find.ts b/x-pack/plugins/cases/server/client/cases/find.ts index 3e4bc47231d12..c8bdb40b41310 100644 --- a/x-pack/plugins/cases/server/client/cases/find.ts +++ b/x-pack/plugins/cases/server/client/cases/find.ts @@ -15,13 +15,12 @@ import { CasesFindRequest, CasesFindRequestRt, throwErrors, - caseStatuses, CasesFindResponseRt, excess, } from '../../../common/api'; import { createCaseError } from '../../common/error'; -import { transformCases } from '../../common/utils'; +import { asArray, transformCases } from '../../common/utils'; import { constructQueryOptions } from '../utils'; import { includeFieldsRequiredForAuthentication } from '../../authorization/utils'; import { Operations } from '../../authorization'; @@ -36,7 +35,7 @@ export const find = async ( params: CasesFindRequest, clientArgs: CasesClientArgs ): Promise => { - const { unsecuredSavedObjectsClient, caseService, authorization, logger } = clientArgs; + const { caseService, authorization, logger } = clientArgs; try { const queryParams = pipe( @@ -55,45 +54,38 @@ export const find = async ( owner: queryParams.owner, }; - const caseQueries = constructQueryOptions({ ...queryArgs, authorizationFilter }); - const cases = await caseService.findCasesGroupedByID({ - unsecuredSavedObjectsClient, - caseOptions: { - ...queryParams, - ...caseQueries, - searchFields: - queryParams.searchFields != null - ? Array.isArray(queryParams.searchFields) - ? queryParams.searchFields - : [queryParams.searchFields] - : queryParams.searchFields, - fields: includeFieldsRequiredForAuthentication(queryParams.fields), - }, + const statusStatsOptions = constructQueryOptions({ + ...queryArgs, + status: undefined, + authorizationFilter, }); + const caseQueryOptions = constructQueryOptions({ ...queryArgs, authorizationFilter }); - ensureSavedObjectsAreAuthorized([...cases.casesMap.values()]); - - // casesStatuses are bounded by us. No need to limit concurrent calls. - const [openCases, inProgressCases, closedCases] = await Promise.all([ - ...caseStatuses.map((status) => { - const statusQuery = constructQueryOptions({ ...queryArgs, status, authorizationFilter }); - return caseService.findCaseStatusStats({ - unsecuredSavedObjectsClient, - caseOptions: statusQuery, - ensureSavedObjectsAreAuthorized, - }); + const [cases, statusStats] = await Promise.all([ + caseService.findCasesGroupedByID({ + caseOptions: { + ...queryParams, + ...caseQueryOptions, + searchFields: asArray(queryParams.searchFields), + fields: includeFieldsRequiredForAuthentication(queryParams.fields), + }, + }), + caseService.getCaseStatusStats({ + searchOptions: statusStatsOptions, }), ]); + ensureSavedObjectsAreAuthorized([...cases.casesMap.values()]); + return CasesFindResponseRt.encode( transformCases({ casesMap: cases.casesMap, page: cases.page, perPage: cases.perPage, total: cases.total, - countOpenCases: openCases, - countInProgressCases: inProgressCases, - countClosedCases: closedCases, + countOpenCases: statusStats.open, + countInProgressCases: statusStats['in-progress'], + countClosedCases: statusStats.closed, }) ); } catch (error) { diff --git a/x-pack/plugins/cases/server/client/cases/get.ts b/x-pack/plugins/cases/server/client/cases/get.ts index 72d5ca2708d28..76f0667023e54 100644 --- a/x-pack/plugins/cases/server/client/cases/get.ts +++ b/x-pack/plugins/cases/server/client/cases/get.ts @@ -59,7 +59,7 @@ export const getCasesByAlertID = async ( { alertID, options }: CasesByAlertIDParams, clientArgs: CasesClientArgs ): Promise => { - const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs; + const { caseService, logger, authorization } = clientArgs; try { const queryParams = pipe( @@ -79,7 +79,6 @@ export const getCasesByAlertID = async ( // This will likely only return one comment saved object, the response aggregation will contain // the keys we need to retrieve the cases const commentsWithAlert = await caseService.getCaseIdsByAlertId({ - unsecuredSavedObjectsClient, alertId: alertID, filter, }); @@ -100,7 +99,6 @@ export const getCasesByAlertID = async ( } const casesInfo = await caseService.getCases({ - unsecuredSavedObjectsClient, caseIds, }); @@ -157,11 +155,10 @@ export const get = async ( { id, includeComments }: GetParams, clientArgs: CasesClientArgs ): Promise => { - const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs; + const { caseService, logger, authorization } = clientArgs; try { const theCase: SavedObject = await caseService.getCase({ - unsecuredSavedObjectsClient, id, }); @@ -179,7 +176,6 @@ export const get = async ( } const theComments = await caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id, options: { sortField: 'created_at', @@ -209,14 +205,13 @@ export const resolve = async ( { id, includeComments }: GetParams, clientArgs: CasesClientArgs ): Promise => { - const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs; + const { caseService, logger, authorization } = clientArgs; try { const { saved_object: resolvedSavedObject, ...resolveData }: SavedObjectsResolveResponse = await caseService.getResolveCase({ - unsecuredSavedObjectsClient, id, }); @@ -240,7 +235,6 @@ export const resolve = async ( } const theComments = await caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id: resolvedSavedObject.id, options: { sortField: 'created_at', diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index e68c67951d571..112fd6ef2c04c 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -142,12 +142,10 @@ export const push = async ( /* Start of update case with push information */ const [myCase, myCaseConfigure, comments] = await Promise.all([ caseService.getCase({ - unsecuredSavedObjectsClient, id: caseId, }), caseConfigureService.find({ unsecuredSavedObjectsClient }), caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id: caseId, options: { fields: [], @@ -177,7 +175,6 @@ export const push = async ( const [updatedCase, updatedComments] = await Promise.all([ caseService.patchCase({ originalCase: myCase, - unsecuredSavedObjectsClient, caseId, updatedAttributes: { ...(shouldMarkAsClosed diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index fa8319d37efd8..184810098bde4 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -97,17 +97,14 @@ function getID( async function getAlertComments({ casesToSync, caseService, - unsecuredSavedObjectsClient, }: { casesToSync: UpdateRequestWithOriginalCase[]; caseService: CasesService; - unsecuredSavedObjectsClient: SavedObjectsClientContract; }): Promise> { const idsOfCasesToSync = casesToSync.map(({ updateReq }) => updateReq.id); // getAllCaseComments will by default get all the comments, unless page or perPage fields are set return caseService.getAllCaseComments({ - unsecuredSavedObjectsClient, id: idsOfCasesToSync, options: { filter: nodeBuilder.is(`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`, CommentType.alert), @@ -166,7 +163,6 @@ async function updateAlerts({ const totalAlerts = await getAlertComments({ casesToSync, caseService, - unsecuredSavedObjectsClient, }); // create an array of requests that indicate the id, index, and status to update an alert @@ -253,7 +249,6 @@ export const update = async ( try { const myCases = await caseService.getCases({ - unsecuredSavedObjectsClient, caseIds: query.cases.map((q) => q.id), }); @@ -320,7 +315,6 @@ export const update = async ( const { username, full_name, email } = user; const updatedDt = new Date().toISOString(); const updatedCases = await caseService.patchCases({ - unsecuredSavedObjectsClient, cases: updateCases.map(({ updateReq, originalCase }) => { // intentionally removing owner from the case so that we don't accidentally allow it to be updated const { id: caseId, version, owner, ...updateCaseAttributes } = updateReq; diff --git a/x-pack/plugins/cases/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts index fb111f267ddac..266c988212cdf 100644 --- a/x-pack/plugins/cases/server/client/client.ts +++ b/x-pack/plugins/cases/server/client/client.ts @@ -11,7 +11,6 @@ import { AttachmentsSubClient, createAttachmentsSubClient } from './attachments/ import { UserActionsSubClient, createUserActionsSubClient } from './user_actions/client'; import { CasesClientInternal, createCasesClientInternal } from './client_internal'; import { ConfigureSubClient, createConfigurationSubClient } from './configure/client'; -import { createStatsSubClient, StatsSubClient } from './stats/client'; import { createMetricsSubClient, MetricsSubClient } from './metrics/client'; /** @@ -23,7 +22,6 @@ export class CasesClient { private readonly _attachments: AttachmentsSubClient; private readonly _userActions: UserActionsSubClient; private readonly _configure: ConfigureSubClient; - private readonly _stats: StatsSubClient; private readonly _metrics: MetricsSubClient; constructor(args: CasesClientArgs) { @@ -32,7 +30,6 @@ export class CasesClient { this._attachments = createAttachmentsSubClient(args, this, this._casesClientInternal); this._userActions = createUserActionsSubClient(args); this._configure = createConfigurationSubClient(args, this._casesClientInternal); - this._stats = createStatsSubClient(args); this._metrics = createMetricsSubClient(args, this); } @@ -64,13 +61,6 @@ export class CasesClient { return this._configure; } - /** - * Retrieves an interface for retrieving statistics related to the cases entities. - */ - public get stats() { - return this._stats; - } - /** * Retrieves an interface for retrieving metrics related to the cases entities. */ diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index d657f1a3f4f48..3cbcee62d8c09 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -91,17 +91,25 @@ export class CasesClientFactory { logger: this.logger, }); - const caseService = new CasesService(this.logger, this.options?.securityPluginStart?.authc); + const unsecuredSavedObjectsClient = savedObjectsService.getScopedClient(request, { + includedHiddenTypes: SAVED_OBJECT_TYPES, + // this tells the security plugin to not perform SO authorization and audit logging since we are handling + // that manually using our Authorization class and audit logger. + excludedWrappers: ['security'], + }); + + const attachmentService = new AttachmentService(this.logger); + const caseService = new CasesService({ + log: this.logger, + authentication: this.options?.securityPluginStart?.authc, + unsecuredSavedObjectsClient, + attachmentService, + }); const userInfo = caseService.getUser({ request }); return createCasesClient({ alertsService: new AlertService(scopedClusterClient, this.logger), - unsecuredSavedObjectsClient: savedObjectsService.getScopedClient(request, { - includedHiddenTypes: SAVED_OBJECT_TYPES, - // this tells the security plugin to not perform SO authorization and audit logging since we are handling - // that manually using our Authorization class and audit logger. - excludedWrappers: ['security'], - }), + unsecuredSavedObjectsClient, // We only want these fields from the userInfo object user: { username: userInfo.username, email: userInfo.email, full_name: userInfo.full_name }, caseService, diff --git a/x-pack/plugins/cases/server/client/metrics/client.ts b/x-pack/plugins/cases/server/client/metrics/client.ts index c5420213f3f97..8fbb30486bc41 100644 --- a/x-pack/plugins/cases/server/client/metrics/client.ts +++ b/x-pack/plugins/cases/server/client/metrics/client.ts @@ -5,10 +5,11 @@ * 2.0. */ -import { CaseMetricsResponse } from '../../../common/api'; +import { CaseMetricsResponse, CasesStatusRequest, CasesStatusResponse } from '../../../common/api'; import { CasesClient } from '../client'; import { CasesClientArgs } from '../types'; +import { getStatusTotalsByType } from './get_cases_metrics'; import { getCaseMetrics, CaseMetricsParams } from './get_case_metrics'; @@ -17,6 +18,10 @@ import { getCaseMetrics, CaseMetricsParams } from './get_case_metrics'; */ export interface MetricsSubClient { getCaseMetrics(params: CaseMetricsParams): Promise; + /** + * Retrieves the total number of open, closed, and in-progress cases. + */ + getStatusTotalsByType(params: CasesStatusRequest): Promise; } /** @@ -30,6 +35,8 @@ export const createMetricsSubClient = ( ): MetricsSubClient => { const casesSubClient: MetricsSubClient = { getCaseMetrics: (params: CaseMetricsParams) => getCaseMetrics(params, casesClient, clientArgs), + getStatusTotalsByType: (params: CasesStatusRequest) => + getStatusTotalsByType(params, clientArgs), }; return Object.freeze(casesSubClient); diff --git a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts index 57755c17e65eb..d2ce8c03edeb7 100644 --- a/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts +++ b/x-pack/plugins/cases/server/client/metrics/get_case_metrics.ts @@ -105,10 +105,9 @@ const checkAndThrowIfInvalidFeatures = ( }; const checkAuthorization = async (params: CaseMetricsParams, clientArgs: CasesClientArgs) => { - const { caseService, unsecuredSavedObjectsClient, authorization } = clientArgs; + const { caseService, authorization } = clientArgs; const caseInfo = await caseService.getCase({ - unsecuredSavedObjectsClient, id: params.caseId, }); diff --git a/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts new file mode 100644 index 0000000000000..82c3a52a10d63 --- /dev/null +++ b/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Boom from '@hapi/boom'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; + +import { + CasesStatusRequest, + CasesStatusResponse, + excess, + CasesStatusRequestRt, + throwErrors, + CasesStatusResponseRt, +} from '../../../common/api'; +import { CasesClientArgs } from '../types'; +import { Operations } from '../../authorization'; +import { constructQueryOptions } from '../utils'; +import { createCaseError } from '../../common/error'; + +export async function getStatusTotalsByType( + params: CasesStatusRequest, + clientArgs: CasesClientArgs +): Promise { + const { caseService, logger, authorization } = clientArgs; + + try { + const queryParams = pipe( + excess(CasesStatusRequestRt).decode(params), + fold(throwErrors(Boom.badRequest), identity) + ); + + const { filter: authorizationFilter } = await authorization.getAuthorizationFilter( + Operations.getCaseStatuses + ); + + const options = constructQueryOptions({ + owner: queryParams.owner, + authorizationFilter, + }); + + const statusStats = await caseService.getCaseStatusStats({ + searchOptions: options, + }); + + return CasesStatusResponseRt.encode({ + count_open_cases: statusStats.open, + count_in_progress_cases: statusStats['in-progress'], + count_closed_cases: statusStats.closed, + }); + } catch (error) { + throw createCaseError({ message: `Failed to get status stats: ${error}`, error, logger }); + } +} diff --git a/x-pack/plugins/cases/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts index 1d0cf000018cb..ecedc7cb05071 100644 --- a/x-pack/plugins/cases/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -13,7 +13,6 @@ import { CasesSubClient } from './cases/client'; import { ConfigureSubClient } from './configure/client'; import { CasesClientFactory } from './factory'; import { MetricsSubClient } from './metrics/client'; -import { StatsSubClient } from './stats/client'; import { UserActionsSubClient } from './user_actions/client'; type CasesSubClientMock = jest.Mocked; @@ -38,6 +37,7 @@ type MetricsSubClientMock = jest.Mocked; const createMetricsSubClientMock = (): MetricsSubClientMock => { return { getCaseMetrics: jest.fn(), + getStatusTotalsByType: jest.fn(), }; }; @@ -75,14 +75,6 @@ const createConfigureSubClientMock = (): ConfigureSubClientMock => { }; }; -type StatsSubClientMock = jest.Mocked; - -const createStatsSubClientMock = (): StatsSubClientMock => { - return { - getStatusTotalsByType: jest.fn(), - }; -}; - export interface CasesClientMock extends CasesClient { cases: CasesSubClientMock; attachments: AttachmentsSubClientMock; @@ -95,7 +87,6 @@ export const createCasesClientMock = (): CasesClientMock => { attachments: createAttachmentsSubClientMock(), userActions: createUserActionsSubClientMock(), configure: createConfigureSubClientMock(), - stats: createStatsSubClientMock(), metrics: createMetricsSubClientMock(), }; return client as unknown as CasesClientMock; diff --git a/x-pack/plugins/cases/server/client/stats/client.ts b/x-pack/plugins/cases/server/client/stats/client.ts deleted file mode 100644 index 6cb945e0fead1..0000000000000 --- a/x-pack/plugins/cases/server/client/stats/client.ts +++ /dev/null @@ -1,88 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Boom from '@hapi/boom'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; - -import { CasesClientArgs } from '..'; -import { - CasesStatusRequest, - CasesStatusResponse, - CasesStatusResponseRt, - caseStatuses, - throwErrors, - excess, - CasesStatusRequestRt, -} from '../../../common/api'; -import { Operations } from '../../authorization'; -import { createCaseError } from '../../common/error'; -import { constructQueryOptions } from '../utils'; - -/** - * Statistics API contract. - */ -export interface StatsSubClient { - /** - * Retrieves the total number of open, closed, and in-progress cases. - */ - getStatusTotalsByType(params: CasesStatusRequest): Promise; -} - -/** - * Creates the interface for retrieving the number of open, closed, and in progress cases. - * - * @ignore - */ -export function createStatsSubClient(clientArgs: CasesClientArgs): StatsSubClient { - return Object.freeze({ - getStatusTotalsByType: (params: CasesStatusRequest) => - getStatusTotalsByType(params, clientArgs), - }); -} - -async function getStatusTotalsByType( - params: CasesStatusRequest, - clientArgs: CasesClientArgs -): Promise { - const { unsecuredSavedObjectsClient, caseService, logger, authorization } = clientArgs; - - try { - const queryParams = pipe( - excess(CasesStatusRequestRt).decode(params), - fold(throwErrors(Boom.badRequest), identity) - ); - - const { filter: authorizationFilter, ensureSavedObjectsAreAuthorized } = - await authorization.getAuthorizationFilter(Operations.getCaseStatuses); - - // casesStatuses are bounded by us. No need to limit concurrent calls. - const [openCases, inProgressCases, closedCases] = await Promise.all([ - ...caseStatuses.map((status) => { - const statusQuery = constructQueryOptions({ - owner: queryParams.owner, - status, - authorizationFilter, - }); - return caseService.findCaseStatusStats({ - unsecuredSavedObjectsClient, - caseOptions: statusQuery, - ensureSavedObjectsAreAuthorized, - }); - }), - ]); - - return CasesStatusResponseRt.encode({ - count_open_cases: openCases, - count_in_progress_cases: inProgressCases, - count_closed_cases: closedCases, - }); - } catch (error) { - throw createCaseError({ message: `Failed to get status stats: ${error}`, error, logger }); - } -} diff --git a/x-pack/plugins/cases/server/common/models/commentable_case.ts b/x-pack/plugins/cases/server/common/models/commentable_case.ts index 4afb427dea1f8..a56e55670ec83 100644 --- a/x-pack/plugins/cases/server/common/models/commentable_case.ts +++ b/x-pack/plugins/cases/server/common/models/commentable_case.ts @@ -118,7 +118,6 @@ export class CommentableCase { try { const updatedCase = await this.caseService.patchCase({ originalCase: this.caseInfo, - unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, caseId: this.caseInfo.id, updatedAttributes: { updated_at: date, @@ -282,7 +281,6 @@ export class CommentableCase { public async encode(): Promise { try { const comments = await this.caseService.getAllCaseComments({ - unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, id: this.caseInfo.id, options: { fields: [], diff --git a/x-pack/plugins/cases/server/common/utils.test.ts b/x-pack/plugins/cases/server/common/utils.test.ts index 314f986f831ae..0479dc73ff6b6 100644 --- a/x-pack/plugins/cases/server/common/utils.test.ts +++ b/x-pack/plugins/cases/server/common/utils.test.ts @@ -28,6 +28,7 @@ import { flattenCommentSavedObject, extractLensReferencesFromCommentString, getOrUpdateLensReferences, + asArray, } from './utils'; interface CommentReference { @@ -822,7 +823,7 @@ describe('common utils', () => { ].join('\n\n'); const extractedReferences = extractLensReferencesFromCommentString( - makeLensEmbeddableFactory(() => ({})), + makeLensEmbeddableFactory(() => ({}), {}), commentString ); @@ -921,7 +922,7 @@ describe('common utils', () => { ].join('\n\n'); const updatedReferences = getOrUpdateLensReferences( - makeLensEmbeddableFactory(() => ({})), + makeLensEmbeddableFactory(() => ({}), {}), newCommentString, { references: currentCommentReferences, @@ -940,4 +941,26 @@ describe('common utils', () => { expect(expectedReferences).toEqual(expect.arrayContaining(updatedReferences)); }); }); + + describe('asArray', () => { + it('returns an empty array when the field is undefined', () => { + expect(asArray(undefined)).toEqual([]); + }); + + it('returns an empty array when the field is null', () => { + expect(asArray(null)).toEqual([]); + }); + + it('leaves the string array as is when it is already an array', () => { + expect(asArray(['value'])).toEqual(['value']); + }); + + it('returns an array of one item when passed a string', () => { + expect(asArray('value')).toEqual(['value']); + }); + + it('returns an array of one item when passed a number', () => { + expect(asArray(100)).toEqual([100]); + }); + }); }); diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index ef04136b0b4a1..8bbf247b24773 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -360,3 +360,11 @@ export const getOrUpdateLensReferences = ( return currentNonLensReferences.concat(newCommentLensReferences); }; + +export const asArray = (field?: T | T[] | null): T[] => { + if (field === undefined || field === null) { + return []; + } + + return Array.isArray(field) ? field : [field]; +}; diff --git a/x-pack/plugins/cases/server/routes/api/stats/get_status.ts b/x-pack/plugins/cases/server/routes/api/stats/get_status.ts index 4f666c399d8fd..ffbc101a75dc4 100644 --- a/x-pack/plugins/cases/server/routes/api/stats/get_status.ts +++ b/x-pack/plugins/cases/server/routes/api/stats/get_status.ts @@ -21,7 +21,7 @@ export function initGetCasesStatusApi({ router, logger }: RouteDeps) { try { const client = await context.cases.getCasesClient(); return response.ok({ - body: await client.stats.getStatusTotalsByType(request.query as CasesStatusRequest), + body: await client.metrics.getStatusTotalsByType(request.query as CasesStatusRequest), }); } catch (error) { logger.error(`Failed to get status stats in route: ${error}`); diff --git a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts index bd48641975247..837a1e4f10d70 100644 --- a/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts +++ b/x-pack/plugins/cases/server/saved_object_types/migrations/comments.test.ts @@ -36,7 +36,7 @@ import { GENERATED_ALERT, SUB_CASE_SAVED_OBJECT } from './constants'; describe('comments migrations', () => { const migrations = createCommentsMigrations({ - lensEmbeddableFactory: makeLensEmbeddableFactory(() => ({})), + lensEmbeddableFactory: makeLensEmbeddableFactory(() => ({}), {}), }); const contextMock = savedObjectsServiceMock.createMigrationContext(); diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 7444159a00bb0..7f966a471c725 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -42,6 +42,7 @@ export class AlertService { const res = await this.scopedClusterClient.search({ index: indices, + ignore_unavailable: true, query: { ids: { values: ids } }, size: 0, aggregations: builtAggs, diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index eb09ddac95dd8..5b3ee796faf96 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -9,6 +9,7 @@ import { Logger, SavedObject, SavedObjectReference, + SavedObjectsClientContract, SavedObjectsUpdateOptions, } from 'kibana/server'; @@ -62,6 +63,11 @@ interface BulkUpdateAttachmentArgs extends ClientArgs { comments: UpdateArgs[]; } +interface CommentStats { + nonAlerts: number; + alerts: number; +} + export class AttachmentService { constructor(private readonly log: Logger) {} @@ -279,4 +285,104 @@ export class AttachmentService { throw error; } } + + public async getCaseCommentStats({ + unsecuredSavedObjectsClient, + caseIds, + }: { + unsecuredSavedObjectsClient: SavedObjectsClientContract; + caseIds: string[]; + }): Promise> { + if (caseIds.length <= 0) { + return new Map(); + } + + interface AggsResult { + references: { + caseIds: { + buckets: Array<{ + key: string; + doc_count: number; + reverse: { + comments: { + buckets: { + alerts: { + doc_count: number; + }; + nonAlerts: { + doc_count: number; + }; + }; + }; + }; + }>; + }; + }; + } + + const res = await unsecuredSavedObjectsClient.find({ + hasReference: caseIds.map((id) => ({ type: CASE_SAVED_OBJECT, id })), + hasReferenceOperator: 'OR', + type: CASE_COMMENT_SAVED_OBJECT, + perPage: 0, + aggs: AttachmentService.buildCommentStatsAggs(caseIds), + }); + + return ( + res.aggregations?.references.caseIds.buckets.reduce((acc, idBucket) => { + acc.set(idBucket.key, { + nonAlerts: idBucket.reverse.comments.buckets.nonAlerts.doc_count, + alerts: idBucket.reverse.comments.buckets.alerts.doc_count, + }); + return acc; + }, new Map()) ?? new Map() + ); + } + + private static buildCommentStatsAggs( + ids: string[] + ): Record { + return { + references: { + nested: { + path: `${CASE_COMMENT_SAVED_OBJECT}.references`, + }, + aggregations: { + caseIds: { + terms: { + field: `${CASE_COMMENT_SAVED_OBJECT}.references.id`, + size: ids.length, + }, + aggregations: { + reverse: { + reverse_nested: {}, + aggregations: { + comments: { + filters: { + filters: { + alerts: { + term: { + [`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`]: CommentType.alert, + }, + }, + nonAlerts: { + bool: { + must_not: { + term: { + [`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`]: CommentType.alert, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + } } diff --git a/x-pack/plugins/cases/server/services/cases/index.test.ts b/x-pack/plugins/cases/server/services/cases/index.test.ts index 10026682e68c8..5ed2d2978f154 100644 --- a/x-pack/plugins/cases/server/services/cases/index.test.ts +++ b/x-pack/plugins/cases/server/services/cases/index.test.ts @@ -40,6 +40,7 @@ import { createSOFindResponse, } from '../test_utils'; import { ESCaseAttributes } from './types'; +import { AttachmentService } from '../attachments'; const createUpdateSOResponse = ({ connector, @@ -117,12 +118,17 @@ const createCasePatchParams = ({ describe('CasesService', () => { const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const mockLogger = loggerMock.create(); + const attachmentService = new AttachmentService(mockLogger); let service: CasesService; beforeEach(() => { jest.resetAllMocks(); - service = new CasesService(mockLogger); + service = new CasesService({ + log: mockLogger, + unsecuredSavedObjectsClient, + attachmentService, + }); }); describe('transforms the external model to the Elasticsearch model', () => { @@ -134,7 +140,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()), originalCase: {} as SavedObject, }); @@ -181,7 +186,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()), originalCase: {} as SavedObject, }); @@ -213,7 +217,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()), originalCase: {} as SavedObject, }); @@ -249,7 +252,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(createJiraConnector()), originalCase: {} as SavedObject, }); @@ -278,7 +280,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePostParams(getNoneCaseConnector(), createExternalService()), originalCase: {} as SavedObject, }); @@ -307,7 +308,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePostParams(createJiraConnector(), createExternalService()), originalCase: { references: [{ id: 'a', name: 'awesome', type: 'hello' }], @@ -344,7 +344,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePatchParams({ externalService: createExternalService() }), originalCase: { references: [ @@ -378,7 +377,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCasePostParams(getNoneCaseConnector(), createExternalService()), originalCase: {} as SavedObject, }); @@ -408,7 +406,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -428,7 +425,6 @@ describe('CasesService', () => { await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(getNoneCaseConnector()), originalCase: {} as SavedObject, }); @@ -452,7 +448,6 @@ describe('CasesService', () => { ); await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams(createJiraConnector()), id: '1', }); @@ -468,7 +463,6 @@ describe('CasesService', () => { ); await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams(createJiraConnector(), createExternalService()), id: '1', }); @@ -560,7 +554,6 @@ describe('CasesService', () => { ); await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams(createJiraConnector(), createExternalService()), id: '1', }); @@ -589,7 +582,6 @@ describe('CasesService', () => { ); await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams( createJiraConnector({ setFieldsToNull: true }), createExternalService() @@ -608,7 +600,6 @@ describe('CasesService', () => { ); await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams(getNoneCaseConnector()), id: '1', }); @@ -624,7 +615,6 @@ describe('CasesService', () => { ); await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams(getNoneCaseConnector()), id: '1', }); @@ -655,7 +645,6 @@ describe('CasesService', () => { ); const res = await service.patchCases({ - unsecuredSavedObjectsClient, cases: [ { caseId: '1', @@ -710,7 +699,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -735,7 +723,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -755,7 +742,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -771,7 +757,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -803,7 +788,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -834,7 +818,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -858,7 +841,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -895,7 +877,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -922,7 +903,6 @@ describe('CasesService', () => { const res = await service.patchCase({ caseId: '1', - unsecuredSavedObjectsClient, updatedAttributes: createCaseUpdateParams(), originalCase: {} as SavedObject, }); @@ -958,7 +938,6 @@ describe('CasesService', () => { ); const res = await service.postNewCase({ - unsecuredSavedObjectsClient, attributes: createCasePostParams(getNoneCaseConnector()), id: '1', }); @@ -979,7 +958,7 @@ describe('CasesService', () => { ]); unsecuredSavedObjectsClient.find.mockReturnValue(Promise.resolve(findMockReturn)); - const res = await service.findCases({ unsecuredSavedObjectsClient }); + const res = await service.findCases(); expect(res.saved_objects[0].attributes.connector.id).toMatchInlineSnapshot(`"1"`); expect( res.saved_objects[0].attributes.external_service?.connector_id @@ -996,7 +975,7 @@ describe('CasesService', () => { ]); unsecuredSavedObjectsClient.find.mockReturnValue(Promise.resolve(findMockReturn)); - const res = await service.findCases({ unsecuredSavedObjectsClient }); + const res = await service.findCases(); const { saved_objects: ignored, ...findResponseFields } = res; expect(findResponseFields).toMatchInlineSnapshot(` Object { @@ -1025,7 +1004,7 @@ describe('CasesService', () => { }) ); - const res = await service.getCases({ unsecuredSavedObjectsClient, caseIds: ['a'] }); + const res = await service.getCases({ caseIds: ['a'] }); expect(res.saved_objects[0].attributes.connector.id).toMatchInlineSnapshot(`"1"`); expect( @@ -1050,7 +1029,7 @@ describe('CasesService', () => { ) ); - const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' }); + const res = await service.getCase({ id: 'a' }); expect(res.attributes.connector.id).toMatchInlineSnapshot(`"1"`); expect(res.attributes.external_service?.connector_id).toMatchInlineSnapshot(`"100"`); @@ -1062,7 +1041,7 @@ describe('CasesService', () => { createCaseSavedObjectResponse({ externalService: createExternalService() }) ) ); - const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' }); + const res = await service.getCase({ id: 'a' }); expect(res.attributes.connector).toMatchInlineSnapshot(` Object { @@ -1078,7 +1057,7 @@ describe('CasesService', () => { unsecuredSavedObjectsClient.get.mockReturnValue( Promise.resolve(createCaseSavedObjectResponse()) ); - const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' }); + const res = await service.getCase({ id: 'a' }); expect(res.attributes.external_service?.connector_id).toMatchInlineSnapshot(`"none"`); }); @@ -1087,7 +1066,7 @@ describe('CasesService', () => { unsecuredSavedObjectsClient.get.mockReturnValue( Promise.resolve(createCaseSavedObjectResponse()) ); - const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' }); + const res = await service.getCase({ id: 'a' }); expect(res.attributes.external_service).toMatchInlineSnapshot(` Object { @@ -1118,7 +1097,7 @@ describe('CasesService', () => { ], } as unknown as SavedObject) ); - const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' }); + const res = await service.getCase({ id: 'a' }); expect(res.attributes.connector).toMatchInlineSnapshot(` Object { @@ -1138,7 +1117,7 @@ describe('CasesService', () => { attributes: { external_service: null }, } as SavedObject) ); - const res = await service.getCase({ unsecuredSavedObjectsClient, id: 'a' }); + const res = await service.getCase({ id: 'a' }); expect(res.attributes.connector).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/cases/server/services/cases/index.ts b/x-pack/plugins/cases/server/services/cases/index.ts index 3e23a3989eed3..832d12071b466 100644 --- a/x-pack/plugins/cases/server/services/cases/index.ts +++ b/x-pack/plugins/cases/server/services/cases/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import pMap from 'p-map'; import { KibanaRequest, Logger, @@ -26,7 +25,6 @@ import { SecurityPluginSetup } from '../../../../security/server'; import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT, - MAX_CONCURRENT_SEARCHES, MAX_DOCS_PER_PAGE, } from '../../../common/constants'; import { @@ -34,17 +32,16 @@ import { CaseResponse, CasesFindRequest, CommentAttributes, - CommentType, User, CaseAttributes, + CaseStatuses, + caseStatuses, } from '../../../common/api'; import { SavedObjectFindOptionsKueryNode } from '../../common/types'; -import { defaultSortField, flattenCaseSavedObject, groupTotalAlertsByID } from '../../common/utils'; +import { defaultSortField, flattenCaseSavedObject } from '../../common/utils'; import { defaultPage, defaultPerPage } from '../../routes/api'; -import { ClientArgs } from '..'; import { combineFilters } from '../../client/utils'; import { includeFieldsRequiredForAuthentication } from '../../authorization/utils'; -import { EnsureSOAuthCallback } from '../../authorization'; import { transformSavedObjectToExternalModel, transformAttributesToESModel, @@ -54,8 +51,9 @@ import { transformFindResponseToExternalModel, } from './transform'; import { ESCaseAttributes } from './types'; +import { AttachmentService } from '../attachments'; -interface GetCaseIdsByAlertIdArgs extends ClientArgs { +interface GetCaseIdsByAlertIdArgs { alertId: string; filter?: KueryNode; } @@ -65,31 +63,25 @@ interface PushedArgs { pushed_by: User; } -interface GetCaseArgs extends ClientArgs { +interface GetCaseArgs { id: string; } -interface GetCasesArgs extends ClientArgs { +interface GetCasesArgs { caseIds: string[]; } interface FindCommentsArgs { - unsecuredSavedObjectsClient: SavedObjectsClientContract; id: string | string[]; options?: SavedObjectFindOptionsKueryNode; } interface FindCaseCommentsArgs { - unsecuredSavedObjectsClient: SavedObjectsClientContract; id: string | string[]; options?: SavedObjectFindOptionsKueryNode; } -interface FindCasesArgs extends ClientArgs { - options?: SavedObjectFindOptionsKueryNode; -} - -interface PostCaseArgs extends ClientArgs { +interface PostCaseArgs { attributes: CaseAttributes; id: string; } @@ -100,9 +92,9 @@ interface PatchCase { originalCase: SavedObject; version?: string; } -type PatchCaseArgs = PatchCase & ClientArgs; +type PatchCaseArgs = PatchCase; -interface PatchCasesArgs extends ClientArgs { +interface PatchCasesArgs { cases: PatchCase[]; } @@ -110,11 +102,6 @@ interface GetUserArgs { request: KibanaRequest; } -interface CaseCommentStats { - commentTotals: Map; - alertTotals: Map; -} - interface CasesMapWithPageInfo { casesMap: Map; page: number; @@ -135,10 +122,27 @@ interface GetReportersArgs { } export class CasesService { - constructor( - private readonly log: Logger, - private readonly authentication?: SecurityPluginSetup['authc'] - ) {} + private readonly log: Logger; + private readonly authentication?: SecurityPluginSetup['authc']; + private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; + private readonly attachmentService: AttachmentService; + + constructor({ + log, + authentication, + unsecuredSavedObjectsClient, + attachmentService, + }: { + log: Logger; + authentication?: SecurityPluginSetup['authc']; + unsecuredSavedObjectsClient: SavedObjectsClientContract; + attachmentService: AttachmentService; + }) { + this.log = log; + this.authentication = authentication; + this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient; + this.attachmentService = attachmentService; + } private buildCaseIdsAggs = ( size: number = 100 @@ -159,7 +163,6 @@ export class CasesService { }); public async getCaseIdsByAlertId({ - unsecuredSavedObjectsClient, alertId, filter, }: GetCaseIdsByAlertIdArgs): Promise< @@ -172,7 +175,7 @@ export class CasesService { filter, ]); - const response = await unsecuredSavedObjectsClient.find< + const response = await this.unsecuredSavedObjectsClient.find< CommentAttributes, GetCaseIdsByAlertIdAggs >({ @@ -204,35 +207,32 @@ export class CasesService { * Returns a map of all cases. */ public async findCasesGroupedByID({ - unsecuredSavedObjectsClient, caseOptions, }: { - unsecuredSavedObjectsClient: SavedObjectsClientContract; caseOptions: FindCaseOptions; }): Promise { - const cases = await this.findCases({ - unsecuredSavedObjectsClient, - options: caseOptions, - }); + const cases = await this.findCases(caseOptions); const casesMap = cases.saved_objects.reduce((accMap, caseInfo) => { accMap.set(caseInfo.id, caseInfo); return accMap; }, new Map>()); - const totalCommentsForCases = await this.getCaseCommentStats({ - unsecuredSavedObjectsClient, - ids: Array.from(casesMap.keys()), + const commentTotals = await this.attachmentService.getCaseCommentStats({ + unsecuredSavedObjectsClient: this.unsecuredSavedObjectsClient, + caseIds: Array.from(casesMap.keys()), }); const casesWithComments = new Map(); for (const [id, caseInfo] of casesMap.entries()) { + const { alerts, nonAlerts } = commentTotals.get(id) ?? { alerts: 0, nonAlerts: 0 }; + casesWithComments.set( id, flattenCaseSavedObject({ savedObject: caseInfo, - totalComment: totalCommentsForCases.commentTotals.get(id) ?? 0, - totalAlerts: totalCommentsForCases.alertTotals.get(id) ?? 0, + totalComment: nonAlerts, + totalAlerts: alerts, }) ); } @@ -245,107 +245,69 @@ export class CasesService { }; } - /** - * Retrieves the number of cases that exist with a given status (open, closed, etc). - */ - public async findCaseStatusStats({ - unsecuredSavedObjectsClient, - caseOptions, - ensureSavedObjectsAreAuthorized, - }: { - unsecuredSavedObjectsClient: SavedObjectsClientContract; - caseOptions: SavedObjectFindOptionsKueryNode; - ensureSavedObjectsAreAuthorized: EnsureSOAuthCallback; - }): Promise { - const cases = await this.findCases({ - unsecuredSavedObjectsClient, - options: { - ...caseOptions, - page: 1, - perPage: MAX_DOCS_PER_PAGE, - }, - }); - - // make sure that the retrieved cases were correctly filtered by owner - ensureSavedObjectsAreAuthorized( - cases.saved_objects.map((caseInfo) => ({ id: caseInfo.id, owner: caseInfo.attributes.owner })) - ); - - return cases.saved_objects.length; - } - - /** - * Returns the number of total comments and alerts for a case - */ - public async getCaseCommentStats({ - unsecuredSavedObjectsClient, - ids, + public async getCaseStatusStats({ + searchOptions, }: { - unsecuredSavedObjectsClient: SavedObjectsClientContract; - ids: string[]; - }): Promise { - if (ids.length <= 0) { - return { - commentTotals: new Map(), - alertTotals: new Map(), - }; - } - - const getCommentsMapper = async (id: string) => - this.getAllCaseComments({ - unsecuredSavedObjectsClient, - id, - options: { page: 1, perPage: 1 }, - }); - - // Ensuring we don't do too many concurrent get running. - const allComments = await pMap(ids, getCommentsMapper, { - concurrency: MAX_CONCURRENT_SEARCHES, - }); - - const alerts = await this.getAllCaseComments({ - unsecuredSavedObjectsClient, - id: ids, - options: { - filter: nodeBuilder.is(`${CASE_COMMENT_SAVED_OBJECT}.attributes.type`, CommentType.alert), + searchOptions: SavedObjectFindOptionsKueryNode; + }): Promise<{ + [status in CaseStatuses]: number; + }> { + const cases = await this.unsecuredSavedObjectsClient.find< + ESCaseAttributes, + { + statuses: { + buckets: Array<{ + key: string; + doc_count: number; + }>; + }; + } + >({ + ...searchOptions, + type: CASE_SAVED_OBJECT, + perPage: 0, + aggs: { + statuses: { + terms: { + field: `${CASE_SAVED_OBJECT}.attributes.status`, + size: caseStatuses.length, + order: { _key: 'asc' }, + }, + }, }, }); - const getID = (comments: SavedObjectsFindResponse) => { - return comments.saved_objects.length > 0 - ? comments.saved_objects[0].references.find((ref) => ref.type === CASE_SAVED_OBJECT)?.id - : undefined; + const statusBuckets = CasesService.getStatusBuckets(cases.aggregations?.statuses.buckets); + return { + open: statusBuckets?.get('open') ?? 0, + 'in-progress': statusBuckets?.get('in-progress') ?? 0, + closed: statusBuckets?.get('closed') ?? 0, }; + } - const groupedComments = allComments.reduce((acc, comments) => { - const id = getID(comments); - if (id) { - acc.set(id, comments.total); - } + private static getStatusBuckets( + buckets: Array<{ key: string; doc_count: number }> | undefined + ): Map | undefined { + return buckets?.reduce((acc, bucket) => { + acc.set(bucket.key, bucket.doc_count); return acc; }, new Map()); - - const groupedAlerts = groupTotalAlertsByID({ comments: alerts }); - return { commentTotals: groupedComments, alertTotals: groupedAlerts }; } - public async deleteCase({ unsecuredSavedObjectsClient, id: caseId }: GetCaseArgs) { + public async deleteCase({ id: caseId }: GetCaseArgs) { try { this.log.debug(`Attempting to DELETE case ${caseId}`); - return await unsecuredSavedObjectsClient.delete(CASE_SAVED_OBJECT, caseId); + return await this.unsecuredSavedObjectsClient.delete(CASE_SAVED_OBJECT, caseId); } catch (error) { this.log.error(`Error on DELETE case ${caseId}: ${error}`); throw error; } } - public async getCase({ - unsecuredSavedObjectsClient, - id: caseId, - }: GetCaseArgs): Promise> { + public async getCase({ id: caseId }: GetCaseArgs): Promise> { try { this.log.debug(`Attempting to GET case ${caseId}`); - const caseSavedObject = await unsecuredSavedObjectsClient.get( + const caseSavedObject = await this.unsecuredSavedObjectsClient.get( CASE_SAVED_OBJECT, caseId ); @@ -357,12 +319,11 @@ export class CasesService { } public async getResolveCase({ - unsecuredSavedObjectsClient, id: caseId, }: GetCaseArgs): Promise> { try { this.log.debug(`Attempting to resolve case ${caseId}`); - const resolveCaseResult = await unsecuredSavedObjectsClient.resolve( + const resolveCaseResult = await this.unsecuredSavedObjectsClient.resolve( CASE_SAVED_OBJECT, caseId ); @@ -377,12 +338,11 @@ export class CasesService { } public async getCases({ - unsecuredSavedObjectsClient, caseIds, }: GetCasesArgs): Promise> { try { this.log.debug(`Attempting to GET cases ${caseIds.join(', ')}`); - const cases = await unsecuredSavedObjectsClient.bulkGet( + const cases = await this.unsecuredSavedObjectsClient.bulkGet( caseIds.map((caseId) => ({ type: CASE_SAVED_OBJECT, id: caseId })) ); return transformBulkResponseToExternalModel(cases); @@ -392,13 +352,12 @@ export class CasesService { } } - public async findCases({ - unsecuredSavedObjectsClient, - options, - }: FindCasesArgs): Promise> { + public async findCases( + options?: SavedObjectFindOptionsKueryNode + ): Promise> { try { this.log.debug(`Attempting to find cases`); - const cases = await unsecuredSavedObjectsClient.find({ + const cases = await this.unsecuredSavedObjectsClient.find({ sortField: defaultSortField, ...options, type: CASE_SAVED_OBJECT, @@ -421,21 +380,20 @@ export class CasesService { } private async getAllComments({ - unsecuredSavedObjectsClient, id, options, }: FindCommentsArgs): Promise> { try { this.log.debug(`Attempting to GET all comments internal for id ${JSON.stringify(id)}`); if (options?.page !== undefined || options?.perPage !== undefined) { - return unsecuredSavedObjectsClient.find({ + return this.unsecuredSavedObjectsClient.find({ type: CASE_COMMENT_SAVED_OBJECT, sortField: defaultSortField, ...options, }); } - return unsecuredSavedObjectsClient.find({ + return this.unsecuredSavedObjectsClient.find({ type: CASE_COMMENT_SAVED_OBJECT, page: 1, perPage: MAX_DOCS_PER_PAGE, @@ -453,7 +411,6 @@ export class CasesService { * to override this pass in the either the page or perPage options. */ public async getAllCaseComments({ - unsecuredSavedObjectsClient, id, options, }: FindCaseCommentsArgs): Promise> { @@ -470,7 +427,6 @@ export class CasesService { this.log.debug(`Attempting to GET all comments for case caseID ${JSON.stringify(id)}`); return await this.getAllComments({ - unsecuredSavedObjectsClient, id, options: { hasReferenceOperator: 'OR', @@ -485,14 +441,11 @@ export class CasesService { } } - public async getReporters({ - unsecuredSavedObjectsClient, - filter, - }: GetReportersArgs): Promise { + public async getReporters({ filter }: GetReportersArgs): Promise { try { this.log.debug(`Attempting to GET all reporters`); - const results = await unsecuredSavedObjectsClient.find< + const results = await this.unsecuredSavedObjectsClient.find< ESCaseAttributes, { reporters: { @@ -549,11 +502,11 @@ export class CasesService { } } - public async getTags({ unsecuredSavedObjectsClient, filter }: GetTagsArgs): Promise { + public async getTags({ filter }: GetTagsArgs): Promise { try { this.log.debug(`Attempting to GET all cases`); - const results = await unsecuredSavedObjectsClient.find< + const results = await this.unsecuredSavedObjectsClient.find< ESCaseAttributes, { tags: { buckets: Array<{ key: string }> } } >({ @@ -604,15 +557,11 @@ export class CasesService { } } - public async postNewCase({ - unsecuredSavedObjectsClient, - attributes, - id, - }: PostCaseArgs): Promise> { + public async postNewCase({ attributes, id }: PostCaseArgs): Promise> { try { this.log.debug(`Attempting to POST a new case`); const transformedAttributes = transformAttributesToESModel(attributes); - const createdCase = await unsecuredSavedObjectsClient.create( + const createdCase = await this.unsecuredSavedObjectsClient.create( CASE_SAVED_OBJECT, transformedAttributes.attributes, { id, references: transformedAttributes.referenceHandler.build() } @@ -625,7 +574,6 @@ export class CasesService { } public async patchCase({ - unsecuredSavedObjectsClient, caseId, updatedAttributes, originalCase, @@ -635,7 +583,7 @@ export class CasesService { this.log.debug(`Attempting to UPDATE case ${caseId}`); const transformedAttributes = transformAttributesToESModel(updatedAttributes); - const updatedCase = await unsecuredSavedObjectsClient.update( + const updatedCase = await this.unsecuredSavedObjectsClient.update( CASE_SAVED_OBJECT, caseId, transformedAttributes.attributes, @@ -653,7 +601,6 @@ export class CasesService { } public async patchCases({ - unsecuredSavedObjectsClient, cases, }: PatchCasesArgs): Promise> { try { @@ -670,7 +617,7 @@ export class CasesService { }; }); - const updatedCases = await unsecuredSavedObjectsClient.bulkUpdate( + const updatedCases = await this.unsecuredSavedObjectsClient.bulkUpdate( bulkUpdate ); return transformUpdateResponsesToExternalModels(updatedCases); diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index 4c7210224c929..8e21db9ccb4e0 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -37,9 +37,8 @@ export const createCaseServiceMock = (): CaseServiceMock => { postNewCase: jest.fn(), patchCase: jest.fn(), patchCases: jest.fn(), - getCaseCommentStats: jest.fn(), - findCaseStatusStats: jest.fn(), findCasesGroupedByID: jest.fn(), + getCaseStatusStats: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error @@ -108,6 +107,7 @@ export const createAttachmentServiceMock = (): AttachmentServiceMock => { getAllAlertsAttachToCase: jest.fn(), countAlertsAttachedToCase: jest.fn(), executeCaseActionsAggregations: jest.fn(), + getCaseCommentStats: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index 7198fb6f8a774..1eef581610f00 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { homePluginMock } from 'src/plugins/home/public/mocks'; import { securityMock } from '../../security/public/mocks'; diff --git a/x-pack/plugins/cloud/server/routes/chat.test.ts b/x-pack/plugins/cloud/server/routes/chat.test.ts index 9ed76eff6d081..e12278e264e5e 100644 --- a/x-pack/plugins/cloud/server/routes/chat.test.ts +++ b/x-pack/plugins/cloud/server/routes/chat.test.ts @@ -44,14 +44,16 @@ describe('chat route', () => { `); }); - test('returns user information and a token', async () => { + test('returns user information taken from saml metadata and a token', async () => { const security = securityMock.createSetup(); const username = 'user.name'; const email = 'user@elastic.co'; security.authc.getCurrentUser.mockReturnValueOnce({ username, - email, + metadata: { + saml_email: [email], + }, }); const router = httpServiceMock.createRouter(); diff --git a/x-pack/plugins/cloud/server/routes/chat.ts b/x-pack/plugins/cloud/server/routes/chat.ts index 62c4475c92ae5..1bc3505c087ae 100644 --- a/x-pack/plugins/cloud/server/routes/chat.ts +++ b/x-pack/plugins/cloud/server/routes/chat.ts @@ -6,11 +6,18 @@ */ import { IRouter } from '../../../../../src/core/server'; -import type { SecurityPluginSetup } from '../../../security/server'; +import type { SecurityPluginSetup, AuthenticatedUser } from '../../../security/server'; import { GET_CHAT_USER_DATA_ROUTE_PATH } from '../../common/constants'; import type { GetChatUserDataResponseBody } from '../../common/types'; import { generateSignedJwt } from '../util/generate_jwt'; +type MetaWithSaml = AuthenticatedUser['metadata'] & { + saml_name: [string]; + saml_email: [string]; + saml_roles: [string]; + saml_principal: [string]; +}; + export const registerChatRoute = ({ router, chatIdentitySecret, @@ -33,7 +40,9 @@ export const registerChatRoute = ({ }, async (_context, request, response) => { const user = security.authc.getCurrentUser(request); - let { email: userEmail, username: userId } = user || {}; + const { metadata, username } = user || {}; + let userId = username; + let [userEmail] = (metadata as MetaWithSaml)?.saml_email || []; // In local development, these values are not populated. This is a workaround // to allow for local testing. diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts index fe43f517f7f3f..ad7d95fc64c2e 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/auto_follow_pattern.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString } from '@kbn/test/jest'; +import { getRandomString } from '@kbn/test-jest-helpers'; import { AutoFollowPattern } from '../../../../common/types'; export const getAutoFollowPatternMock = ({ diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts index e2f48e688610e..c3a52400dea00 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/fixtures/follower_index.ts @@ -6,7 +6,7 @@ */ import Chance from 'chance'; -import { getRandomString } from '@kbn/test/jest'; +import { getRandomString } from '@kbn/test-jest-helpers'; import { FollowerIndex } from '../../../../common/types'; const chance = new Chance(); diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js index 9f2cb6452c6f2..b3e47b2ad7dca 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/follower_indices_list.test.js @@ -11,7 +11,7 @@ Could not load worker ReferenceError: Worker is not defined at createWorker (//node_modules/brace/index.js:17992:5) */ -import { stubWebWorker } from '@kbn/test/jest'; // eslint-disable-line no-unused-vars +import { stubWebWorker } from '@kbn/test-jest-helpers'; // eslint-disable-line no-unused-vars import { act } from 'react-dom/test-utils'; import { getFollowerIndexMock } from './fixtures/follower_index'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js index c1902e22265b8..138ea72bfe7ff 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_add.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { AutoFollowPatternAdd } from '../../../app/sections/auto_follow_pattern_add'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js index 43d677596690f..0b6e6216209d8 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_edit.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { AutoFollowPatternEdit } from '../../../app/sections/auto_follow_pattern_edit'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js index 329d83d6fe3c7..0c7f483685075 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/auto_follow_pattern_list.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject } from '@kbn/test-jest-helpers'; import { AutoFollowPatternList } from '../../../app/sections/home/auto_follow_pattern_list'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js index a873af2de8b06..ab107348bd5b7 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_add.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { FollowerIndexAdd } from '../../../app/sections/follower_index_add'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js index f8a68e54ea52e..5f83aa841b11d 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_edit.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { FollowerIndexEdit } from '../../../app/sections/follower_index_edit'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js index 5e6a48413b18b..961ce940084e7 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/follower_index_list.helpers.js @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject } from '@kbn/test-jest-helpers'; import { FollowerIndicesList } from '../../../app/sections/home/follower_indices_list'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js index 1f03a56fbbcdb..8263b14e9eaab 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/home.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { CrossClusterReplicationHome } from '../../../app/sections/home/home'; import { ccrStore } from '../../../app/store'; import { routing } from '../../../app/services/routing'; diff --git a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/index.js b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/index.js index 5f910565edb3a..0fedc1ab6b903 100644 --- a/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/__jest__/client_integration/helpers/index.js @@ -13,7 +13,7 @@ import { setup as followerIndexAddSetup } from './follower_index_add.helpers'; import { setup as followerIndexEditSetup } from './follower_index_edit.helpers'; import { setup as homeSetup } from './home.helpers'; -export { nextTick, getRandomString, findTestSubject, delay } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject, delay } from '@kbn/test-jest-helpers'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 6e9b06ad36df7..5f5753b8858e0 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -6,7 +6,7 @@ */ import React, { ReactNode } from 'react'; -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { render, waitFor, screen, act } from '@testing-library/react'; import { Storage } from '../../../../../../../src/plugins/kibana_utils/public/'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; diff --git a/x-pack/plugins/data_visualizer/kibana.json b/x-pack/plugins/data_visualizer/kibana.json index 03f21bcde469d..aee54c216150c 100644 --- a/x-pack/plugins/data_visualizer/kibana.json +++ b/x-pack/plugins/data_visualizer/kibana.json @@ -30,6 +30,7 @@ "esUiShared", "fieldFormats", "uiActions", + "lens", "cloud" ], "owner": { diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/lens_utils.ts b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/lens_utils.ts index 3db1795456127..10ee347750e10 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/lens_utils.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/components/field_data_row/action_menu/lens_utils.ts @@ -17,6 +17,7 @@ import type { TypedLensByValueInput, XYLayerConfig, } from '../../../../../../../lens/public'; +import { DOCUMENT_FIELD_NAME as RECORDS_FIELD } from '../../../../../../../lens/common/constants'; import { FieldVisConfig } from '../../stats_table/types'; import { JOB_FIELD_TYPES } from '../../../../../../common/constants'; @@ -52,7 +53,7 @@ export function getNumberSettings(item: FieldVisConfig, defaultIndexPattern: Ind label: COUNT, dataType: 'number', isBucketed: false, - sourceField: 'Records', + sourceField: RECORDS_FIELD, operationType: 'count', }, }; @@ -107,7 +108,7 @@ export function getDateSettings(item: FieldVisConfig) { label: COUNT, operationType: 'count', scale: 'ratio', - sourceField: 'Records', + sourceField: RECORDS_FIELD, }, col1: { dataType: 'date', @@ -148,7 +149,7 @@ export function getKeywordSettings(item: FieldVisConfig) { label: COUNT, dataType: 'number', isBucketed: false, - sourceField: 'Records', + sourceField: RECORDS_FIELD, operationType: 'count', }, }; @@ -181,7 +182,7 @@ export function getBooleanSettings(item: FieldVisConfig) { label: COUNT, dataType: 'number', isBucketed: false, - sourceField: 'Records', + sourceField: RECORDS_FIELD, operationType: 'count', }, }; diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/edit_flyout/overrides.test.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/edit_flyout/overrides.test.js index 8a4695f823128..a81b3b5865008 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/edit_flyout/overrides.test.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/edit_flyout/overrides.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { FILE_FORMATS } from '../../../../../common/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts index 09b4292a29008..c9b5afe2f6cf4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/app_logic.test.ts @@ -21,6 +21,21 @@ describe('AppLogic', () => { jest.clearAllMocks(); }); + const DEFAULT_VALUES = { + configuredLimits: { + engine: { + maxDocumentByteSize: 102400, + maxEnginesPerMetaEngine: 15, + }, + }, + account: { + accountId: 'some-id-string', + onboardingComplete: true, + role: DEFAULT_INITIAL_APP_DATA.appSearch.role, + }, + myRole: {}, + }; + it('sets values from props', () => { mount({}, DEFAULT_INITIAL_APP_DATA); @@ -54,7 +69,12 @@ describe('AppLogic', () => { mount({}, { ...DEFAULT_INITIAL_APP_DATA, appSearch: { onboardingComplete: false } }); AppLogic.actions.setOnboardingComplete(); - expect(AppLogic.values.account.onboardingComplete).toEqual(true); + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + account: { + onboardingComplete: true, + }, + }); }); }); }); @@ -64,7 +84,11 @@ describe('AppLogic', () => { it('falls back to an empty object if role is missing', () => { mount({}, { ...DEFAULT_INITIAL_APP_DATA, appSearch: {} }); - expect(AppLogic.values.myRole).toEqual({}); + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + account: {}, + myRole: {}, + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts index 62ba44128663a..a87555c58014a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/analytics_logic.test.ts @@ -11,7 +11,7 @@ jest.mock('../engine', () => ({ EngineLogic: { values: { engineName: 'test-engine' } }, })); -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx index ea5a28c02c989..d9ffb83a561c4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/test_helpers/shared_columns_tests.tsx @@ -14,7 +14,7 @@ import '../../../../../__mocks__/engine_logic.mock'; import { ReactWrapper } from 'enzyme'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; export const runActionColumnTests = (wrapper: ReactWrapper) => { const { http } = mockHttpValues; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts index 51d51b5aee88c..35fa74374acb2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/api_logs_logic.test.ts @@ -13,7 +13,7 @@ import { import { mockApiLog } from './__mocks__/api_log.mock'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DEFAULT_META } from '../../../shared/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts index 466ccc61838f0..08bd78b5298d4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/add_domain/add_domain_logic.test.ts @@ -26,7 +26,7 @@ jest.mock('./utils', () => ({ getDomainWithProtocol: jest.fn().mockImplementation((domain) => domain), })); -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { CrawlerLogic } from '../../crawler_logic'; import { CrawlerDomain } from '../../types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts index 3c95243459a1c..5c9e95e097dd4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler_logic.test.ts @@ -20,7 +20,7 @@ jest.mock('./manage_crawls_popover_logic', () => ({ }, })); -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { CrawlUnits } from '../../types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts index f80465fa2c407..da879192efdfa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/manage_crawls_popover_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../../../__mocks__/kea_logic'; import '../../../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { CrawlerDomain } from '../../types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts index 152fe0f64de4d..f24fe4576e449 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawl_detail_logic.test.ts @@ -8,7 +8,7 @@ import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts index fda96ca5f8381..a4963d7f21b51 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_domains_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { Meta } from '../../../../../common/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts index 0735b5262a20a..e622798e688ab 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts index 547218ad6a2c1..53b63831f14b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_single_domain_logic.test.ts @@ -21,7 +21,7 @@ jest.mock('./crawler_logic', () => ({ }, })); -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts index decb98d227975..4ccd91959831a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_logic.test.ts @@ -11,7 +11,7 @@ import { mockHttpValues, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DEFAULT_META } from '../../../shared/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx index a3ca646bd9f54..db9ee7a4bd12b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/suggestions_logic.test.tsx @@ -8,7 +8,7 @@ import { LogicMounter, mockHttpValues } from '../../../../__mocks__/kea_logic'; import '../../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DEFAULT_META } from '../../../../shared/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts index 644139250c07c..193d397b1fb50 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curation/curation_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../../__mocks__/kea_logic'; import '../../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts index 71d8b65f1a639..d8fcc01c29a96 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/curations_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DEFAULT_META } from '../../../shared/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts index e7ac2b3610998..8b20c6a64e073 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curation_suggestion/curation_suggestion_logic.test.ts @@ -14,7 +14,7 @@ import { import '../../../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; import { HydratedCurationSuggestion } from '../../types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts index af9f876820790..fd6589330e43f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_history/components/ignored_queries_panel/ignored_queries_logic.test.ts @@ -16,7 +16,7 @@ import { itShowsServerErrorAsFlashMessage } from '../../../../../../../test_help // I don't know why eslint is saying this line is out of order // eslint-disable-next-line import/order -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { IgnoredQueriesLogic } from './ignored_queries_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx index b95ae0bca5bf6..73559b2c4b757 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings.test.tsx @@ -17,7 +17,7 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { EuiButtonEmpty, EuiCallOut, EuiSwitch } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { docLinks } from '../../../../../shared/doc_links'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts index e9643f92f2f71..e3f8a47b401b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/views/curations_settings/curations_settings_logic.test.ts @@ -17,7 +17,7 @@ jest.mock('../../curations_logic', () => ({ }, })); -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { CurationsLogic } from '../..'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts index 753871765896a..edd5c5566a45d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts @@ -9,7 +9,7 @@ import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; import dedent from 'dedent'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; jest.mock('../engine', () => ({ EngineLogic: { values: { engineName: 'test-engine' } }, diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts index 848a85f23c2cb..3cdad784a7d27 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../__mocks__/kea_logic'; import { mockEngineValues } from '../../__mocks__'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { InternalSchemaType } from '../../../shared/schema/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts index adcc6bc546629..2b0a4627a64b3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_logic.test.ts @@ -11,7 +11,7 @@ import { mockFlashMessageHelpers, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { SchemaType } from '../../../shared/schema/types'; import { ApiTokenTypes } from '../credentials/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts index 70ff3b5adc078..3379b5d7850c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_creation/engine_creation_logic.test.ts @@ -12,7 +12,7 @@ import { mockFlashMessageHelpers, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { EngineCreationLogic } from './engine_creation_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts index 142aab98a643b..6f0454ca73754 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_logic.test.ts @@ -11,7 +11,7 @@ jest.mock('../engine', () => ({ EngineLogic: { values: { engineName: 'some-engine' } }, })); -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts index d0a227c8c6fbe..9659745aeb61d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_logic.test.ts @@ -11,7 +11,7 @@ import { mockFlashMessageHelpers, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DEFAULT_META } from '../../../shared/constants'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts index 20c5860160b78..c254a978f8789 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/log_retention/log_retention_logic.test.ts @@ -11,7 +11,7 @@ import { mockFlashMessageHelpers, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { LogRetentionOptions } from './types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts index a785f70a4edb7..9f03044476263 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/meta_engine_creation_logic.test.ts @@ -12,7 +12,7 @@ import { mockKibanaValues, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { MetaEngineCreationLogic } from './meta_engine_creation_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 6ac1c27a27959..605c5ee9d7e80 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../__mocks__/kea_logic'; import { mockEngineValues, mockEngineActions } from '../../__mocks__'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index 92cb2346e0a26..f498d53c509d1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -10,7 +10,7 @@ import { mockEngineValues } from '../../__mocks__'; import { omit } from 'lodash'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { Schema, SchemaConflicts, SchemaType } from '../../../shared/schema/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts index 426934b95388e..ee20fab3da66c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/sample_response/sample_response_logic.test.ts @@ -8,7 +8,7 @@ import { LogicMounter, mockHttpValues } from '../../../../__mocks__/kea_logic'; import '../../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { flashAPIErrors } from '../../../../shared/flash_messages'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts index a7422b34d2843..d6802c303807b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings_logic.test.ts @@ -13,7 +13,7 @@ import { import { engines } from '../../__mocks__/engines.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts index 0b8e5e97cf5ed..dd469a486ca45 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/sample_engine_creation_cta/sample_engine_creation_cta_logic.test.ts @@ -12,7 +12,7 @@ import { mockFlashMessageHelpers, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { SampleEngineCreationCtaLogic } from './sample_engine_creation_cta_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts index bb8156a8b55fa..67c88d211f118 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/reindex_job/reindex_job_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../../__mocks__/kea_logic'; import '../../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { ReindexJobLogic } from './reindex_job_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts index 5b40b362bc665..e12ad7405742a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_base_logic.test.ts @@ -8,7 +8,7 @@ import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { SchemaType } from '../../../shared/schema/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts index 484996b710524..8f74a44f8a930 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/schema/schema_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../__mocks__/kea_logic'; import { mockEngineActions } from '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { SchemaType, Schema } from '../../../shared/schema/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts index d4f5ee29f7467..a56d2f7d5c949 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search/search_logic.test.ts @@ -12,7 +12,7 @@ import { mockFlashMessageHelpers, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { SearchLogic } from './search_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts index 49444fbd0c5c5..58f6f3d98eb28 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../__mocks__/kea_logic'; import { mockEngineValues } from '../../__mocks__'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts index 75d3c46ef72ee..eaaf43259185e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/source_engines/source_engines_logic.test.ts @@ -13,7 +13,7 @@ import { import { mockRecursivelyFetchEngines } from '../../__mocks__'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { EngineLogic } from '../engine'; import { EngineDetails } from '../engine/types'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts index 7376bc11df79e..b00619359f6c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/synonyms_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../__mocks__/kea_logic'; import '../../__mocks__/engine_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts index c03ca8267993a..2eb7f5cb3cdfb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/recursively_fetch_engines/index.test.ts @@ -7,7 +7,7 @@ import { mockHttpValues } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts index 481013d91bf6f..a87aadcc04995 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/tables/generic_endpoint_inline_editable_table/generic_endpoint_inline_editable_table_logic.test.ts @@ -21,7 +21,7 @@ import { mockHttpValues, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { GenericEndpointInlineEditableTableLogic } from './generic_endpoint_inline_editable_table_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts b/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts index 4f1f4a40aa503..8f17edf6628fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts +++ b/x-pack/plugins/enterprise_search/public/applications/test_helpers/error_handling.ts @@ -7,7 +7,7 @@ import { mockFlashMessageHelpers } from '../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { HttpHandler } from 'src/core/public'; export const itShowsServerErrorAsFlashMessage = (httpMethod: HttpHandler, callback: () => void) => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts index 8018243472bac..5731a9a829e83 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/app_logic.test.ts @@ -69,8 +69,10 @@ describe('AppLogic', () => { describe('setContext', () => { it('sets context', () => { AppLogic.actions.setContext(true); - - expect(AppLogic.values.isOrganization).toEqual(true); + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + isOrganization: true, + }); }); }); @@ -79,7 +81,13 @@ describe('AppLogic', () => { mount(DEFAULT_INITIAL_APP_DATA); AppLogic.actions.setSourceRestriction(true); - expect(AppLogic.values.account.canCreatePrivateSources).toEqual(true); + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + searchOAuth: DEFAULT_INITIAL_APP_DATA.searchOAuth, + account: { + canCreatePrivateSources: true, + }, + }); }); }); @@ -89,7 +97,13 @@ describe('AppLogic', () => { mount(DEFAULT_INITIAL_APP_DATA); AppLogic.actions.setOrgName(NAME); - expect(AppLogic.values.organization.name).toEqual(NAME); + expect(AppLogic.values).toEqual({ + ...DEFAULT_VALUES, + searchOAuth: DEFAULT_INITIAL_APP_DATA.searchOAuth, + organization: { + name: NAME, + }, + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts index a02b1578bd38a..2013539b45b27 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/api_keys/api_keys_logic.test.ts @@ -11,7 +11,7 @@ import { mockHttpValues, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { DEFAULT_META } from '../../../shared/constants'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index da4e9cad9e276..65ccd8d95256e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../../../__mocks__/kea_logic'; import { sourceConfigData } from '../../../../__mocks__/content_sources.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; @@ -46,7 +46,7 @@ describe('AddSourceLogic', () => { const { navigateToUrl } = mockKibanaValues; const { clearFlashMessages, flashAPIErrors, setErrorMessage } = mockFlashMessageHelpers; - const defaultValues = { + const DEFAULT_VALUES = { addSourceCurrentStep: AddSourceSteps.ConfigIntroStep, addSourceProps: {}, dataLoading: true, @@ -90,78 +90,104 @@ describe('AddSourceLogic', () => { }); it('has expected default values', () => { - expect(AddSourceLogic.values).toEqual(defaultValues); + expect(AddSourceLogic.values).toEqual(DEFAULT_VALUES); }); describe('actions', () => { it('setSourceConfigData', () => { AddSourceLogic.actions.setSourceConfigData(sourceConfigData); - expect(AddSourceLogic.values.sourceConfigData).toEqual(sourceConfigData); - expect(AddSourceLogic.values.dataLoading).toEqual(false); - expect(AddSourceLogic.values.buttonLoading).toEqual(false); - expect(AddSourceLogic.values.clientIdValue).toEqual( - sourceConfigData.configuredFields.clientId - ); - expect(AddSourceLogic.values.clientSecretValue).toEqual( - sourceConfigData.configuredFields.clientSecret - ); - expect(AddSourceLogic.values.baseUrlValue).toEqual(sourceConfigData.configuredFields.baseUrl); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + sourceConfigData, + dataLoading: false, + buttonLoading: false, + clientIdValue: sourceConfigData.configuredFields.clientId, + baseUrlValue: sourceConfigData.configuredFields.baseUrl, + clientSecretValue: sourceConfigData.configuredFields.clientSecret, + }); }); it('setSourceConnectData', () => { AddSourceLogic.actions.setSourceConnectData(sourceConnectData); - expect(AddSourceLogic.values.sourceConnectData).toEqual(sourceConnectData); - expect(AddSourceLogic.values.buttonLoading).toEqual(false); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + sourceConnectData, + buttonLoading: false, + }); }); it('setClientIdValue', () => { AddSourceLogic.actions.setClientIdValue('id'); - expect(AddSourceLogic.values.clientIdValue).toEqual('id'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + clientIdValue: 'id', + }); }); it('setClientSecretValue', () => { AddSourceLogic.actions.setClientSecretValue('secret'); - expect(AddSourceLogic.values.clientSecretValue).toEqual('secret'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + clientSecretValue: 'secret', + }); }); it('setBaseUrlValue', () => { AddSourceLogic.actions.setBaseUrlValue('secret'); - expect(AddSourceLogic.values.baseUrlValue).toEqual('secret'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + baseUrlValue: 'secret', + }); }); it('setCustomSourceNameValue', () => { AddSourceLogic.actions.setCustomSourceNameValue('name'); - expect(AddSourceLogic.values.customSourceNameValue).toEqual('name'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + customSourceNameValue: 'name', + }); }); it('setSourceLoginValue', () => { AddSourceLogic.actions.setSourceLoginValue('login'); - expect(AddSourceLogic.values.loginValue).toEqual('login'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + loginValue: 'login', + }); }); it('setSourcePasswordValue', () => { AddSourceLogic.actions.setSourcePasswordValue('password'); - expect(AddSourceLogic.values.passwordValue).toEqual('password'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + passwordValue: 'password', + }); }); it('setSourceSubdomainValue', () => { AddSourceLogic.actions.setSourceSubdomainValue('subdomain'); - expect(AddSourceLogic.values.subdomainValue).toEqual('subdomain'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + subdomainValue: 'subdomain', + }); }); it('setSourceIndexPermissionsValue', () => { AddSourceLogic.actions.setSourceIndexPermissionsValue(true); - expect(AddSourceLogic.values.indexPermissionsValue).toEqual(true); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + indexPermissionsValue: true, + }); }); it('setCustomSourceData', () => { @@ -174,69 +200,85 @@ describe('AddSourceLogic', () => { AddSourceLogic.actions.setCustomSourceData(newCustomSource); - expect(AddSourceLogic.values.newCustomSource).toEqual(newCustomSource); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + newCustomSource, + }); }); it('setPreContentSourceConfigData', () => { AddSourceLogic.actions.setPreContentSourceConfigData(config); - expect(AddSourceLogic.values.dataLoading).toEqual(false); - expect(AddSourceLogic.values.sectionLoading).toEqual(false); - expect(AddSourceLogic.values.currentServiceType).toEqual(config.serviceType); - expect(AddSourceLogic.values.githubOrganizations).toEqual(config.githubOrganizations); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + oauthConfigCompleted: true, + dataLoading: false, + sectionLoading: false, + currentServiceType: config.serviceType, + githubOrganizations: config.githubOrganizations, + }); }); it('setSelectedGithubOrganizations', () => { AddSourceLogic.actions.setSelectedGithubOrganizations('foo'); - expect(AddSourceLogic.values.selectedGithubOrganizationsMap).toEqual({ foo: true }); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + selectedGithubOrganizationsMap: { + foo: true, + }, + selectedGithubOrganizations: ['foo'], + }); }); it('setPreContentSourceId', () => { AddSourceLogic.actions.setPreContentSourceId('123'); - expect(AddSourceLogic.values.preContentSourceId).toEqual('123'); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + oauthConfigCompleted: false, + preContentSourceId: '123', + }); }); it('setButtonNotLoading', () => { AddSourceLogic.actions.setButtonNotLoading(); - expect(AddSourceLogic.values.buttonLoading).toEqual(false); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + buttonLoading: false, + }); }); it('resetSourceState', () => { AddSourceLogic.actions.resetSourceState(); - expect(AddSourceLogic.values.dataLoading).toEqual(false); - expect(AddSourceLogic.values.buttonLoading).toEqual(false); - expect(AddSourceLogic.values.clientIdValue).toEqual(''); - expect(AddSourceLogic.values.clientSecretValue).toEqual(''); - expect(AddSourceLogic.values.baseUrlValue).toEqual(''); - expect(AddSourceLogic.values.loginValue).toEqual(''); - expect(AddSourceLogic.values.passwordValue).toEqual(''); - expect(AddSourceLogic.values.subdomainValue).toEqual(''); - expect(AddSourceLogic.values.indexPermissionsValue).toEqual(false); - expect(AddSourceLogic.values.customSourceNameValue).toEqual(''); - expect(AddSourceLogic.values.newCustomSource).toEqual({}); - expect(AddSourceLogic.values.currentServiceType).toEqual(''); - expect(AddSourceLogic.values.githubOrganizations).toEqual([]); - expect(AddSourceLogic.values.selectedGithubOrganizationsMap).toEqual({}); + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + }); }); it('handles fallback states', () => { const { publicKey, privateKey, consumerKey } = sourceConfigData.configuredFields; - AddSourceLogic.actions.setSourceConfigData({ + const sourceConfigDataMock = { ...sourceConfigData, configuredFields: { publicKey, privateKey, consumerKey, }, + }; + AddSourceLogic.actions.setSourceConfigData(sourceConfigDataMock); + + expect(AddSourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + sourceConfigData: sourceConfigDataMock, + clientIdValue: '', + clientSecretValue: '', + baseUrlValue: '', }); - - expect(AddSourceLogic.values.clientIdValue).toEqual(''); - expect(AddSourceLogic.values.clientSecretValue).toEqual(''); - expect(AddSourceLogic.values.baseUrlValue).toEqual(''); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts index 81a97c2d19e16..336f892bad51b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../../../__mocks__/kea_logic'; import { exampleResult } from '../../../../__mocks__/content_sources.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts index 748e5068833cf..7dba607026761 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_logic.test.ts @@ -12,7 +12,7 @@ import { } from '../../../../../__mocks__/kea_logic'; import { mostRecentIndexJob } from '../../../../__mocks__/content_sources.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; const contentSource = { id: 'source123' }; jest.mock('../../source_logic', () => ({ @@ -44,7 +44,7 @@ describe('SchemaLogic', () => { const { clearFlashMessages, flashSuccessToast, setErrorMessage } = mockFlashMessageHelpers; const { mount } = new LogicMounter(SchemaLogic); - const defaultValues = { + const DEFAULT_VALUES = { sourceId: '', activeSchema: {}, serverSchema: {}, @@ -86,24 +86,35 @@ describe('SchemaLogic', () => { }); it('has expected default values', () => { - expect(SchemaLogic.values).toEqual(defaultValues); + expect(SchemaLogic.values).toEqual(DEFAULT_VALUES); }); describe('actions', () => { + const initializedState = { + ...DEFAULT_VALUES, + activeSchema: schema, + serverSchema: schema, + mostRecentIndexJob, + dataLoading: false, + filteredSchemaFields: schema, + }; + it('onInitializeSchema', () => { SchemaLogic.actions.onInitializeSchema(serverResponse); - expect(SchemaLogic.values.sourceId).toEqual(contentSource.id); - expect(SchemaLogic.values.activeSchema).toEqual(schema); - expect(SchemaLogic.values.serverSchema).toEqual(schema); - expect(SchemaLogic.values.mostRecentIndexJob).toEqual(mostRecentIndexJob); - expect(SchemaLogic.values.dataLoading).toEqual(false); + expect(SchemaLogic.values).toEqual({ + ...initializedState, + sourceId: contentSource.id, + }); }); it('onInitializeSchemaFieldErrors', () => { SchemaLogic.actions.onInitializeSchemaFieldErrors({ fieldCoercionErrors }); - expect(SchemaLogic.values.fieldCoercionErrors).toEqual(fieldCoercionErrors); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + fieldCoercionErrors, + }); }); it('onSchemaSetSuccess', () => { SchemaLogic.actions.onSchemaSetSuccess({ @@ -111,72 +122,96 @@ describe('SchemaLogic', () => { mostRecentIndexJob, }); - expect(SchemaLogic.values.activeSchema).toEqual(schema); - expect(SchemaLogic.values.serverSchema).toEqual(schema); - expect(SchemaLogic.values.mostRecentIndexJob).toEqual(mostRecentIndexJob); - expect(SchemaLogic.values.newFieldType).toEqual(SchemaType.Text); - expect(SchemaLogic.values.addFieldFormErrors).toEqual(null); - expect(SchemaLogic.values.formUnchanged).toEqual(true); - expect(SchemaLogic.values.showAddFieldModal).toEqual(false); - expect(SchemaLogic.values.dataLoading).toEqual(false); - expect(SchemaLogic.values.rawFieldName).toEqual(''); + expect(SchemaLogic.values).toEqual({ + ...initializedState, + newFieldType: SchemaType.Text, + addFieldFormErrors: null, + formUnchanged: true, + showAddFieldModal: false, + rawFieldName: '', + }); }); it('onSchemaSetFormErrors', () => { SchemaLogic.actions.onSchemaSetFormErrors(errors); - expect(SchemaLogic.values.addFieldFormErrors).toEqual(errors); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + addFieldFormErrors: errors, + }); }); it('updateNewFieldType', () => { SchemaLogic.actions.updateNewFieldType(SchemaType.Number); - expect(SchemaLogic.values.newFieldType).toEqual(SchemaType.Number); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + newFieldType: SchemaType.Number, + }); }); it('onFieldUpdate', () => { SchemaLogic.actions.onFieldUpdate({ schema, formUnchanged: false }); - expect(SchemaLogic.values.activeSchema).toEqual(schema); - expect(SchemaLogic.values.formUnchanged).toEqual(false); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + activeSchema: schema, + filteredSchemaFields: schema, + formUnchanged: false, + }); }); it('onIndexingComplete', () => { SchemaLogic.actions.onIndexingComplete(1); - expect(SchemaLogic.values.mostRecentIndexJob).toEqual({ - ...mostRecentIndexJob, - activeReindexJobId: undefined, - percentageComplete: 100, - hasErrors: true, - isActive: false, + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + mostRecentIndexJob: { + ...mostRecentIndexJob, + activeReindexJobId: undefined, + percentageComplete: 100, + hasErrors: true, + isActive: false, + }, }); }); it('resetMostRecentIndexJob', () => { SchemaLogic.actions.resetMostRecentIndexJob(mostRecentIndexJob); - expect(SchemaLogic.values.mostRecentIndexJob).toEqual(mostRecentIndexJob); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + mostRecentIndexJob, + }); }); it('setFieldName', () => { const NAME = 'name'; SchemaLogic.actions.setFieldName(NAME); - expect(SchemaLogic.values.rawFieldName).toEqual(NAME); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + rawFieldName: NAME, + }); }); it('setFilterValue', () => { const VALUE = 'string'; SchemaLogic.actions.setFilterValue(VALUE); - expect(SchemaLogic.values.filterValue).toEqual(VALUE); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + filterValue: VALUE, + }); }); it('openAddFieldModal', () => { SchemaLogic.actions.openAddFieldModal(); - expect(SchemaLogic.values.showAddFieldModal).toEqual(true); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + showAddFieldModal: true, + }); }); it('closeAddFieldModal', () => { @@ -184,14 +219,20 @@ describe('SchemaLogic', () => { SchemaLogic.actions.openAddFieldModal(); SchemaLogic.actions.closeAddFieldModal(); - expect(SchemaLogic.values.showAddFieldModal).toEqual(false); - expect(SchemaLogic.values.addFieldFormErrors).toEqual(null); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + showAddFieldModal: false, + addFieldFormErrors: null, + }); }); it('resetSchemaState', () => { SchemaLogic.actions.resetSchemaState(); - expect(SchemaLogic.values.dataLoading).toEqual(true); + expect(SchemaLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: true, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); @@ -452,11 +493,24 @@ describe('SchemaLogic', () => { describe('selectors', () => { describe('filteredSchemaFields', () => { + const expectedValues = { + ...DEFAULT_VALUES, + dataLoading: false, + mostRecentIndexJob, + serverSchema: schema, + sourceId: contentSource.id, + }; + it('handles empty response', () => { SchemaLogic.actions.onInitializeSchema(serverResponse); SchemaLogic.actions.setFilterValue('baz'); - expect(SchemaLogic.values.filteredSchemaFields).toEqual({}); + expect(SchemaLogic.values).toEqual({ + ...expectedValues, + activeSchema: schema, + filterValue: 'baz', + filteredSchemaFields: {}, + }); }); it('handles filtered response', () => { @@ -468,7 +522,13 @@ describe('SchemaLogic', () => { SchemaLogic.actions.onFieldUpdate({ schema: newSchema, formUnchanged: false }); SchemaLogic.actions.setFilterValue('foo'); - expect(SchemaLogic.values.filteredSchemaFields).toEqual(schema); + expect(SchemaLogic.values).toEqual({ + ...expectedValues, + activeSchema: newSchema, + filterValue: 'foo', + filteredSchemaFields: schema, + formUnchanged: false, + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts index 0ccfd6aa63ae4..20a6ba238a2f4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../../../__mocks__/kea_logic'; import { fullContentSources } from '../../../../__mocks__/content_sources.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index 420909df081b3..5437ffe6ec290 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -34,7 +34,7 @@ describe('SourceLogic', () => { const contentSource = fullContentSources[0]; - const defaultValues = { + const DEFAULT_VALUES = { contentSource: {}, contentItems: [], dataLoading: true, @@ -58,15 +58,18 @@ describe('SourceLogic', () => { }); it('has expected default values', () => { - expect(SourceLogic.values).toEqual(defaultValues); + expect(SourceLogic.values).toEqual(DEFAULT_VALUES); }); describe('actions', () => { it('setContentSource', () => { SourceLogic.actions.setContentSource(contentSource); - expect(SourceLogic.values.contentSource).toEqual(contentSource); - expect(SourceLogic.values.dataLoading).toEqual(false); + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + contentSource, + dataLoading: false, + }); }); it('onUpdateSourceName', () => { @@ -74,9 +77,13 @@ describe('SourceLogic', () => { SourceLogic.actions.setContentSource(contentSource); SourceLogic.actions.onUpdateSourceName(NAME); - expect(SourceLogic.values.contentSource).toEqual({ - ...contentSource, - name: NAME, + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + contentSource: { + ...contentSource, + name: NAME, + }, }); expect(flashSuccessToast).toHaveBeenCalled(); }); @@ -84,9 +91,12 @@ describe('SourceLogic', () => { it('setSearchResults', () => { SourceLogic.actions.setSearchResults(searchServerResponse); - expect(SourceLogic.values.contentItems).toEqual(contentItems); - expect(SourceLogic.values.contentMeta).toEqual(meta); - expect(SourceLogic.values.sectionLoading).toEqual(false); + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + contentItems, + contentMeta: meta, + sectionLoading: false, + }); }); it('setContentFilterValue', () => { @@ -95,14 +105,22 @@ describe('SourceLogic', () => { SourceLogic.actions.setContentSource(contentSource); SourceLogic.actions.setContentFilterValue(VALUE); - expect(SourceLogic.values.contentMeta).toEqual({ - ...meta, - page: { - ...meta.page, - current: DEFAULT_META.page.current, + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + dataLoading: false, + sectionLoading: false, + contentItems, + contentSource, + + contentMeta: { + ...meta, + page: { + ...meta.page, + current: DEFAULT_META.page.current, + }, }, + contentFilterValue: VALUE, }); - expect(SourceLogic.values.contentFilterValue).toEqual(VALUE); }); it('setActivePage', () => { @@ -110,11 +128,16 @@ describe('SourceLogic', () => { SourceLogic.actions.setSearchResults(searchServerResponse); SourceLogic.actions.setActivePage(PAGE); - expect(SourceLogic.values.contentMeta).toEqual({ - ...meta, - page: { - ...meta.page, - current: PAGE, + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + contentItems, + sectionLoading: false, + contentMeta: { + ...meta, + page: { + ...meta.page, + current: PAGE, + }, }, }); }); @@ -124,13 +147,19 @@ describe('SourceLogic', () => { SourceLogic.actions.removeContentSource(contentSource.id); SourceLogic.actions.setButtonNotLoading(); - expect(SourceLogic.values.buttonLoading).toEqual(false); + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + buttonLoading: false, + }); }); it('showDiagnosticDownloadButton', () => { SourceLogic.actions.showDiagnosticDownloadButton(); - expect(SourceLogic.values.diagnosticDownloadButtonVisible).toEqual(true); + expect(SourceLogic.values).toEqual({ + ...DEFAULT_VALUES, + diagnosticDownloadButtonVisible: true, + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts index df61e23fa3cdc..1e2a9fc2de5a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/group_logic.test.ts @@ -14,7 +14,7 @@ import { import { groups } from '../../__mocks__/groups.mock'; import { mockGroupValues } from './__mocks__/group_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { GROUPS_PATH } from '../../routes'; @@ -45,13 +45,16 @@ describe('GroupLogic', () => { it('sets reducers', () => { GroupLogic.actions.onInitializeGroup(group); - expect(GroupLogic.values.group).toEqual(group); - expect(GroupLogic.values.dataLoading).toEqual(false); - expect(GroupLogic.values.groupNameInputValue).toEqual(group.name); - expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); - expect(GroupLogic.values.cachedSourcePriorities).toEqual(sourcePriorities); - expect(GroupLogic.values.activeSourcePriorities).toEqual(sourcePriorities); - expect(GroupLogic.values.groupPrioritiesUnchanged).toEqual(true); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + group, + dataLoading: false, + groupNameInputValue: group.name, + selectedGroupSources: sourceIds, + cachedSourcePriorities: sourcePriorities, + activeSourcePriorities: sourcePriorities, + groupPrioritiesUnchanged: true, + }); }); }); @@ -63,8 +66,11 @@ describe('GroupLogic', () => { }; GroupLogic.actions.onGroupNameChanged(renamedGroup); - expect(GroupLogic.values.group).toEqual(renamedGroup); - expect(GroupLogic.values.groupNameInputValue).toEqual(renamedGroup.name); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + group: renamedGroup, + groupNameInputValue: renamedGroup.name, + }); }); }); @@ -72,9 +78,12 @@ describe('GroupLogic', () => { it('sets reducers', () => { GroupLogic.actions.onGroupPrioritiesChanged(group); - expect(GroupLogic.values.dataLoading).toEqual(false); - expect(GroupLogic.values.cachedSourcePriorities).toEqual(sourcePriorities); - expect(GroupLogic.values.activeSourcePriorities).toEqual(sourcePriorities); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + cachedSourcePriorities: sourcePriorities, + activeSourcePriorities: sourcePriorities, + dataLoading: false, + }); }); }); @@ -83,7 +92,10 @@ describe('GroupLogic', () => { const name = 'new name'; GroupLogic.actions.onGroupNameInputChange(name); - expect(GroupLogic.values.groupNameInputValue).toEqual(name); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + groupNameInputValue: name, + }); }); }); @@ -91,7 +103,10 @@ describe('GroupLogic', () => { it('sets reducer', () => { GroupLogic.actions.addGroupSource(sourceIds[0]); - expect(GroupLogic.values.selectedGroupSources).toEqual([sourceIds[0]]); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + selectedGroupSources: [sourceIds[0]], + }); }); }); @@ -101,7 +116,10 @@ describe('GroupLogic', () => { GroupLogic.actions.addGroupSource(sourceIds[1]); GroupLogic.actions.removeGroupSource(sourceIds[0]); - expect(GroupLogic.values.selectedGroupSources).toEqual([sourceIds[1]]); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + selectedGroupSources: [sourceIds[1]], + }); }); }); @@ -109,11 +127,14 @@ describe('GroupLogic', () => { it('sets reducers', () => { GroupLogic.actions.onGroupSourcesSaved(group); - expect(GroupLogic.values.group).toEqual(group); - expect(GroupLogic.values.orgSourcesModalVisible).toEqual(false); - expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); - expect(GroupLogic.values.cachedSourcePriorities).toEqual(sourcePriorities); - expect(GroupLogic.values.activeSourcePriorities).toEqual(sourcePriorities); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + group, + orgSourcesModalVisible: false, + cachedSourcePriorities: sourcePriorities, + activeSourcePriorities: sourcePriorities, + selectedGroupSources: sourceIds, + }); }); }); @@ -122,7 +143,10 @@ describe('GroupLogic', () => { const errors = ['this is an error']; GroupLogic.actions.setGroupModalErrors(errors); - expect(GroupLogic.values.managerModalFormErrors).toEqual(errors); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + managerModalFormErrors: errors, + }); }); }); @@ -130,8 +154,11 @@ describe('GroupLogic', () => { it('sets reducers', () => { GroupLogic.actions.hideOrgSourcesModal(group); - expect(GroupLogic.values.orgSourcesModalVisible).toEqual(false); - expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + orgSourcesModalVisible: false, + selectedGroupSources: sourceIds, + }); }); }); @@ -139,7 +166,10 @@ describe('GroupLogic', () => { it('sets reducers', () => { GroupLogic.actions.selectAllSources(group.contentSources); - expect(GroupLogic.values.selectedGroupSources).toEqual(sourceIds); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + selectedGroupSources: sourceIds, + }); }); }); @@ -148,10 +178,11 @@ describe('GroupLogic', () => { const PRIORITY_VALUE = 4; GroupLogic.actions.updatePriority(sourceIds[0], PRIORITY_VALUE); - expect(GroupLogic.values.activeSourcePriorities).toEqual({ - [sourceIds[0]]: PRIORITY_VALUE, + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + activeSourcePriorities: { [sourceIds[0]]: PRIORITY_VALUE }, + groupPrioritiesUnchanged: false, }); - expect(GroupLogic.values.groupPrioritiesUnchanged).toEqual(false); }); }); @@ -159,8 +190,11 @@ describe('GroupLogic', () => { it('sets reducers', () => { GroupLogic.actions.resetGroup(); - expect(GroupLogic.values.group).toEqual({}); - expect(GroupLogic.values.dataLoading).toEqual(true); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + group: {}, + dataLoading: true, + }); }); }); @@ -169,7 +203,10 @@ describe('GroupLogic', () => { GroupLogic.actions.showConfirmDeleteModal(); GroupLogic.actions.hideConfirmDeleteModal(); - expect(GroupLogic.values.confirmDeleteModalVisible).toEqual(false); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + confirmDeleteModalVisible: false, + }); }); }); }); @@ -216,7 +253,18 @@ describe('GroupLogic', () => { http.delete.mockReturnValue(Promise.resolve(true)); GroupLogic.actions.deleteGroup(); - expect(GroupLogic.values.confirmDeleteModalVisible).toEqual(false); + + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + group, + dataLoading: false, + groupNameInputValue: group.name, + selectedGroupSources: sourceIds, + cachedSourcePriorities: sourcePriorities, + activeSourcePriorities: sourcePriorities, + groupPrioritiesUnchanged: true, + confirmDeleteModalVisible: false, + }); expect(http.delete).toHaveBeenCalledWith('/internal/workplace_search/groups/123'); await nextTick(); @@ -318,7 +366,10 @@ describe('GroupLogic', () => { it('sets reducer and clears flash messages', () => { GroupLogic.actions.showConfirmDeleteModal(); - expect(GroupLogic.values.confirmDeleteModalVisible).toEqual(true); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + confirmDeleteModalVisible: true, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); @@ -327,7 +378,10 @@ describe('GroupLogic', () => { it('sets reducer and clears flash messages', () => { GroupLogic.actions.showOrgSourcesModal(); - expect(GroupLogic.values.orgSourcesModalVisible).toEqual(true); + expect(GroupLogic.values).toEqual({ + ...mockGroupValues, + orgSourcesModalVisible: true, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts index 15951a9f8b9ca..97163f1529938 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups_logic.test.ts @@ -15,7 +15,7 @@ import { groups } from '../../__mocks__/groups.mock'; import { users } from '../../__mocks__/users.mock'; import { mockGroupsValues } from './__mocks__/groups_logic.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { JSON_HEADER as headers } from '../../../../../common/constants'; import { DEFAULT_META } from '../../../shared/constants'; @@ -51,9 +51,12 @@ describe('GroupsLogic', () => { it('sets reducers', () => { GroupsLogic.actions.onInitializeGroups({ contentSources, users }); - expect(GroupsLogic.values.groupsDataLoading).toEqual(false); - expect(GroupsLogic.values.contentSources).toEqual(contentSources); - expect(GroupsLogic.values.users).toEqual(users); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + groupsDataLoading: false, + contentSources, + users, + }); }); }); @@ -61,10 +64,13 @@ describe('GroupsLogic', () => { it('sets reducers', () => { GroupsLogic.actions.setSearchResults(groupsResponse); - expect(GroupsLogic.values.groups).toEqual(groups); - expect(GroupsLogic.values.groupListLoading).toEqual(false); - expect(GroupsLogic.values.newGroupName).toEqual(''); - expect(GroupsLogic.values.groupsMeta).toEqual(DEFAULT_META); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + groups, + groupListLoading: false, + newGroupName: '', + groupsMeta: DEFAULT_META, + }); }); }); @@ -74,7 +80,11 @@ describe('GroupsLogic', () => { GroupsLogic.actions.addFilteredSource('bar'); GroupsLogic.actions.addFilteredSource('baz'); - expect(GroupsLogic.values.filteredSources).toEqual(['bar', 'baz', 'foo']); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + hasFiltersSet: true, + filteredSources: ['bar', 'baz', 'foo'], + }); }); }); @@ -85,7 +95,11 @@ describe('GroupsLogic', () => { GroupsLogic.actions.addFilteredSource('baz'); GroupsLogic.actions.removeFilteredSource('foo'); - expect(GroupsLogic.values.filteredSources).toEqual(['bar', 'baz']); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + hasFiltersSet: true, + filteredSources: ['bar', 'baz'], + }); }); }); @@ -95,7 +109,11 @@ describe('GroupsLogic', () => { GroupsLogic.actions.addFilteredUser('bar'); GroupsLogic.actions.addFilteredUser('baz'); - expect(GroupsLogic.values.filteredUsers).toEqual(['bar', 'baz', 'foo']); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + hasFiltersSet: true, + filteredUsers: ['bar', 'baz', 'foo'], + }); }); }); @@ -106,7 +124,11 @@ describe('GroupsLogic', () => { GroupsLogic.actions.addFilteredUser('baz'); GroupsLogic.actions.removeFilteredUser('foo'); - expect(GroupsLogic.values.filteredUsers).toEqual(['bar', 'baz']); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + hasFiltersSet: true, + filteredUsers: ['bar', 'baz'], + }); }); }); @@ -114,8 +136,11 @@ describe('GroupsLogic', () => { it('sets reducers', () => { GroupsLogic.actions.setGroupUsers(users); - expect(GroupsLogic.values.allGroupUsersLoading).toEqual(false); - expect(GroupsLogic.values.allGroupUsers).toEqual(users); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + allGroupUsersLoading: false, + allGroupUsers: users, + }); }); }); @@ -123,8 +148,11 @@ describe('GroupsLogic', () => { it('sets reducer', () => { GroupsLogic.actions.setAllGroupLoading(true); - expect(GroupsLogic.values.allGroupUsersLoading).toEqual(true); - expect(GroupsLogic.values.allGroupUsers).toEqual([]); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + allGroupUsersLoading: true, + allGroupUsers: [], + }); }); }); @@ -132,7 +160,10 @@ describe('GroupsLogic', () => { it('sets reducer', () => { GroupsLogic.actions.setFilterValue('foo'); - expect(GroupsLogic.values.filterValue).toEqual('foo'); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + filterValue: 'foo', + }); }); }); @@ -141,8 +172,11 @@ describe('GroupsLogic', () => { const NEW_NAME = 'new name'; GroupsLogic.actions.setNewGroupName(NEW_NAME); - expect(GroupsLogic.values.newGroupName).toEqual(NEW_NAME); - expect(GroupsLogic.values.newGroupNameErrors).toEqual([]); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + newGroupName: NEW_NAME, + newGroupNameErrors: [], + }); }); }); @@ -150,12 +184,15 @@ describe('GroupsLogic', () => { it('sets reducer', () => { GroupsLogic.actions.setNewGroup(groups[0]); - expect(GroupsLogic.values.newGroupModalOpen).toEqual(false); - expect(GroupsLogic.values.newGroup).toEqual(groups[0]); - expect(GroupsLogic.values.newGroupNameErrors).toEqual([]); - expect(GroupsLogic.values.filteredSources).toEqual([]); - expect(GroupsLogic.values.filteredUsers).toEqual([]); - expect(GroupsLogic.values.groupsMeta).toEqual(DEFAULT_META); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + newGroupModalOpen: false, + newGroup: groups[0], + newGroupNameErrors: [], + filteredSources: [], + filteredUsers: [], + groupsMeta: DEFAULT_META, + }); }); }); @@ -164,7 +201,10 @@ describe('GroupsLogic', () => { const errors = ['this is an error']; GroupsLogic.actions.setNewGroupFormErrors(errors); - expect(GroupsLogic.values.newGroupNameErrors).toEqual(errors); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + newGroupNameErrors: errors, + }); }); }); @@ -172,9 +212,12 @@ describe('GroupsLogic', () => { it('sets reducer', () => { GroupsLogic.actions.closeNewGroupModal(); - expect(GroupsLogic.values.newGroupModalOpen).toEqual(false); - expect(GroupsLogic.values.newGroupName).toEqual(''); - expect(GroupsLogic.values.newGroupNameErrors).toEqual([]); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + newGroupNameErrors: [], + newGroupName: '', + newGroupModalOpen: false, + }); }); }); @@ -184,7 +227,10 @@ describe('GroupsLogic', () => { GroupsLogic.actions.toggleFilterSourcesDropdown(); GroupsLogic.actions.closeFilterSourcesDropdown(); - expect(GroupsLogic.values.filterSourcesDropdownOpen).toEqual(false); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + filterSourcesDropdownOpen: false, + }); }); }); @@ -194,7 +240,10 @@ describe('GroupsLogic', () => { GroupsLogic.actions.toggleFilterUsersDropdown(); GroupsLogic.actions.closeFilterUsersDropdown(); - expect(GroupsLogic.values.filterUsersDropdownOpen).toEqual(false); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + filterUsersDropdownOpen: false, + }); }); }); @@ -204,7 +253,11 @@ describe('GroupsLogic', () => { GroupsLogic.actions.setSearchResults(groupsResponse); GroupsLogic.actions.setGroupsLoading(); - expect(GroupsLogic.values.groupListLoading).toEqual(true); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + groups, + groupListLoading: true, + }); }); }); @@ -212,7 +265,10 @@ describe('GroupsLogic', () => { it('sets reducer', () => { GroupsLogic.actions.resetGroups(); - expect(GroupsLogic.values.newGroup).toEqual(null); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + newGroup: null, + }); }); }); }); @@ -339,11 +395,14 @@ describe('GroupsLogic', () => { const activePage = 3; GroupsLogic.actions.setActivePage(activePage); - expect(GroupsLogic.values.groupsMeta).toEqual({ - ...DEFAULT_META, - page: { - ...DEFAULT_META.page, - current: activePage, + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + groupsMeta: { + ...DEFAULT_META, + page: { + ...DEFAULT_META.page, + current: activePage, + }, }, }); @@ -355,8 +414,11 @@ describe('GroupsLogic', () => { it('sets reducer and clears flash messages', () => { GroupsLogic.actions.openNewGroupModal(); - expect(GroupsLogic.values.newGroupModalOpen).toEqual(true); - expect(GroupsLogic.values.newGroup).toEqual(null); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + newGroupModalOpen: true, + newGroup: null, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); @@ -365,10 +427,13 @@ describe('GroupsLogic', () => { it('sets reducer and clears flash messages', () => { GroupsLogic.actions.resetGroupsFilters(); - expect(GroupsLogic.values.filteredSources).toEqual([]); - expect(GroupsLogic.values.filteredUsers).toEqual([]); - expect(GroupsLogic.values.filterValue).toEqual(''); - expect(GroupsLogic.values.groupsMeta).toEqual(DEFAULT_META); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + filteredSources: [], + filteredUsers: [], + filterValue: '', + groupsMeta: DEFAULT_META, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); @@ -377,7 +442,10 @@ describe('GroupsLogic', () => { it('sets reducer and clears flash messages', () => { GroupsLogic.actions.toggleFilterSourcesDropdown(); - expect(GroupsLogic.values.filterSourcesDropdownOpen).toEqual(true); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + filterSourcesDropdownOpen: true, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); @@ -386,7 +454,10 @@ describe('GroupsLogic', () => { it('sets reducer and clears flash messages', () => { GroupsLogic.actions.toggleFilterUsersDropdown(); - expect(GroupsLogic.values.filterUsersDropdownOpen).toEqual(true); + expect(GroupsLogic.values).toEqual({ + ...mockGroupsValues, + filterUsersDropdownOpen: true, + }); expect(clearFlashMessages).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts index 747feb9bc5880..6e90d5de3db8a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/oauth_authorize/oauth_authorize_logic.test.ts @@ -11,7 +11,7 @@ import { mockHttpValues, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { OAuthAuthorizeLogic, transformServerPreAuth } from './oauth_authorize_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts index 64e87b77bacef..d74e255cf4591 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/overview_logic.test.ts @@ -42,18 +42,27 @@ describe('OverviewLogic', () => { }); it('will set `dataLoading` to false', () => { - expect(OverviewLogic.values.dataLoading).toEqual(false); + expect(OverviewLogic.values).toEqual({ + ...mockOverviewValues, + ...data, + dataLoading: false, + }); }); it('will set server values', () => { - expect(OverviewLogic.values.hasUsers).toEqual(true); - expect(OverviewLogic.values.hasOrgSources).toEqual(true); - expect(OverviewLogic.values.isOldAccount).toEqual(true); - expect(OverviewLogic.values.sourcesCount).toEqual(1); - expect(OverviewLogic.values.pendingInvitationsCount).toEqual(1); - expect(OverviewLogic.values.accountsCount).toEqual(1); - expect(OverviewLogic.values.privateSourcesCount).toEqual(1); - expect(OverviewLogic.values.activityFeed).toEqual(feed); + expect(OverviewLogic.values).toEqual({ + ...mockOverviewValues, + ...data, + dataLoading: false, + hasUsers: true, + hasOrgSources: true, + isOldAccount: true, + sourcesCount: 1, + pendingInvitationsCount: 1, + accountsCount: 1, + privateSourcesCount: 1, + activityFeed: feed, + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts index 9379c81afe199..b3246171ee5a7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings_logic.test.ts @@ -13,7 +13,7 @@ import { import { groups } from '../../__mocks__/groups.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { elasticsearchUsers } from '../../../shared/role_mapping/__mocks__/elasticsearch_users'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts index bc8e1942e7a1f..1aaa9ec622078 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/search_authorize/search_authorize_logic.test.ts @@ -11,7 +11,7 @@ import { mockHttpValues, } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { SearchAuthorizeLogic } from './search_authorize_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts index df9035d57e56b..5469376f1ca33 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/security_logic.test.ts @@ -7,7 +7,7 @@ import { LogicMounter, mockHttpValues } from '../../../__mocks__/kea_logic'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.test.tsx index 876891161b28b..b8f4c55bc7bab 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/components/branding_section.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; import { EuiFilePicker, EuiConfirmModal } from '@elastic/eui'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; jest.mock('../../../utils', () => ({ readUploadedFileAsBase64: jest.fn(({ img }) => img), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts index d98c9efe04d8c..f226434b8b8c0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/settings/settings_logic.test.ts @@ -13,7 +13,7 @@ import { } from '../../../__mocks__/kea_logic'; import { configuredSources, oauthApplication } from '../../__mocks__/content_sources.mock'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { itShowsServerErrorAsFlashMessage } from '../../../test_helpers'; import { ORG_UPDATED_MESSAGE, OAUTH_APP_UPDATED_MESSAGE } from '../../constants'; diff --git a/x-pack/plugins/file_upload/kibana.json b/x-pack/plugins/file_upload/kibana.json index f70bc6377972a..b4ee114645e1c 100644 --- a/x-pack/plugins/file_upload/kibana.json +++ b/x-pack/plugins/file_upload/kibana.json @@ -1,6 +1,6 @@ { "id": "fileUpload", - "version": "8.1.0", + "version": "8.2.0", "kibanaVersion": "kibana", "server": true, "ui": true, diff --git a/x-pack/plugins/fleet/README.md b/x-pack/plugins/fleet/README.md index 6c26cb29fc541..f67314ba49fa4 100644 --- a/x-pack/plugins/fleet/README.md +++ b/x-pack/plugins/fleet/README.md @@ -36,7 +36,7 @@ One common development workflow is: ``` - Start Elasticsearch in one shell ``` - yarn es snapshot -E xpack.security.authc.api_key.enabled=true + yarn es snapshot -E xpack.security.authc.api_key.enabled=true -E xpack.security.authc.token.enabled=true ``` - Start Kibana in another shell ``` @@ -58,6 +58,19 @@ _The following is adapted from the Fleet Server [README](https://github.com/elas ```yml server.host: 0.0.0.0 +xpack.fleet.agents.enabled: true +xpack.fleet.packages: + - name: fleet_server + version: latest +xpack.fleet.agentPolicies: + - name: Fleet Server policy + id: fleet-server-policy + description: Fleet server policy + namespace: default + package_policies: + - name: Fleet Server + package: + name: fleet_server ``` 2. Append the following option to the command you use to start Elasticsearch @@ -69,7 +82,7 @@ server.host: 0.0.0.0 This command should look something like this: ``` -yarn es snapshot --license trial -E xpack.security.authc.api_key.enabled=true -E path.data=/tmp/es-data -E http.host=0.0.0.0 +yarn es snapshot --license trial -E xpack.security.authc.api_key.enabled=true -E xpack.security.authc.token.enabled=true -E path.data=/tmp/es-data -E http.host=0.0.0.0 ``` 3. Run the Fleet Server Docker container. Make sure you include a `BASE-PATH` value if your local Kibana instance is using one. `YOUR-IP` should correspond to the IP address used by your Docker network to represent the host. For Windows and Mac machines, this should be `192.168.65.2`. If you're not sure what this IP should be, run the following to look it up: @@ -81,7 +94,7 @@ docker run -it --rm alpine nslookup host.docker.internal To run the Fleet Server Docker container: ``` -docker run -e KIBANA_HOST=http://{YOUR-IP}:5601/{BASE-PATH} -e KIBANA_USERNAME=elastic -e KIBANA_PASSWORD=changeme -e ELASTICSEARCH_HOST=http://{YOUR-IP}:9200 -e ELASTICSEARCH_USERNAME=elastic -e ELASTICSEARCH_PASSWORD=changeme -e KIBANA_FLEET_SETUP=1 -e FLEET_SERVER_ENABLE=1 -e FLEET_SERVER_INSECURE_HTTP=1 -p 8220:8220 docker.elastic.co/beats/elastic-agent:{VERSION} +docker run -e KIBANA_HOST=http://{YOUR-IP}:5601/{BASE-PATH} -e KIBANA_USERNAME=elastic -e KIBANA_PASSWORD=changeme -e ELASTICSEARCH_HOST=http://{YOUR-IP}:9200 -e KIBANA_FLEET_SETUP=1 -e FLEET_SERVER_ENABLE=1 -e FLEET_SERVER_INSECURE_HTTP=1 -e FLEET_SERVER_POLICY_ID=fleet-server-policy -p 8220:8220 docker.elastic.co/beats/elastic-agent:{VERSION} ``` Ensure you provide the `-p 8220:8220` port mapping to map the Fleet Server container's port `8220` to your local machine's port `8220` in order for Fleet to communicate with Fleet Server. diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts index 98056d6906c59..afb6a2f806f9a 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.test.ts @@ -606,6 +606,132 @@ describe('Fleet - validatePackagePolicy()', () => { }, }); }); + + it('returns package policy validation error if input var does not exist', () => { + expect( + validatePackagePolicy( + { + description: 'Linux Metrics', + enabled: true, + inputs: [ + { + enabled: true, + streams: [ + { + data_stream: { + dataset: 'linux.memory', + type: 'metrics', + }, + enabled: true, + }, + ], + type: 'linux/metrics', + vars: { + period: { + type: 'string', + value: '1s', + }, + }, + }, + ], + name: 'linux-3d13ada6-a9ae-46df-8e57-ff5050f4b671', + namespace: 'default', + output_id: '', + package: { + name: 'linux', + title: 'Linux Metrics', + version: '0.6.2', + }, + policy_id: 'b25cb6e0-8347-11ec-96f9-6590c25bacf9', + }, + { + ...mockPackage, + name: 'linux', + policy_templates: [ + { + name: 'system', + title: 'Linux kernel metrics', + description: 'Collect system metrics from Linux operating systems', + inputs: [ + { + title: 'Collect system metrics from Linux instances', + vars: [ + { + name: 'system.hostfs', + type: 'text', + title: 'Proc Filesystem Directory', + multi: false, + required: false, + show_user: true, + description: 'The proc filesystem base directory.', + }, + ], + type: 'system/metrics', + description: + 'Collecting Linux entropy, Network Summary, RAID, service, socket, and users metrics', + }, + { + title: 'Collect low-level system metrics from Linux instances', + vars: [], + type: 'linux/metrics', + description: 'Collecting Linux conntrack, ksm, pageinfo metrics.', + }, + ], + multiple: true, + }, + ], + data_streams: [ + { + dataset: 'linux.memory', + package: 'linux', + path: 'memory', + streams: [ + { + input: 'linux/metrics', + title: 'Linux memory metrics', + vars: [ + { + name: 'period', + type: 'text', + title: 'Period', + multi: false, + required: true, + show_user: true, + default: '10s', + }, + ], + template_path: 'stream.yml.hbs', + description: 'Linux paging and memory management metrics', + }, + ], + title: 'Linux-only memory metrics', + release: 'experimental', + type: 'metrics', + }, + ], + }, + safeLoad + ) + ).toEqual({ + description: null, + inputs: { + 'linux/metrics': { + streams: { + 'linux.memory': { + vars: { + period: ['Period is required'], + }, + }, + }, + vars: { + period: ['period var definition does not exist'], + }, + }, + }, + name: null, + namespace: null, + }); + }); }); describe('works for packages with multiple policy templates (aka integrations)', () => { diff --git a/x-pack/plugins/fleet/common/services/validate_package_policy.ts b/x-pack/plugins/fleet/common/services/validate_package_policy.ts index b6befdf8c790e..f1e28bfbe4e55 100644 --- a/x-pack/plugins/fleet/common/services/validate_package_policy.ts +++ b/x-pack/plugins/fleet/common/services/validate_package_policy.ts @@ -143,7 +143,7 @@ export const validatePackagePolicy = ( results[name] = input.enabled ? validatePackagePolicyConfig( configEntry, - inputVarDefsByPolicyTemplateAndType[inputKey][name], + (inputVarDefsByPolicyTemplateAndType[inputKey] ?? {})[name], name, safeLoadYaml ) @@ -210,10 +210,15 @@ export const validatePackagePolicyConfig = ( } if (varDef === undefined) { - // eslint-disable-next-line no-console - console.debug(`No variable definition for ${varName} found`); - - return null; + errors.push( + i18n.translate('xpack.fleet.packagePolicyValidation.nonExistentVarMessage', { + defaultMessage: '{varName} var definition does not exist', + values: { + varName, + }, + }) + ); + return errors; } if (varDef.required) { diff --git a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts index 0a35dff62c8db..ffb0f14c97a7f 100644 --- a/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/integrations_real.spec.ts @@ -34,14 +34,13 @@ describe('Add Integration - Real API', () => { }); it('should install integration without policy', () => { - cy.visit('/app/integrations/detail/tomcat-1.3.0/settings'); + cy.visit('/app/integrations/detail/tomcat/settings'); cy.get('.euiButton').contains('Install Apache Tomcat assets').click(); cy.get('.euiCallOut').contains('This action will install 1 assets'); cy.getBySel(CONFIRM_MODAL_BTN).click(); cy.get('.euiLoadingSpinner').should('not.exist'); - cy.getBySel('installedVersion').contains('1.3.0'); cy.get('.euiButton').contains('Uninstall Apache Tomcat').click(); cy.getBySel(CONFIRM_MODAL_BTN).click(); diff --git a/x-pack/plugins/fleet/cypress/integration/package_policy.spec.ts b/x-pack/plugins/fleet/cypress/integration/package_policy.spec.ts index 507b4edbcfc70..b2fa21371539d 100644 --- a/x-pack/plugins/fleet/cypress/integration/package_policy.spec.ts +++ b/x-pack/plugins/fleet/cypress/integration/package_policy.spec.ts @@ -10,7 +10,7 @@ describe('Edit package policy', () => { id: 'policy-1', name: 'fleet_server-1', namespace: 'default', - package: { name: 'fleet_server', title: 'Fleet Server', version: '1.1.1' }, + package: { name: 'fleet_server', title: 'Fleet Server', version: '1.1.0' }, enabled: true, policy_id: 'fleet-server-policy', output_id: 'fleet-default-output', @@ -64,6 +64,47 @@ describe('Edit package policy', () => { package_policies: [{ id: 'policy-1', name: 'fleet_server-1' }], }, }); + cy.intercept('/api/fleet/epm/packages/fleet_server*', { + item: { + name: 'fleet_server', + title: 'Fleet Server', + version: '1.1.0', + release: 'ga', + description: 'Centrally manage Elastic Agents with the Fleet Server integration', + type: 'integration', + download: '/epr/fleet_server/fleet_server-1.1.0.zip', + path: '/package/fleet_server/1.1.0', + icons: [], + conditions: { kibana: { version: '^7.16.0 || ^8.0.0' } }, + owner: { github: 'elastic/ingest-management' }, + categories: ['elastic_stack'], + format_version: '1.0.0', + readme: '/package/fleet_server/1.1.0/docs/README.md', + license: 'basic', + assets: {}, + policy_templates: [ + { + name: 'fleet_server', + title: 'Fleet Server', + description: 'Fleet Server setup', + inputs: [ + { + type: 'fleet-server', + vars: [], + title: 'Fleet Server', + description: 'Fleet Server Configuration', + template_path: 'agent.yml.hbs', + }, + ], + multiple: true, + }, + ], + latestVersion: '1.1.0', + removable: true, + keepPoliciesUpToDate: false, + status: 'not_installed', + }, + }); }); it('should edit package policy', () => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index 4bce393aebb14..8151215c3cced 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -64,10 +64,9 @@ import type { } from '../../../../../../common/types/rest_spec'; import type { PackagePolicyEditExtensionComponentProps } from '../../../types'; import { pkgKeyFromPackageInfo, storedPackagePoliciesToAgentInputs } from '../../../services'; - import { EuiButtonWithTooltip } from '../../../../integrations/sections/epm/screens/detail'; -import { hasUpgradeAvailable } from './utils'; +import { fixApmDurationVars, hasUpgradeAvailable } from './utils'; export const EditPackagePolicyPage = memo(() => { const { @@ -232,6 +231,10 @@ export const EditPackagePolicyForm = memo<{ ...basePolicyInputVars, }; } + // Fix duration vars, if it's a migrated setting, and it's a plain old number with no suffix + if (basePackage.name === 'apm') { + newVars = fixApmDurationVars(newVars); + } return { ...restOfInput, streams: streams.map((stream: any) => { diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/fix_apm_duration_vars.test.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/fix_apm_duration_vars.test.ts new file mode 100644 index 0000000000000..ab2a8238e7b37 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/fix_apm_duration_vars.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fixApmDurationVars } from './fix_apm_duration_vars'; + +describe('Edit Package Policy - fixApmDurationVars()', () => { + const mockApmVars = { + idle_timeout: { + type: 'text', + value: '45', + }, + read_timeout: { + type: 'text', + value: '3600', + }, + shutdown_timeout: { + type: 'text', + value: '30s', + }, + max_header_bytes: { + type: 'integer', + value: 3, + }, + url: { + type: 'text', + value: 'http://localhost:8200', + }, + }; + describe('when the APM var is a duration var', () => { + it('adds duration unit suffix to APM duration vars ', () => { + const newVars = fixApmDurationVars(mockApmVars); + expect(newVars.idle_timeout.value).toEqual('45s'); + }); + it('doesnt add the suffix if doesnt end with number', () => { + const newVars = fixApmDurationVars(mockApmVars); + expect(newVars.shutdown_timeout.value).toEqual(mockApmVars.shutdown_timeout.value); + }); + }); + describe('when the APM is not a duration var', () => { + it('keeps the same value', () => { + const newVars = fixApmDurationVars(mockApmVars); + expect(newVars.url.value).toEqual(mockApmVars.url.value); + expect(newVars.max_header_bytes.value).toEqual(mockApmVars.max_header_bytes.value); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/fix_apm_duration_vars.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/fix_apm_duration_vars.ts new file mode 100644 index 0000000000000..bea92f35f52cc --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/fix_apm_duration_vars.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PackagePolicyConfigRecord } from '../../../../../integrations/types'; +import { DURATION_APM_SETTINGS_VARS } from '../../../../constants'; + +// Fix duration vars, if it's a migrated setting, and it's a plain old number with no suffix, we should assume seconds +export function fixApmDurationVars(vars: PackagePolicyConfigRecord) { + const { IDLE_TIMEOUT, READ_TIMEOUT, SHUTDOWN_TIMEOUT, TAIL_SAMPLING_INTERVAL, WRITE_TIMEOUT } = + DURATION_APM_SETTINGS_VARS; + // convert vars to array, map each key/value pair into another pair + // and then fromEntries gives back the object + return Object.fromEntries( + Object.entries(vars).map((entry: any) => { + if ( + entry[0] === IDLE_TIMEOUT || + entry[0] === READ_TIMEOUT || + entry[0] === SHUTDOWN_TIMEOUT || + entry[0] === TAIL_SAMPLING_INTERVAL || + entry[0] === WRITE_TIMEOUT + ) { + // we add the unit seconds sufix as default + if (/[0-9]+$/.test(entry[1].value)) { + entry[1] = { ...entry[1], value: entry[1].value + 's' }; + return [entry[0], entry[1]]; + } + } + return entry; + }) + ); +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/index.ts index e8206e9dbbf97..be01b9834a7dc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/utils/index.ts @@ -6,3 +6,4 @@ */ export * from './has_upgrade_available'; +export { fixApmDurationVars } from './fix_apm_duration_vars'; diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/release_badge.ts b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/release_badge.ts index 547d920045b90..b08e43ec3d4c5 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/release_badge.ts +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/components/release_badge.ts @@ -14,7 +14,7 @@ export const RELEASE_BADGE_LABEL: { [key in Exclude]: str defaultMessage: 'Beta', }), experimental: i18n.translate('xpack.fleet.epm.releaseBadge.experimentalLabel', { - defaultMessage: 'Experimental', + defaultMessage: 'Technical preview', }), }; @@ -23,6 +23,7 @@ export const RELEASE_BADGE_DESCRIPTION: { [key in Exclude defaultMessage: 'This integration is not recommended for use in production environments.', }), experimental: i18n.translate('xpack.fleet.epm.releaseBadge.experimentalDescription', { - defaultMessage: 'This integration may have breaking changes or be removed in a future release.', + defaultMessage: + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', }), }; diff --git a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx index 0e6138aea7cb4..617bd95e18a6d 100644 --- a/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx +++ b/x-pack/plugins/fleet/public/components/agent_enrollment_flyout/agent_enrollment_flyout.test.tsx @@ -8,7 +8,7 @@ import './agent_enrollment_flyout.test.mocks'; import React from 'react'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import { coreMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/fleet/public/constants/index.ts b/x-pack/plugins/fleet/public/constants/index.ts index dddd7552f0151..6fa1a4161a066 100644 --- a/x-pack/plugins/fleet/public/constants/index.ts +++ b/x-pack/plugins/fleet/public/constants/index.ts @@ -29,3 +29,11 @@ export * from './page_paths'; export const INDEX_NAME = '.kibana'; export const CUSTOM_LOGS_INTEGRATION_NAME = 'log'; + +export const DURATION_APM_SETTINGS_VARS = { + IDLE_TIMEOUT: 'idle_timeout', + READ_TIMEOUT: 'read_timeout', + SHUTDOWN_TIMEOUT: 'shutdown_timeout', + TAIL_SAMPLING_INTERVAL: 'tail_sampling_interval', + WRITE_TIMEOUT: 'write_timeout', +}; diff --git a/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts b/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts new file mode 100644 index 0000000000000..bb34dc3258d05 --- /dev/null +++ b/x-pack/plugins/fleet/server/integration_tests/docker_registry_helper.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { spawn } from 'child_process'; +import type { ChildProcess } from 'child_process'; + +import fetch from 'node-fetch'; + +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +export function useDockerRegistry() { + const packageRegistryPort = process.env.FLEET_PACKAGE_REGISTRY_PORT || '8081'; + + if (!packageRegistryPort.match(/^[0-9]{4}/)) { + throw new Error('Invalid FLEET_PACKAGE_REGISTRY_PORT'); + } + + let dockerProcess: ChildProcess | undefined; + async function startDockerRegistryServer() { + const dockerImage = `docker.elastic.co/package-registry/distribution@sha256:de952debe048d903fc73e8a4472bb48bb95028d440cba852f21b863d47020c61`; + + const args = ['run', '--rm', '-p', `${packageRegistryPort}:8080`, dockerImage]; + + dockerProcess = spawn('docker', args, { stdio: 'inherit' }); + + let isExited = dockerProcess.exitCode !== null; + dockerProcess.once('exit', () => { + isExited = true; + }); + + let retries = 0; + while (!isExited && retries++ <= 20) { + try { + const res = await fetch(`http://localhost:${packageRegistryPort}/`); + if (res.status === 200) { + return; + } + } catch (err) { + // swallow errors + } + + await delay(3000); + } + + dockerProcess.kill(); + throw new Error('Unable to setup docker registry'); + } + + async function cleanupDockerRegistryServer() { + if (dockerProcess && !dockerProcess.killed) { + dockerProcess.kill(); + } + } + + beforeAll(async () => { + jest.setTimeout(5 * 60 * 1000); // 5 minutes timeout + await startDockerRegistryServer(); + }); + + afterAll(async () => { + await cleanupDockerRegistryServer(); + }); + + return `http://localhost:${packageRegistryPort}`; +} diff --git a/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts b/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts new file mode 100644 index 0000000000000..8907399adb628 --- /dev/null +++ b/x-pack/plugins/fleet/server/integration_tests/ha_setup.test.ts @@ -0,0 +1,306 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import Path from 'path'; + +import { range } from 'lodash'; + +import type { ISavedObjectsRepository } from 'src/core/server'; +import * as kbnTestServer from 'src/core/test_helpers/kbn_server'; + +import type { + AgentPolicySOAttributes, + Installation, + OutputSOAttributes, + PackagePolicySOAttributes, +} from '../types'; + +import { useDockerRegistry } from './docker_registry_helper'; + +const logFilePath = Path.join(__dirname, 'logs.log'); + +type Root = ReturnType; + +const startAndWaitForFleetSetup = async (root: Root) => { + const start = await root.start(); + + const isFleetSetupRunning = async () => { + const statusApi = kbnTestServer.getSupertest(root, 'get', '/api/status'); + const resp = await statusApi.send(); + const fleetStatus = resp.body?.status?.plugins?.fleet; + if (fleetStatus?.meta?.error) { + throw new Error(`Setup failed: ${JSON.stringify(fleetStatus)}`); + } + + return !fleetStatus || fleetStatus?.summary === 'Fleet is setting up'; + }; + + while (await isFleetSetupRunning()) { + await new Promise((resolve) => setTimeout(resolve, 2000)); + } + + return start; +}; + +const createAndSetupRoot = async (config?: object) => { + const root = kbnTestServer.createRootWithCorePlugins( + { + xpack: { + fleet: config, + }, + logging: { + appenders: { + file: { + type: 'file', + fileName: logFilePath, + layout: { + type: 'json', + }, + }, + }, + loggers: [ + { + name: 'root', + appenders: ['file'], + }, + { + name: 'plugins.fleet', + level: 'all', + }, + ], + }, + }, + { oss: false } + ); + + await root.preboot(); + await root.setup(); + return root; +}; + +/** + * Verifies that multiple Kibana instances running in parallel will not create duplicate preconfiguration objects. + */ +describe('Fleet setup preconfiguration with multiple instances Kibana', () => { + let esServer: kbnTestServer.TestElasticsearchUtils; + // let esClient: Client; + let roots: Root[] = []; + + const registryUrl = useDockerRegistry(); + + const startServers = async () => { + const { startES } = kbnTestServer.createTestServers({ + adjustTimeout: (t) => jest.setTimeout(t), + settings: { + es: { + license: 'trial', + }, + }, + }); + + esServer = await startES(); + }; + + const addRoots = async (n: number) => { + const newRoots = await Promise.all(range(n).map(() => createAndSetupRoot(preconfiguration))); + newRoots.forEach((r) => roots.push(r)); + return newRoots; + }; + + const startRoots = async () => { + return await Promise.all(roots.map(startAndWaitForFleetSetup)); + }; + + const stopServers = async () => { + for (const root of roots) { + await root.shutdown(); + } + roots = []; + + if (esServer) { + await esServer.stop(); + } + + await new Promise((res) => setTimeout(res, 10000)); + }; + + beforeEach(async () => { + await startServers(); + }); + + afterEach(async () => { + await stopServers(); + }); + + describe('preconfiguration setup', () => { + it('sets up Fleet correctly with single Kibana instance', async () => { + await addRoots(1); + const [root1Start] = await startRoots(); + const soClient = root1Start.savedObjects.createInternalRepository(); + await expectFleetSetupState(soClient); + }); + + it('sets up Fleet correctly when multiple Kibana instances are started at the same time', async () => { + await addRoots(3); + const [root1Start] = await startRoots(); + const soClient = root1Start.savedObjects.createInternalRepository(); + await expectFleetSetupState(soClient); + }); + + it('sets up Fleet correctly when multiple Kibana instaces are started in serial', async () => { + const [root1] = await addRoots(1); + const root1Start = await startAndWaitForFleetSetup(root1); + const soClient = root1Start.savedObjects.createInternalRepository(); + await expectFleetSetupState(soClient); + + const [root2] = await addRoots(1); + await startAndWaitForFleetSetup(root2); + await expectFleetSetupState(soClient); + + const [root3] = await addRoots(1); + await startAndWaitForFleetSetup(root3); + await expectFleetSetupState(soClient); + }); + }); + + const preconfiguration = { + registryUrl, + packages: [ + { + name: 'fleet_server', + version: 'latest', + }, + { + name: 'apm', + version: 'latest', + }, + ], + outputs: [ + { + name: 'Preconfigured output', + id: 'preconfigured-output', + type: 'elasticsearch', + hosts: ['http://127.0.0.1:9200'], + }, + ], + agentPolicies: [ + { + name: 'managed-test', + id: 'managed-policy-test', + data_output_id: 'preconfigured-output', + monitoring_output_id: 'preconfigured-output', + is_managed: true, + is_default_fleet_server: true, + package_policies: [ + { + name: 'fleet-server-123', + package: { + name: 'fleet_server', + }, + inputs: [ + { + type: 'fleet-server', + keep_enabled: true, + vars: [{ name: 'host', value: '127.0.0.1:8220', frozen: true }], + }, + ], + }, + ], + }, + { + name: 'nonmanaged-test', + id: 'nonmanaged-policy-test', + is_managed: false, + package_policies: [ + { + name: 'apm-123', + package: { + name: 'apm', + }, + inputs: [ + { + type: 'apm', + keep_enabled: true, + vars: [ + { name: 'api_key_enabled', value: true }, + { name: 'host', value: '0.0.0.0:8200', frozen: true }, + ], + }, + ], + }, + ], + }, + ], + }; + + async function expectFleetSetupState(soClient: ISavedObjectsRepository) { + // Assert setup state + const agentPolicies = await soClient.find({ + type: 'ingest-agent-policies', + perPage: 10000, + }); + expect(agentPolicies.saved_objects).toHaveLength(2); + expect(agentPolicies.saved_objects.map((ap) => ap.attributes)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'managed-test', + is_managed: true, + is_default_fleet_server: true, + data_output_id: 'preconfigured-output', + }), + expect.objectContaining({ + name: 'nonmanaged-test', + is_managed: false, + }), + ]) + ); + + const packagePolicies = await soClient.find({ + type: 'ingest-package-policies', + perPage: 10000, + }); + expect(packagePolicies.saved_objects).toHaveLength(2); + expect(packagePolicies.saved_objects.map((pp) => pp.attributes.name)).toEqual( + expect.arrayContaining(['apm-123', 'fleet-server-123']) + ); + + const outputs = await soClient.find({ + type: 'ingest-outputs', + perPage: 10000, + }); + expect(outputs.saved_objects).toHaveLength(2); + expect(outputs.saved_objects.map((o) => o.attributes)).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'default', + is_default: true, + is_default_monitoring: true, + type: 'elasticsearch', + output_id: 'fleet-default-output', + hosts: ['http://localhost:9200'], + }), + expect.objectContaining({ + name: 'Preconfigured output', + is_default: false, + is_default_monitoring: false, + type: 'elasticsearch', + output_id: 'preconfigured-output', + hosts: ['http://127.0.0.1:9200'], + }), + ]) + ); + + const packages = await soClient.find({ + type: 'epm-packages', + perPage: 10000, + }); + expect(packages.saved_objects).toHaveLength(2); + expect(packages.saved_objects.map((p) => p.attributes.name)).toEqual( + expect.arrayContaining(['fleet_server', 'apm']) + ); + } +}); diff --git a/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts b/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts index 9efbacfae17bf..4a2b4f2495e96 100644 --- a/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/reset_preconfiguration.test.ts @@ -14,6 +14,8 @@ import type { HttpMethod } from 'src/core/test_helpers/kbn_server'; import type { AgentPolicySOAttributes } from '../types'; +import { useDockerRegistry } from './docker_registry_helper'; + const logFilePath = Path.join(__dirname, 'logs.log'); type Root = ReturnType; @@ -46,6 +48,8 @@ describe('Fleet preconfiguration reset', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let kbnServer: kbnTestServer.TestKibanaUtils; + const registryUrl = useDockerRegistry(); + const startServers = async () => { const { startES } = kbnTestServer.createTestServers({ adjustTimeout: (t) => jest.setTimeout(t), @@ -63,6 +67,7 @@ describe('Fleet preconfiguration reset', () => { { xpack: { fleet: { + registryUrl, packages: [ { name: 'fleet_server', @@ -195,8 +200,7 @@ describe('Fleet preconfiguration reset', () => { await stopServers(); }); - // FLAKY: https://github.com/elastic/kibana/issues/123103 - describe.skip('Reset all policy', () => { + describe('Reset all policy', () => { it('Works and reset all preconfigured policies', async () => { const resetAPI = getSupertestWithAdminUser( kbnServer.root, @@ -225,9 +229,7 @@ describe('Fleet preconfiguration reset', () => { }); }); - // FLAKY: https://github.com/elastic/kibana/issues/123104 - // FLAKY: https://github.com/elastic/kibana/issues/123105 - describe.skip('Reset one preconfigured policy', () => { + describe('Reset one preconfigured policy', () => { const POLICY_ID = 'test-12345'; it('Works and reset one preconfigured policies if the policy is already deleted (with a ghost package policy)', async () => { diff --git a/x-pack/plugins/fleet/server/integration_tests/router.test.ts b/x-pack/plugins/fleet/server/integration_tests/router.test.ts deleted file mode 100644 index eb002f5d731d8..0000000000000 --- a/x-pack/plugins/fleet/server/integration_tests/router.test.ts +++ /dev/null @@ -1,208 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -test.skip('requires one test', () => {}); - -/** - * skipped due to all being flaky: https://github.com/elastic/kibana/issues/58954 - * - * commented out due to hooks being called regardless of skip - * https://github.com/facebook/jest/issues/8379 - -import { resolve } from 'path'; -import * as kbnTestServer from '../../../../../src/test_utils/kbn_server'; - -function createXPackRoot(config: {} = {}) { - return kbnTestServer.createRoot({ - plugins: { - paths: [ - resolve(__dirname, '../../../../../x-pack/plugins/encrypted_saved_objects'), - resolve(__dirname, '../../../../../x-pack/plugins/fleet'), - resolve(__dirname, '../../../../../x-pack/plugins/licensing'), - ], - }, - migrations: { skip: true }, - xpack: config, - }); -} - -describe('fleet', () => { - describe('default. manager, EPM, and Fleet all disabled', () => { - let root: ReturnType; - - beforeAll(async () => { - root = createXPackRoot(); - await root.preboot(); - await root.setup(); - await root.start(); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('does not have agent policy api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agent_policies').expect(404); - }); - - it('does not have package policies api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/package_policies').expect(404); - }); - - it('does not have EPM api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/epm/packages').expect(404); - }); - - it('does not have Fleet api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agents/setup').expect(404); - }); - }); - - describe('manager only (no EPM, no Fleet)', () => { - let root: ReturnType; - - beforeAll(async () => { - const fleetConfig = { - enabled: true, - }; - root = createXPackRoot({ - fleet: fleetConfig, - }); - await root.preboot(); - await root.setup(); - await root.start(); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('has agent policy api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agent_policies').expect(200); - }); - - it('has package policies api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/package_policies').expect(200); - }); - - it('does not have EPM api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/epm/packages').expect(404); - }); - - it('does not have Fleet api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agents/setup').expect(404); - }); - }); - - // For now, only the manager routes (/agent_policies & /package_policies) are added - // EPM and ingest will be conditionally added when we enable these lines - // https://github.com/jfsiii/kibana/blob/f73b54ebb7e0f6fc00efd8a6800a01eb2d9fb772/x-pack/plugins/fleet/server/plugin.ts#L84 - // adding tests to confirm the Fleet & EPM routes are never added - - describe('manager and EPM; no Fleet', () => { - let root: ReturnType; - - beforeAll(async () => { - const fleetConfig = { - enabled: true, - epm: { enabled: true }, - }; - root = createXPackRoot({ - fleet: fleetConfig, - }); - await root.preboot(); - await root.setup(); - await root.start(); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('has agent policy api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agent_policies').expect(200); - }); - - it('has package policies api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/package_policies').expect(200); - }); - - it('does have EPM api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/epm/packages').expect(500); - }); - - it('does not have Fleet api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agents/setup').expect(404); - }); - }); - - describe('manager and Fleet; no EPM)', () => { - let root: ReturnType; - - beforeAll(async () => { - const fleetConfig = { - enabled: true, - fleet: { enabled: true }, - }; - root = createXPackRoot({ - fleet: fleetConfig, - }); - await root.preboot(); - await root.setup(); - await root.start(); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('has agent policy api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agent_policies').expect(200); - }); - - it('has package policies api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/package_policies').expect(200); - }); - - it('does not have EPM api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/epm/packages').expect(404); - }); - - it('does have Fleet api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agents/setup').expect(200); - }); - }); - - describe('all flags enabled: manager, EPM, and Fleet)', () => { - let root: ReturnType; - - beforeAll(async () => { - const fleetConfig = { - enabled: true, - epm: { enabled: true }, - fleet: { enabled: true }, - }; - root = createXPackRoot({ - fleet: fleetConfig, - }); - await root.preboot(); - await root.setup(); - await root.start(); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('has agent policy api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agent_policies').expect(200); - }); - - it('has package policies api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/package_policies').expect(200); - }); - - it('does have EPM api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/epm/packages').expect(500); - }); - - it('does have Fleet api', async () => { - await kbnTestServer.request.get(root, '/api/fleet/agents/setup').expect(200); - }); - }); -}); -*/ diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index d8c2032596e34..06d700c3577b5 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -348,7 +348,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( policy!, packagePoliciesToAdd!, defaultOutput, - !created + true ); // Add the is_managed flag after configuring package policies to avoid errors diff --git a/x-pack/plugins/fleet/server/telemetry/sender.ts b/x-pack/plugins/fleet/server/telemetry/sender.ts index 473ff470842bf..2377f5e016deb 100644 --- a/x-pack/plugins/fleet/server/telemetry/sender.ts +++ b/x-pack/plugins/fleet/server/telemetry/sender.ts @@ -176,7 +176,7 @@ export class TelemetryEventsSender { this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); } catch (err) { this.logger.debug( - `Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}` + `Error sending events: ${err?.response?.status} ${JSON.stringify(err.response.data)}` ); } } diff --git a/x-pack/plugins/global_search/public/services/utils.test.ts b/x-pack/plugins/global_search/public/services/utils.test.ts index 8f22ee8082e81..99d55bf608ebf 100644 --- a/x-pack/plugins/global_search/public/services/utils.test.ts +++ b/x-pack/plugins/global_search/public/services/utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { StubBrowserStorage } from '@kbn/test/jest'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; import { getDefaultPreference } from './utils'; describe('getDefaultPreference', () => { diff --git a/x-pack/plugins/global_search_bar/kibana.json b/x-pack/plugins/global_search_bar/kibana.json index 94b8aba2dd7f9..538b003a29ef0 100644 --- a/x-pack/plugins/global_search_bar/kibana.json +++ b/x-pack/plugins/global_search_bar/kibana.json @@ -4,7 +4,7 @@ "name": "Kibana Core", "githubTeam": "kibana-core" }, - "version": "8.1.0", + "version": "8.2.0", "kibanaVersion": "kibana", "server": false, "ui": true, diff --git a/x-pack/plugins/graph/kibana.json b/x-pack/plugins/graph/kibana.json index e8b651a9eb0ea..b4b1e9c21a235 100644 --- a/x-pack/plugins/graph/kibana.json +++ b/x-pack/plugins/graph/kibana.json @@ -1,6 +1,6 @@ { "id": "graph", - "version": "8.1.0", + "version": "8.2.0", "kibanaVersion": "kibana", "server": true, "ui": true, diff --git a/x-pack/plugins/graph/public/components/search_bar.test.tsx b/x-pack/plugins/graph/public/components/search_bar.test.tsx index 7d39e8027e92b..58a2996fd1e67 100644 --- a/x-pack/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/plugins/graph/public/components/search_bar.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { SearchBar, SearchBarProps } from './search_bar'; import React, { Component, ReactElement } from 'react'; import { CoreStart } from 'src/core/public'; diff --git a/x-pack/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/plugins/graph/public/components/settings/settings.test.tsx index 1c3fbb9f54f67..96d5a2a5b2bac 100644 --- a/x-pack/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/plugins/graph/public/components/settings/settings.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiTab, EuiListGroupItem, EuiButton, EuiAccordion, EuiFieldText } from '@elastic/eui'; import * as Rx from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { Settings, SettingsWorkspaceProps } from './settings'; import { act } from '@testing-library/react'; import { ReactWrapper } from 'enzyme'; diff --git a/x-pack/plugins/grokdebugger/kibana.json b/x-pack/plugins/grokdebugger/kibana.json index f8cb75f7d10b6..bbba9651882fd 100644 --- a/x-pack/plugins/grokdebugger/kibana.json +++ b/x-pack/plugins/grokdebugger/kibana.json @@ -1,6 +1,6 @@ { "id": "grokdebugger", - "version": "8.1.0", + "version": "8.2.0", "kibanaVersion": "kibana", "owner": { "name": "Stack Management", diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx index 8d55a3e093a31..153f1c09c53a9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.helpers.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, TestBedConfig } from '@kbn/test-jest-helpers'; import { docLinksServiceMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { createBreadcrumbsMock } from '../../../public/application/services/breadcrumbs.mock'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/edit_warning.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/edit_warning.test.ts index 521d5d4da8cef..83b3a9fc53398 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/edit_warning.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/edit_warning.test.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { setupEnvironment } from '../../helpers'; import { initTestBed } from '../init_test_bed'; import { getDefaultHotPhasePolicy, POLICY_NAME } from '../constants'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts index aaa2b3dafddde..57a70237be6e1 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/frozen_phase.test.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { licensingMock } from '../../../../../licensing/public/mocks'; import { setupEnvironment } from '../../helpers'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts index 9741ee87bda10..a0a1c80c90bbe 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/node_allocation/cloud_aware_behavior.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBedConfig } from '@kbn/test/jest'; +import { TestBedConfig } from '@kbn/test-jest-helpers'; import { AppServicesContext } from '../../../../../public/types'; import { createTogglePhaseAction, createNodeAllocationActions } from '../../../helpers'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts index 84ee96cd46987..5b5d3819c3463 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBedConfig } from '@kbn/test/jest'; +import { TestBedConfig } from '@kbn/test-jest-helpers'; import { createColdPhaseActions, createDeletePhaseActions, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx index 251ff234c230e..64d5cd65cda68 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/init_test_bed.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBedConfig } from '@kbn/test-jest-helpers'; import { docLinksServiceMock } from 'src/core/public/mocks'; import '../helpers/global_mocks'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts index 34d07d4a36ff9..4b863071e191c 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/errors_actions.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; const createWaitForValidationAction = (testBed: TestBed) => () => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts index f2a27aba9ec56..b6ed40de36ca9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/forcemerge_actions.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_set_value_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_set_value_action.ts index 95e90ac04192b..57a41831406f9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_set_value_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_set_value_action.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; export function createFormSetValueAction( testBed: TestBed, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_action.ts index 6bf47f5d513f6..b2408583cc3aa 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_action.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; export const createFormToggleAction = (testBed: TestBed, dataTestSubject: string) => async () => { const { form, component } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts index b3510466b4b90..a5c436b13ee0f 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/form_toggle_and_set_value_action.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts index eeab42c408244..79fb77e53cc59 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/index_priority_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; import { createFormToggleAndSetValueAction } from './form_toggle_and_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts index 551474c8b16e6..ef00fdc8d7757 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/min_age_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; import { createFormSetValueAction } from './form_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts index 7c0f8fea7299d..2a680590654a8 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/node_allocation_actions.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { DataTierAllocationType } from '../../../../public/application/sections/edit_policy/types'; import { Phase } from '../../../../common/types'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts index 7f07480cc248d..4bc223943c6c7 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { createForceMergeActions, createShrinkActions, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts index b76143eccf1f2..1a7ec56815b00 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/readonly_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts index f987ce6d0ca2f..43eca26a4e1ce 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; import { createFormToggleAndSetValueAction } from './form_toggle_and_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/request_flyout_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/request_flyout_actions.ts index 87e66ea71e0e4..ca79d52741595 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/request_flyout_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/request_flyout_actions.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; const jsonSelector = 'policyRequestJson'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts index 93a1b959ec969..683ba31843829 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/rollover_actions.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { createFormToggleAction } from './form_toggle_action'; import { createFormSetValueAction } from './form_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/save_policy_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/save_policy_action.ts index 3fcd1205b6e1a..9c27e38d75e0d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/save_policy_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/save_policy_action.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; export const createSavePolicyAction = (testBed: TestBed) => async () => { const { find, component } = testBed; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts index f1a64c3943511..3efffcddbece6 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/searchable_snapshot_actions.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { Phase } from '../../../../common/types'; import { createFormToggleAction } from './form_toggle_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts index 394a64696d5eb..def20f73b82fe 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/shrink_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { Phase } from '../../../../common/types'; import { createFormSetValueAction } from './form_set_value_action'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts index 37e0ef17d2254..8aaf968560a1d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/target_types/jest'; +import type { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; const createSetWaitForSnapshotAction = (testBed: TestBed) => async (snapshotPolicyName: string) => { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts index ce5b8b234e088..c22efae87d5ac 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/toggle_phase_action.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { Phase } from '../../../../common/types'; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx index a38c6e1af5c2a..240cc18fdc02d 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/extend_index_management.test.tsx @@ -9,7 +9,7 @@ import moment from 'moment-timezone'; import axios from 'axios'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { usageCollectionPluginMock } from '../../../../src/plugins/usage_collection/public/mocks'; import { Index } from '../common/types'; import { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx index 1a45b2c6d93dc..3a1c2dd36812f 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/policy_table.test.tsx @@ -8,7 +8,7 @@ import moment from 'moment-timezone'; import React, { ReactElement } from 'react'; import { ReactWrapper } from 'enzyme'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test'; import { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts index 79df6e8e9f20c..ba2dea966d8cb 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/index.ts @@ -7,8 +7,8 @@ import './mocks'; -export type { TestBed } from '@kbn/test/jest'; -export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { setupEnvironment, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts index e3184cadbdc49..e3295a8f4fb18 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts @@ -9,7 +9,12 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { EuiDescriptionListDescription } from '@elastic/eui'; -import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, +} from '@kbn/test-jest-helpers'; import { DataStream } from '../../../common'; import { IndexManagementHome } from '../../../public/application/sections/home'; import { indexManagementStore } from '../../../public/application/store'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts index ad8aceb7d56b8..46287fcdcf074 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { IndexManagementHome } from '../../../public/application/sections/home'; import { indexManagementStore } from '../../../public/application/store'; import { WithAppDependencies, services, TestSubjects } from '../helpers'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts index 426bb11f3c733..60d4b7d3f2317 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/home.test.ts @@ -16,7 +16,7 @@ import { HomeTestBed, setup } from './home.helpers'; Could not load worker ReferenceError: Worker is not defined at createWorker (//node_modules/brace/index.js:17992:5) */ -import { stubWebWorker } from '@kbn/test/jest'; +import { stubWebWorker } from '@kbn/test-jest-helpers'; stubWebWorker(); describe('', () => { diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts index 4ddd14562577a..69dcabc287d6b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/index_templates_tab.helpers.ts @@ -7,7 +7,12 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, +} from '@kbn/test-jest-helpers'; import { TemplateList } from '../../../public/application/sections/home/template_list'; import { TemplateDeserialized } from '../../../common'; import { WithAppDependencies, TestSubjects } from '../helpers'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index 018025b25246b..719758e18525a 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -8,7 +8,12 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, +} from '@kbn/test-jest-helpers'; import { IndexManagementHome } from '../../../public/application/sections/home'; import { indexManagementStore } from '../../../public/application/store'; import { WithAppDependencies, services, TestSubjects } from '../helpers'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index 9e3f323ca45ea..6387a0904a69c 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -43,7 +43,7 @@ jest.mock('../../../public/application/lib/ace.js', () => { Could not load worker ReferenceError: Worker is not defined at createWorker (//node_modules/brace/index.js:17992:5) */ -import { stubWebWorker } from '@kbn/test/jest'; +import { stubWebWorker } from '@kbn/test-jest-helpers'; import { createMemoryHistory } from 'history'; stubWebWorker(); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts index dffa6fee19d06..9aec6cae7a17e 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { TemplateClone } from '../../../public/application/sections/template_clone'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts index 450d2c524b445..b039fa83000ed 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { TemplateCreate } from '../../../public/application/sections/template_create'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts index 6c73da3b3379d..a7f87d828eb23 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { TemplateEdit } from '../../../public/application/sections/template_edit'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts index d14855354f0ad..57d0b282d351d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_form.helpers.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { TestBed, SetupFunc } from '@kbn/test/jest'; +import { TestBed, SetupFunc } from '@kbn/test-jest-helpers'; import { TemplateDeserialized } from '../../../common'; interface MappingField { diff --git a/x-pack/plugins/index_management/__jest__/components/index_table.test.js b/x-pack/plugins/index_management/__jest__/components/index_table.test.js index 5e5538fcca4e8..f98c891e5f4f5 100644 --- a/x-pack/plugins/index_management/__jest__/components/index_table.test.js +++ b/x-pack/plugins/index_management/__jest__/components/index_table.test.js @@ -19,7 +19,7 @@ import axiosXhrAdapter from 'axios/lib/adapters/xhr'; Could not load worker ReferenceError: Worker is not defined at createWorker (//node_modules/brace/index.js:17992:5) */ -import { mountWithIntl, stubWebWorker } from '@kbn/test/jest'; // eslint-disable-line no-unused-vars +import { mountWithIntl, stubWebWorker } from '@kbn/test-jest-helpers'; // eslint-disable-line no-unused-vars import { BASE_PATH, API_BASE_PATH } from '../../common/constants'; import { AppWithoutRouter } from '../../public/application/app'; diff --git a/x-pack/plugins/index_management/common/constants/plugin.ts b/x-pack/plugins/index_management/common/constants/plugin.ts index 060d42ca26b02..482661045b3fa 100644 --- a/x-pack/plugins/index_management/common/constants/plugin.ts +++ b/x-pack/plugins/index_management/common/constants/plugin.ts @@ -22,4 +22,4 @@ export const PLUGIN = { // "PluginInitializerContext.env.packageInfo.version". In some cases it is not possible // to dynamically inject that version without a huge refactor on the code base. // We will then keep this single constant to declare on which major branch we are. -export const MAJOR_VERSION = '8.1.0'; +export const MAJOR_VERSION = '8.2.0'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts index 06f0036cc5c77..18b5bbfd775bb 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_create.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { BASE_PATH } from '../../../../../../../common'; import { ComponentTemplateCreate } from '../../../component_template_wizard'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts index 49bef82ce3d11..cdf376028ff1d 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_details.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { WithAppDependencies } from './setup_environment'; import { ComponentTemplateDetailsFlyoutContent } from '../../../component_template_details'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts index e7b8df245aaa9..6e0f9d55ef7f0 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { BASE_PATH } from '../../../../../../../common'; import { ComponentTemplateEdit } from '../../../component_template_wizard'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts index 578a124125107..ce97ac9d4e222 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_form.helpers.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; interface MappingField { name: string; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts index 680550d16096b..2a01518e25466 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/component_template_list.helpers.ts @@ -13,7 +13,7 @@ import { AsyncTestBedConfig, findTestSubject, nextTick, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; import { BASE_PATH } from '../../../../../../../common'; import { WithAppDependencies } from './setup_environment'; import { ComponentTemplateList } from '../../../component_template_list/component_template_list'; diff --git a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts index 5185878ee98bc..72c3b8af534a5 100644 --- a/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/component_templates/__jest__/client_integration/helpers/index.ts @@ -8,7 +8,7 @@ import { setup as componentTemplatesListSetup } from './component_template_list.helpers'; import { setup as componentTemplateDetailsSetup } from './component_template_details.helpers'; -export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { setupEnvironment, componentTemplatesDependencies } from './setup_environment'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index d436492756659..5244cdb65bb0f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -12,8 +12,8 @@ import { getMappingsEditorDataFactory, } from './mappings_editor.helpers'; -export type { TestBed } from '@kbn/test/jest'; -export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { kibanaVersion } from './setup_environment'; export const componentHelpers = { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx index 074d96e9be4c1..1e3cfd99cedb4 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { registerTestBed, TestBed, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, TestBed, findTestSubject } from '@kbn/test-jest-helpers'; // This import needs to come first as it sets the jest.mock calls import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx index 8259c78b8e140..c2502f14facfb 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx @@ -18,7 +18,7 @@ jest.mock('lodash', () => { }; }); -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { LoadMappingsProvider } from './load_mappings_provider'; const ComponentToTest = ({ onJson }: { onJson: () => void }) => ( diff --git a/x-pack/plugins/index_management/server/lib/fetch_indices.ts b/x-pack/plugins/index_management/server/lib/fetch_indices.ts index 5050353f992b6..7b25956703db2 100644 --- a/x-pack/plugins/index_management/server/lib/fetch_indices.ts +++ b/x-pack/plugins/index_management/server/lib/fetch_indices.ts @@ -30,6 +30,9 @@ async function fetchIndicesCall( '*.settings.index.hidden', '*.data_stream', ], + // for better performance only compute aliases and settings of indices but not mappings + // @ts-expect-error new param https://github.com/elastic/elasticsearch-specification/issues/1382 + features: ['aliases', 'settings'], }); if (!Object.keys(indices).length) { diff --git a/x-pack/plugins/index_management/test/fixtures/template.ts b/x-pack/plugins/index_management/test/fixtures/template.ts index dd279cf4bcaec..ae9be96015735 100644 --- a/x-pack/plugins/index_management/test/fixtures/template.ts +++ b/x-pack/plugins/index_management/test/fixtures/template.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString, getRandomNumber } from '@kbn/test/jest'; +import { getRandomString, getRandomNumber } from '@kbn/test-jest-helpers'; import { TemplateDeserialized, TemplateType, TemplateListItem } from '../../common'; const objHasProperties = (obj?: Record): boolean => { diff --git a/x-pack/plugins/infra/common/http_api/metadata_api.ts b/x-pack/plugins/infra/common/http_api/metadata_api.ts index a0524af805f00..0f847f19f75d3 100644 --- a/x-pack/plugins/infra/common/http_api/metadata_api.ts +++ b/x-pack/plugins/infra/common/http_api/metadata_api.ts @@ -37,8 +37,8 @@ export const InfraMetadataHostRT = rt.partial({ name: rt.string, hostname: rt.string, id: rt.string, - ip: rt.array(rt.string), - mac: rt.array(rt.string), + ip: rt.union([rt.array(rt.string), rt.string]), + mac: rt.union([rt.array(rt.string), rt.string]), os: InfraMetadataOSRT, architecture: rt.string, containerized: rt.boolean, diff --git a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx index c8cd2da45c5c3..d34f6b369b2df 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx +++ b/x-pack/plugins/infra/public/alerting/inventory/components/expression.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; // We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` diff --git a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx index 3bbc3cb2662cb..e40c72171f72b 100644 --- a/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_anomaly/components/expression.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; // We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` import { coreMock as mockCoreMock } from 'src/core/public/mocks'; import React from 'react'; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.test.tsx index 94192f9f911c5..309808bf48352 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; // We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx index f7e3201bbf2c9..e5c6e1a4d8984 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_chart.test.tsx @@ -6,7 +6,7 @@ */ import { DataViewBase } from '@kbn/es-query'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; // We are using this inside a `jest.mock` call. Jest requires dynamic dependencies to be prefixed with `mock` diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx index 1d8c6f6339878..6a0de2481e64a 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { Comparator } from '../../../../common/alerting/metrics'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_view_configuration.test.tsx b/x-pack/plugins/infra/public/containers/logs/log_view_configuration.test.tsx index f30878dd2ff3d..1fe14d9b2965b 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_view_configuration.test.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_view_configuration.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountHook } from '@kbn/test/jest'; +import { mountHook } from '@kbn/test-jest-helpers'; import { useLogViewConfiguration } from './log_view_configuration'; diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/metrics_time.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/metrics_time.test.tsx index c16314a6c0e71..5260cd4d680f6 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/metrics_time.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/hooks/metrics_time.test.tsx @@ -8,7 +8,7 @@ import { createMemoryHistory } from 'history'; import React from 'react'; import { Router } from 'react-router-dom'; -import { mountHook } from '@kbn/test/jest'; +import { mountHook } from '@kbn/test-jest-helpers'; import { ScopedHistory } from '../../../../../../../../src/core/public'; import { useMetricsTime } from './use_metrics_time'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts index ed88156b740bf..5f4dc01fa924a 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/index.ts @@ -11,7 +11,7 @@ import { setup as pipelinesCloneSetup } from './pipelines_clone.helpers'; import { setup as pipelinesEditSetup } from './pipelines_edit.helpers'; import { setup as pipelinesCreateFromCsvSetup } from './pipelines_create_from_csv.helpers'; -export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts index 3f11f9538ffa9..432b9046f1071 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipeline_form.helpers.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; export const getFormActions = (testBed: TestBed) => { const { find, form, component } = testBed; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts index 51f6d9bd96bd6..5b5d6704e9001 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_clone.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test-jest-helpers'; import { PipelinesClone } from '../../../public/application/sections/pipelines_clone'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts index faf1b42042ec1..3dc97cf121b98 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test-jest-helpers'; import { PipelinesCreate } from '../../../public/application/sections/pipelines_create'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create_from_csv.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create_from_csv.helpers.ts index e7de57de0e948..ea9d623e216b2 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create_from_csv.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_create_from_csv.helpers.ts @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { PipelinesCreateFromCsv } from '../../../public/application/sections/pipelines_create_from_csv'; import { WithAppDependencies } from './setup_environment'; import { getCreateFromCsvPath, ROUTES } from '../../../public/application/services/navigation'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts index 9a3c41196653f..74d124de885ff 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig, TestBed } from '@kbn/test-jest-helpers'; import { PipelinesEdit } from '../../../public/application/sections/pipelines_edit'; import { getFormActions, PipelineFormTestSubjects } from './pipeline_form.helpers'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts index e2d9f1f8bf5f9..6fa3a7a9473fe 100644 --- a/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts +++ b/x-pack/plugins/ingest_pipelines/__jest__/client_integration/helpers/pipelines_list.helpers.ts @@ -7,7 +7,12 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, +} from '@kbn/test-jest-helpers'; import { PipelinesList } from '../../../public/application/sections/pipelines_list'; import { WithAppDependencies } from './setup_environment'; import { getListPath, ROUTES } from '../../../public/application/services/navigation'; diff --git a/x-pack/plugins/ingest_pipelines/kibana.json b/x-pack/plugins/ingest_pipelines/kibana.json index 958aa729ccf0d..912584e808331 100644 --- a/x-pack/plugins/ingest_pipelines/kibana.json +++ b/x-pack/plugins/ingest_pipelines/kibana.json @@ -1,6 +1,6 @@ { "id": "ingestPipelines", - "version": "8.1.0", + "version": "8.2.0", "server": true, "ui": true, "owner": { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx index 79ffd28c9e788..dd269e34fa694 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; import React from 'react'; -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { Props } from '../'; import { ProcessorsEditorWithDeps } from './processors_editor'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx index 65d9b8f306058..274d41651fe91 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/processors/processor.helpers.tsx @@ -13,8 +13,8 @@ import axiosXhrAdapter from 'axios/lib/adapters/xhr'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks'; -import { registerTestBed, TestBed } from '@kbn/test/jest'; -import { stubWebWorker } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; +import { stubWebWorker } from '@kbn/test-jest-helpers'; import { uiMetricService, apiService } from '../../../../services'; import { Props } from '../../'; import { initHttpRequests } from '../http_requests.helpers'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx index 263a40a605d2d..ff8802a91cc9b 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx @@ -13,8 +13,8 @@ import axiosXhrAdapter from 'axios/lib/adapters/xhr'; /* eslint-disable-next-line @kbn/eslint/no-restricted-paths */ import { usageCollectionPluginMock } from 'src/plugins/usage_collection/public/mocks'; -import { registerTestBed, TestBed } from '@kbn/test/jest'; -import { stubWebWorker } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; +import { stubWebWorker } from '@kbn/test-jest-helpers'; /* eslint-disable-next-line @kbn/eslint/no-restricted-paths */ import '../../../../../../../../src/plugins/es_ui_shared/public/components/code_editor/jest_mock'; diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.test.tsx index 74b759fc80375..9392fe2398e2f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/load_from_json/modal_provider.test.tsx @@ -18,7 +18,7 @@ jest.mock('lodash', () => { }; }); -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; const setup = ({ onDone }: { onDone: OnDoneLoadJsonHandler }) => { return registerTestBed( diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx index 21f6cb6bc2052..7cd45dffd5def 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { DataContext } from './table_basic'; import { createGridCell } from './cell_value'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx index a8ba6d553b738..67d95f04869f8 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx @@ -19,23 +19,27 @@ import type { ColumnConfig } from '../../../common/expressions'; export const createGridColumns = ( bucketColumns: string[], table: Datatable, - handleFilterClick: ( - field: string, - value: unknown, - colIndex: number, - rowIndex: number, - negate?: boolean - ) => void, - handleTransposedColumnClick: ( - bucketValues: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>, - negate?: boolean - ) => void, + handleFilterClick: + | (( + field: string, + value: unknown, + colIndex: number, + rowIndex: number, + negate?: boolean + ) => void) + | undefined, + handleTransposedColumnClick: + | (( + bucketValues: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>, + negate?: boolean + ) => void) + | undefined, isReadOnly: boolean, columnConfig: ColumnConfig, visibleColumns: string[], formatFactory: FormatFactory, onColumnResize: (eventData: { columnId: string; width: number | undefined }) => void, - onColumnHide: (eventData: { columnId: string }) => void, + onColumnHide: ((eventData: { columnId: string }) => void) | undefined, alignments: Record ) => { const columnsReverseLookup = table.columns.reduce< @@ -63,86 +67,87 @@ export const createGridColumns = ( const filterable = bucketLookup.has(field); const { name, index: colIndex } = columnsReverseLookup[field]; - const cellActions = filterable - ? [ - ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { - const { rowValue, contentsIsDefined, cellContent } = getContentData({ - rowIndex, - columnId, - }); - - const filterForText = i18n.translate( - 'xpack.lens.table.tableCellFilter.filterForValueText', - { - defaultMessage: 'Filter for value', - } - ); - const filterForAriaLabel = i18n.translate( - 'xpack.lens.table.tableCellFilter.filterForValueAriaLabel', - { - defaultMessage: 'Filter for value: {cellContent}', - values: { - cellContent, - }, - } - ); - - return ( - contentsIsDefined && ( - { - handleFilterClick(field, rowValue, colIndex, rowIndex); - closePopover(); - }} - iconType="plusInCircle" - > - {filterForText} - - ) - ); - }, - ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { - const { rowValue, contentsIsDefined, cellContent } = getContentData({ - rowIndex, - columnId, - }); - - const filterOutText = i18n.translate( - 'xpack.lens.table.tableCellFilter.filterOutValueText', - { - defaultMessage: 'Filter out value', - } - ); - const filterOutAriaLabel = i18n.translate( - 'xpack.lens.table.tableCellFilter.filterOutValueAriaLabel', - { - defaultMessage: 'Filter out value: {cellContent}', - values: { - cellContent, - }, - } - ); - - return ( - contentsIsDefined && ( - { - handleFilterClick(field, rowValue, colIndex, rowIndex, true); - closePopover(); - }} - iconType="minusInCircle" - > - {filterOutText} - - ) - ); - }, - ] - : undefined; + const cellActions = + filterable && handleFilterClick + ? [ + ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + const { rowValue, contentsIsDefined, cellContent } = getContentData({ + rowIndex, + columnId, + }); + + const filterForText = i18n.translate( + 'xpack.lens.table.tableCellFilter.filterForValueText', + { + defaultMessage: 'Filter for value', + } + ); + const filterForAriaLabel = i18n.translate( + 'xpack.lens.table.tableCellFilter.filterForValueAriaLabel', + { + defaultMessage: 'Filter for value: {cellContent}', + values: { + cellContent, + }, + } + ); + + return ( + contentsIsDefined && ( + { + handleFilterClick(field, rowValue, colIndex, rowIndex); + closePopover(); + }} + iconType="plusInCircle" + > + {filterForText} + + ) + ); + }, + ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + const { rowValue, contentsIsDefined, cellContent } = getContentData({ + rowIndex, + columnId, + }); + + const filterOutText = i18n.translate( + 'xpack.lens.table.tableCellFilter.filterOutValueText', + { + defaultMessage: 'Filter out value', + } + ); + const filterOutAriaLabel = i18n.translate( + 'xpack.lens.table.tableCellFilter.filterOutValueAriaLabel', + { + defaultMessage: 'Filter out value: {cellContent}', + values: { + cellContent, + }, + } + ); + + return ( + contentsIsDefined && ( + { + handleFilterClick(field, rowValue, colIndex, rowIndex, true); + closePopover(); + }} + iconType="minusInCircle" + > + {filterOutText} + + ) + ); + }, + ] + : undefined; const columnArgs = columnConfig.columns.find(({ columnId }) => columnId === field); const isTransposed = Boolean(columnArgs?.originalColumnId); @@ -163,7 +168,7 @@ export const createGridColumns = ( 'data-test-subj': 'lensDatatableResetWidth', isDisabled: initialWidth == null, }); - if (!isTransposed) { + if (!isTransposed && onColumnHide) { additionalActions.push({ color: 'text', size: 'xs', @@ -178,7 +183,7 @@ export const createGridColumns = ( } if (!isReadOnly) { - if (isTransposed && columnArgs?.bucketValues) { + if (isTransposed && columnArgs?.bucketValues && handleTransposedColumnClick) { const bucketValues = columnArgs?.bucketValues; additionalActions.push({ color: 'text', diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx index 22f5227baa556..d8dabd81441da 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.test.tsx @@ -10,7 +10,7 @@ import { EuiButtonGroup, EuiComboBox, EuiFieldText } from '@elastic/eui'; import { FramePublicAPI, Operation, VisualizationDimensionEditorProps } from '../../types'; import { DatatableVisualizationState } from '../visualization'; import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TableDimensionEditor } from './dimension_editor'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; import { PaletteRegistry } from 'src/plugins/charts/public'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index 46ca179e7cdb4..8c75ee9efcc6b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { ReactWrapper, shallow, mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiDataGrid } from '@elastic/eui'; import { IAggType } from 'src/plugins/data/public'; import { @@ -121,6 +121,7 @@ describe('DatatableComponent', () => { paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} renderMode="edit" + interactive /> ) ).toMatchSnapshot(); @@ -141,6 +142,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ) ).toMatchSnapshot(); @@ -161,6 +163,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ) ).toMatchSnapshot(); @@ -185,6 +188,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -230,6 +234,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -314,6 +319,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -340,6 +346,36 @@ describe('DatatableComponent', () => { }); }); + test('it should not invoke executeTriggerActions if interactivity is set to false', async () => { + const { args, data } = sampleArgs(); + + const wrapper = mountWithIntl( + ({ convert: (x) => x } as IFieldFormat)} + dispatchEvent={onDispatchEvent} + getType={jest.fn(() => ({ type: 'buckets' } as IAggType))} + renderMode="edit" + paletteService={chartPluginMock.createPaletteRegistry()} + uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive={false} + /> + ); + + wrapper.find('[data-test-subj="dataGridRowCell"]').first().simulate('focus'); + + await waitForWrapperUpdate(wrapper); + + expect(wrapper.find('[data-test-subj="lensDatatableFilterOut"]').exists()).toBe(false); + }); + test('it shows emptyPlaceholder for undefined bucketed data', () => { const { args, data } = sampleArgs(); const emptyData: LensMultiTable = { @@ -364,6 +400,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); expect(component.find(VisualizationContainer)).toHaveLength(1); @@ -387,6 +424,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -437,6 +475,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -467,6 +506,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -495,6 +535,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -541,6 +582,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -567,6 +609,7 @@ describe('DatatableComponent', () => { renderMode="edit" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); // mnake a copy of the data, changing only the name of the first column @@ -602,6 +645,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -637,6 +681,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); expect(wrapper.find('[data-test-subj="lnsDataTable-footer-a"]').exists()).toEqual(false); @@ -672,6 +717,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -706,6 +752,7 @@ describe('DatatableComponent', () => { renderMode="view" paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} + interactive /> ); @@ -728,6 +775,7 @@ describe('DatatableComponent', () => { paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} renderMode="edit" + interactive /> ); @@ -762,6 +810,7 @@ describe('DatatableComponent', () => { paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} renderMode="edit" + interactive /> ); @@ -786,6 +835,7 @@ describe('DatatableComponent', () => { paletteService: chartPluginMock.createPaletteRegistry(), uiSettings: { get: jest.fn() } as unknown as IUiSettingsClient, renderMode: 'edit' as RenderMode, + interactive: true, }; const wrapper = mount( @@ -821,6 +871,7 @@ describe('DatatableComponent', () => { paletteService={chartPluginMock.createPaletteRegistry()} uiSettings={{ get: jest.fn() } as unknown as IUiSettingsClient} renderMode="edit" + interactive /> ); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index 116331771a9dd..403858f4ba48c 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -58,6 +58,8 @@ const PAGE_SIZE_OPTIONS = [DEFAULT_PAGE_SIZE, 20, 30, 50, 100]; export const DatatableComponent = (props: DatatableRenderProps) => { const [firstTable] = Object.values(props.data.tables); + const isInteractive = props.interactive; + const [columnConfig, setColumnConfig] = useState({ columns: props.args.columns, sortingColumnId: props.args.sortingColumnId, @@ -160,13 +162,16 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ); const handleFilterClick = useMemo( - () => createGridFilterHandler(firstTableRef, onClickValue), - [firstTableRef, onClickValue] + () => (isInteractive ? createGridFilterHandler(firstTableRef, onClickValue) : undefined), + [firstTableRef, onClickValue, isInteractive] ); const handleTransposedColumnClick = useMemo( - () => createTransposeColumnFilterHandler(onClickValue, untransposedDataRef), - [onClickValue, untransposedDataRef] + () => + isInteractive + ? createTransposeColumnFilterHandler(onClickValue, untransposedDataRef) + : undefined, + [onClickValue, untransposedDataRef, isInteractive] ); const bucketColumns = useMemo( @@ -206,8 +211,11 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ); const onColumnHide = useMemo( - () => createGridHideHandler(columnConfig, setColumnConfig, onEditAction), - [onEditAction, setColumnConfig, columnConfig] + () => + isInteractive + ? createGridHideHandler(columnConfig, setColumnConfig, onEditAction) + : undefined, + [onEditAction, setColumnConfig, columnConfig, isInteractive] ); const isNumericMap: Record = useMemo(() => { @@ -278,7 +286,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { ); const trailingControlColumns: EuiDataGridControlColumn[] = useMemo(() => { - if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick) { + if (!hasAtLeastOneRowClickAction || !onRowContextMenuClick || !isInteractive) { return []; } return [ @@ -311,7 +319,13 @@ export const DatatableComponent = (props: DatatableRenderProps) => { }, }, ]; - }, [firstTableRef, onRowContextMenuClick, columnConfig, hasAtLeastOneRowClickAction]); + }, [ + firstTableRef, + onRowContextMenuClick, + columnConfig, + hasAtLeastOneRowClickAction, + isInteractive, + ]); const renderCellValue = useMemo( () => @@ -333,7 +347,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => { [visibleColumns] ); - const sorting = useMemo( + const sorting = useMemo( () => createGridSortingConfig(sortBy, sortDirection as LensGridDirection, onEditAction), [onEditAction, sortBy, sortDirection] ); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/toolbar.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/toolbar.test.tsx index 35e5c81cb72c4..6bc27808c7c0c 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/toolbar.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/toolbar.test.tsx @@ -6,7 +6,7 @@ */ import React, { FormEvent } from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DataTableToolbar } from './toolbar'; import { DatatableVisualizationState } from '../visualization'; import { FramePublicAPI, VisualizationToolbarProps } from '../../types'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/types.ts b/x-pack/plugins/lens/public/datatable_visualization/components/types.ts index e7be5b78545e8..c5d94549b687e 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/types.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/types.ts @@ -49,6 +49,7 @@ export type DatatableRenderProps = DatatableProps & { renderMode: RenderMode; paletteService: PaletteRegistry; uiSettings: IUiSettingsClient; + interactive: boolean; /** * A boolean for each table row, which is true if the row active diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 2417e395792e0..51b6f7b8332f5 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -41,7 +41,7 @@ export const getDatatableRenderer = (dependencies: { handlers: ILensInterpreterRenderHandlers ) => { const resolvedGetType = await dependencies.getType; - const { hasCompatibleActions } = handlers; + const { hasCompatibleActions, isInteractive } = handlers; // An entry for each table row, whether it has any actions attached to // ROW_CLICK_TRIGGER trigger. @@ -81,6 +81,7 @@ export const getDatatableRenderer = (dependencies: { paletteService={dependencies.paletteService} getType={resolvedGetType} rowHasRowClickTriggerActions={rowHasRowClickTriggerActions} + interactive={isInteractive()} uiSettings={dependencies.uiSettings} /> diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 0115a8c5b39c7..ccfd6a49a2d8b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -24,6 +24,7 @@ import { TableDimensionEditor } from './components/dimension_editor'; import { CUSTOM_PALETTE } from '../shared_components/coloring/constants'; import { LayerType, layerTypes } from '../../common'; import { getDefaultSummaryLabel, PagingState } from '../../common/expressions'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import type { ColumnState, SortingState } from '../../common/expressions'; import { DataTableToolbar } from './components/toolbar'; export interface DatatableVisualizationState { @@ -84,6 +85,8 @@ export const getDatatableVisualization = ({ switchVisualizationType: (_, state) => state, + triggers: [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick], + initialize(addNewLayer, state) { return ( state || { diff --git a/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx b/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx index 6beb565be098c..d52eb3c578059 100644 --- a/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx +++ b/x-pack/plugins/lens/public/debounced_component/debounced_component.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl as mount } from '@kbn/test/jest'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { debouncedComponent } from './debounced_component'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx index c68c04b4b3e21..6879c35f30fe1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/editor_frame.tsx @@ -47,6 +47,9 @@ export function EditorFrame(props: EditorFrameProps) { const visualization = useLensSelector(selectVisualization); const areDatasourcesLoaded = useLensSelector(selectAreDatasourcesLoaded); const isVisualizationLoaded = !!visualization.state; + const visualizationTypeIsKnown = Boolean( + visualization.activeId && props.visualizationMap[visualization.activeId] + ); const framePublicAPI: FramePublicAPI = useLensSelector((state) => selectFramePublicAPI(state, datasourceMap) ); @@ -121,6 +124,7 @@ export function EditorFrame(props: EditorFrameProps) { ) } suggestionsPanel={ + visualizationTypeIsKnown && areDatasourcesLoaded && ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 6752c0ae03cea..5f14e83bf41a1 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -46,7 +46,10 @@ import { UiActionsStart } from '../../../../../../../src/plugins/ui_actions/publ import { VIS_EVENT_TO_TRIGGER } from '../../../../../../../src/plugins/visualizations/public'; import { WorkspacePanelWrapper } from './workspace_panel_wrapper'; import { DropIllustration } from '../../../assets/drop_illustration'; -import { getOriginalRequestErrorMessages } from '../../error_helper'; +import { + getOriginalRequestErrorMessages, + getUnknownVisualizationTypeError, +} from '../../error_helper'; import { getMissingIndexPattern, validateDatasourceAndVisualization } from '../state_helpers'; import { DefaultInspectorAdapters } from '../../../../../../../src/plugins/expressions/common'; import { @@ -163,6 +166,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ] : []; + const unknownVisError = visualization.activeId && !activeVisualization; + // Note: mind to all these eslint disable lines: the frameAPI will change too frequently // and to prevent race conditions it is ok to leave them there. @@ -180,7 +185,7 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ); const expression = useMemo(() => { - if (!configurationValidationError?.length && !missingRefsErrors.length) { + if (!configurationValidationError?.length && !missingRefsErrors.length && !unknownVisError) { try { const ast = buildExpression({ visualization: activeVisualization, @@ -213,6 +218,12 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ })); } } + if (unknownVisError) { + setLocalState((s) => ({ + ...s, + expressionBuildError: [getUnknownVisualizationTypeError(visualization.activeId!)], + })); + } }, [ activeVisualization, visualization.state, @@ -221,6 +232,8 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ datasourceLayers, configurationValidationError?.length, missingRefsErrors.length, + unknownVisError, + visualization.activeId, ]); const expressionExists = Boolean(expression); @@ -415,6 +428,7 @@ export const VisualizationWrapper = ({ fixAction?: DatasourceFixAction; }>; missingRefsErrors?: Array<{ shortMessage: string; longMessage: React.ReactNode }>; + unknownVisError?: Array<{ shortMessage: string; longMessage: React.ReactNode }>; }; ExpressionRendererComponent: ReactExpressionRendererType; application: ApplicationStart; diff --git a/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts b/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts index 9df48d99ce762..7d237cb783c5e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/error_helper.ts @@ -165,3 +165,17 @@ export function getMissingIndexPatterns(indexPatternIds: string[]) { values: { count: indexPatternIds.length, ids: indexPatternIds.join(', ') }, }); } + +export function getUnknownVisualizationTypeError(visType: string) { + return { + shortMessage: i18n.translate('xpack.lens.unknownVisType.shortMessage', { + defaultMessage: `Unknown visualization type`, + }), + longMessage: i18n.translate('xpack.lens.unknownVisType.longMessage', { + defaultMessage: `The visualization type {visType} could not be resolved.`, + values: { + visType, + }, + }), + }; +} diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx index 17e18392e83e9..5ae3cb571bdbb 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.test.tsx @@ -318,6 +318,52 @@ describe('embeddable', () => { expect(spacesPluginStart.ui.components.getEmbeddableLegacyUrlConflict).toHaveBeenCalled(); }); + it('should not render if timeRange prop is not passed when a referenced data view is time based', async () => { + attributeService = attributeServiceMockFromSavedVis({ + ...savedVis, + references: [ + { type: 'index-pattern', id: '123', name: 'abc' }, + { type: 'index-pattern', id: '123', name: 'def' }, + { type: 'index-pattern', id: '456', name: 'ghi' }, + ], + }); + const embeddable = new Embeddable( + { + timefilter: dataPluginMock.createSetupContract().query.timefilter.timefilter, + attributeService, + expressionRenderer, + basePath, + inspector: inspectorPluginMock.createStartContract(), + indexPatternService: { + get: (id: string) => Promise.resolve({ id, isTimeBased: jest.fn(() => true) }), + } as unknown as IndexPatternsContract, + capabilities: { + canSaveDashboards: true, + canSaveVisualizations: true, + }, + getTrigger, + visualizationMap: {}, + injectFilterReferences: jest.fn(mockInjectFilterReferences), + theme: themeServiceMock.createStartContract(), + documentToExpression: () => + Promise.resolve({ + ast: { + type: 'expression', + chain: [ + { type: 'function', function: 'my', arguments: {} }, + { type: 'function', function: 'expression', arguments: {} }, + ], + }, + errors: undefined, + }), + }, + {} as LensEmbeddableInput + ); + await embeddable.initializeSavedVis({} as LensEmbeddableInput); + embeddable.render(mountpoint); + expect(expressionRenderer).toHaveBeenCalledTimes(0); + }); + it('should initialize output with deduped list of index patterns', async () => { attributeService = attributeServiceMockFromSavedVis({ ...savedVis, @@ -335,7 +381,7 @@ describe('embeddable', () => { basePath, inspector: inspectorPluginMock.createStartContract(), indexPatternService: { - get: (id: string) => Promise.resolve({ id }), + get: (id: string) => Promise.resolve({ id, isTimeBased: () => false }), } as unknown as IndexPatternsContract, capabilities: { canSaveDashboards: true, diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 2878a484686d2..a430a72276ca3 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -265,20 +265,10 @@ export class Embeddable } public supportedTriggers() { - if (!this.savedVis) { + if (!this.savedVis || !this.savedVis.visualizationType) { return []; } - switch (this.savedVis.visualizationType) { - case 'lnsXY': - return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush]; - case 'lnsDatatable': - return [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.tableRowContextMenuClick]; - case 'lnsPie': - return [VIS_EVENT_TO_TRIGGER.filter]; - case 'lnsMetric': - default: - return []; - } + return this.deps.visualizationMap[this.savedVis.visualizationType]?.triggers || []; } public getInspectorAdapters() { @@ -308,6 +298,31 @@ export class Embeddable return ret?.length ? ret : undefined; } + private maybeAddTimeRangeError( + errors: ErrorMessage[] | undefined, + input: LensEmbeddableInput, + indexPatterns: IndexPattern[] + ) { + // if at least one indexPattern is time based, then the Lens embeddable requires the timeRange prop + if ( + input.timeRange == null && + indexPatterns.some((indexPattern) => indexPattern.isTimeBased()) + ) { + return [ + ...(errors || []), + { + shortMessage: i18n.translate('xpack.lens.embeddable.missingTimeRangeParam.shortMessage', { + defaultMessage: `Missing timeRange property`, + }), + longMessage: i18n.translate('xpack.lens.embeddable.missingTimeRangeParam.longMessage', { + defaultMessage: `The timeRange property is required for the given configuration`, + }), + }, + ]; + } + return errors; + } + async initializeSavedVis(input: LensEmbeddableInput) { const unwrapResult: LensUnwrapResult | false = await this.deps.attributeService .unwrapAttributes(input) @@ -334,15 +349,12 @@ export class Embeddable this.expression = expression; this.errors = this.maybeAddConflictError(errors, metaInfo?.sharingSavedObjectProps); - if (this.errors) { - this.logError('validation'); - } await this.initializeOutput(); this.isInitialized = true; } onContainerStateChanged(containerState: LensEmbeddableInput) { - if (this.handleContainerStateChanged(containerState)) this.reload(); + if (this.handleContainerStateChanged(containerState) || this.errors?.length) this.reload(); } handleContainerStateChanged(containerState: LensEmbeddableInput): boolean { @@ -582,6 +594,13 @@ export class Embeddable // the container to pick them up and use them to configure filter bar and // config dropdown correctly. const input = this.getInput(); + + this.errors = this.maybeAddTimeRangeError(this.errors, input, indexPatterns); + + if (this.errors) { + this.logError('validation'); + } + const title = input.hidePanelTitles ? '' : input.title || this.savedVis.title; const savedObjectId = (input as LensByReferenceInput).savedObjectId; this.updateOutput({ diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx index 94473f2be3613..b8fd06a09ebcd 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx @@ -25,6 +25,8 @@ import type { IndexPatternPersistedState } from '../indexpattern_datasource/type import type { XYState } from '../xy_visualization/types'; import type { PieVisualizationState, MetricState } from '../../common/expressions'; import type { DatatableVisualizationState } from '../datatable_visualization/visualization'; +import type { HeatmapVisualizationState } from '../heatmap_visualization/types'; +import type { GaugeVisualizationState } from '../visualizations/gauge/constants'; type LensAttributes = Omit< Document, @@ -48,7 +50,10 @@ export type TypedLensByValueInput = Omit & { | LensAttributes<'lnsXY', XYState> | LensAttributes<'lnsPie', PieVisualizationState> | LensAttributes<'lnsDatatable', DatatableVisualizationState> - | LensAttributes<'lnsMetric', MetricState>; + | LensAttributes<'lnsMetric', MetricState> + | LensAttributes<'lnsHeatmap', HeatmapVisualizationState> + | LensAttributes<'lnsGauge', GaugeVisualizationState> + | LensAttributes; }; export type EmbeddableComponentProps = (TypedLensByValueInput | LensByReferenceInput) & { diff --git a/x-pack/plugins/lens/public/heatmap_visualization/constants.ts b/x-pack/plugins/lens/public/heatmap_visualization/constants.ts index f1a905fa084aa..449261dbc69fe 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/constants.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/constants.ts @@ -26,7 +26,7 @@ export const CHART_NAMES = { shapeType: CHART_SHAPES.HEATMAP, icon: HeatmapIcon, label: i18n.translate('xpack.lens.heatmap.heatmapLabel', { - defaultMessage: 'Heatmap', + defaultMessage: 'Heat map', }), groupLabel, }, diff --git a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts index ec50502546824..34907c2e93c63 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts @@ -237,7 +237,7 @@ describe('heatmap suggestions', () => { type: LEGEND_FUNCTION, }, }, - title: 'Heatmap', + title: 'Heat map', hide: true, previewIcon: 'empty', score: 0, @@ -290,7 +290,7 @@ describe('heatmap suggestions', () => { type: LEGEND_FUNCTION, }, }, - title: 'Heatmap', + title: 'Heat map', hide: true, previewIcon: 'empty', score: 0.3, @@ -356,7 +356,7 @@ describe('heatmap suggestions', () => { type: LEGEND_FUNCTION, }, }, - title: 'Heatmap', + title: 'Heat map', // Temp hide all suggestions while heatmap is in beta hide: true, previewIcon: 'empty', @@ -431,7 +431,7 @@ describe('heatmap suggestions', () => { type: LEGEND_FUNCTION, }, }, - title: 'Heatmap', + title: 'Heat map', // Temp hide all suggestions while heatmap is in beta hide: true, previewIcon: 'empty', diff --git a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts index 38f43406d092c..fac07d322e037 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts @@ -104,7 +104,7 @@ export const getSuggestions: Visualization['getSugges { state: newState, title: i18n.translate('xpack.lens.heatmap.heatmapLabel', { - defaultMessage: 'Heatmap', + defaultMessage: 'Heat map', }), // Temp hide all suggestions while heatmap is in beta hide: true || hide, diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx index c21f738cee695..046e66e3f4708 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx @@ -108,7 +108,7 @@ export const getHeatmapVisualization = ({ id: 'heatmap', icon: HeatmapIcon, label: i18n.translate('xpack.lens.heatmapVisualization.heatmapLabel', { - defaultMessage: 'Heatmap', + defaultMessage: 'Heat map', }), groupLabel: groupLabelForHeatmap, showExperimentalBadge: false, diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 3e622d8ac9312..1c045e63e9e26 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -12,7 +12,7 @@ export type { TypedLensByValueInput, } from './embeddable/embeddable_component'; export type { XYState } from './xy_visualization/types'; -export type { DataType, OperationMetadata } from './types'; +export type { DataType, OperationMetadata, Visualization } from './types'; export type { PieVisualizationState, PieLayerState, @@ -28,6 +28,8 @@ export type { } from '../common/expressions'; export type { ValueLabelConfig } from '../common/types'; export type { DatatableVisualizationState } from './datatable_visualization/visualization'; +export type { HeatmapVisualizationState } from './heatmap_visualization/types'; +export type { GaugeVisualizationState } from './visualizations/gauge/constants'; export type { IndexPatternPersistedState, PersistedIndexPatternLayer, @@ -56,9 +58,11 @@ export type { MathIndexPatternColumn, OverallSumIndexPatternColumn, FormulaPublicApi, + StaticValueIndexPatternColumn, } from './indexpattern_datasource/types'; export type { LensEmbeddableInput } from './embeddable'; +export { layerTypes } from '../common'; -export type { LensPublicStart } from './plugin'; +export type { LensPublicStart, LensPublicSetup } from './plugin'; export const plugin = () => new LensPlugin(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx index 7fe115847b2b5..03dc83d030838 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.test.tsx @@ -17,7 +17,7 @@ import { NoFieldsCallout } from './no_fields_callout'; import { act } from 'react-dom/test-utils'; import { coreMock } from 'src/core/public/mocks'; import { IndexPatternPrivateState } from './types'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { ChangeIndexPattern } from './change_indexpattern'; import { EuiProgress, EuiLoadingSpinner } from '@elastic/eui'; import { documentField } from './document_field'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx index 975e08e5ca1a9..d8121e4b726be 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.test.tsx @@ -21,7 +21,7 @@ import { IndexPatternDimensionEditorComponent, IndexPatternDimensionEditorProps, } from './dimension_panel'; -import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest'; +import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { IUiSettingsClient, SavedObjectsClientContract, HttpSetup, CoreSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { generateId } from '../../id_generator'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx index 4a16739e65972..5131a1224341e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ReactWrapper, ShallowWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { EuiComboBox } from '@elastic/eui'; -import { mountWithIntl as mount } from '@kbn/test/jest'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx index cb721416a1df9..c86fdcc33b15f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.test.tsx @@ -11,7 +11,7 @@ import { act } from 'react-dom/test-utils'; import { EuiLoadingSpinner, EuiPopover } from '@elastic/eui'; import { InnerFieldItem, FieldItemProps } from './field_item'; import { coreMock } from 'src/core/public/mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; import { IndexPattern } from './types'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx index b3ade3ebc48b8..b886cc5539d40 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/fields_accordion.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiLoadingSpinner, EuiNotificationBadge } from '@elastic/eui'; import { coreMock } from 'src/core/public/mocks'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; import { IndexPattern } from './types'; import { FieldItem } from './field_item'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx index 335796442bd8b..91b9de58bdaa1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/layerpanel.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { IndexPatternPrivateState } from './types'; import { IndexPatternLayerPanelProps, LayerPanel } from './layerpanel'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { ShallowWrapper } from 'enzyme'; import { EuiSelectable } from '@elastic/eui'; import { ChangeIndexPattern } from './change_indexpattern'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx index 58bd40368656b..ea43766464cf5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/date_histogram.tsx @@ -250,6 +250,7 @@ export const dateHistogramOperation: OperationDefinition< }; setInterval(newInterval); }} + step={1} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx index a0e5fca114c37..0fb7f35beb02b 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.test.tsx @@ -840,7 +840,7 @@ describe('formula', () => { ...columnParams, } as FormulaIndexPatternColumn, }, - columnOrder: [], + columnOrder: ['col1'], indexPatternId: '', }; } @@ -1388,6 +1388,45 @@ invalid: " } }); + it('returns an error if the formula is fully static and there is at least one bucket dimension', () => { + const formulaLayer = getNewLayerWithFormula('5 + 3 * 7'); + expect( + formulaOperation.getErrorMessage!( + { + ...formulaLayer, + columns: { + ...formulaLayer.columns, + col0: { + dataType: 'date', + isBucketed: true, + label: '', + operationType: 'date_histogram', + references: [], + sourceField: 'ts', + }, + }, + columnOrder: ['col0', 'col1'], + }, + 'col1', + indexPattern, + operationDefinitionMap + ) + ).toEqual([ + 'A layer with only static values will not show results, use at least one dynamic metric', + ]); + }); + + it('returns no error if the formula is fully static and there is no bucket dimension', () => { + expect( + formulaOperation.getErrorMessage!( + getNewLayerWithFormula('5 + 3 * 7'), + 'col1', + indexPattern, + operationDefinitionMap + ) + ).toEqual(undefined); + }); + it('returns no error if a math operation is passed to fullReference operations', () => { const formulas = [ 'derivative(7+1)', diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx index ce0d03a232e28..9bbd1588d29b6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/formula/formula.tsx @@ -86,6 +86,25 @@ export const formulaOperation: OperationDefinition marker); + const hasBuckets = layer.columnOrder.some((colId) => layer.columns[colId].isBucketed); + const hasOtherMetrics = layer.columnOrder.some((colId) => { + const col = layer.columns[colId]; + return ( + !col.isBucketed && + !col.isStaticValue && + col.operationType !== 'math' && + col.operationType !== 'formula' + ); + }); + + if (hasBuckets && !hasOtherMetrics) { + innerErrors.push({ + message: i18n.translate('xpack.lens.indexPattern.noRealMetricError', { + defaultMessage: + 'A layer with only static values will not show results, use at least one dynamic metric', + }), + }); + } return innerErrors.length ? innerErrors.map(({ message }) => message) : undefined; }, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx index 84bcb48f95234..8a35a093f4ac0 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/advanced_editor.tsx @@ -134,6 +134,7 @@ export const RangePopover = ({ compressed placeholder={FROM_PLACEHOLDER} isInvalid={!isValidRange(tempRange)} + step={1} /> @@ -164,6 +165,7 @@ export const RangePopover = ({ onSubmit(); } }} + step={1} /> diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx index 0adaf8ea00640..d41ddaf26fb3f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/static_value.tsx @@ -215,6 +215,7 @@ export const staticValueOperation: OperationDefinition< compressed value={inputValue ?? ''} onChange={onChangeHandler} + step="any" />
); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts index 4474effc8c8c8..05454c4e0751c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/index.ts @@ -40,4 +40,5 @@ export type { OverallAverageIndexPatternColumn, FormulaIndexPatternColumn, MathIndexPatternColumn, + StaticValueIndexPatternColumn, } from './definitions'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index 432c932b85da8..0d8b57a5502ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -29,6 +29,15 @@ import { isColumnFormatted, isColumnOfType } from './operations/definitions/help type OriginalColumn = { id: string } & GenericIndexPatternColumn; +declare global { + interface Window { + /** + * Debug setting to make requests complete slower than normal. data.search.aggs.shardDelay.enabled has to be set via settings for this to work + */ + ELASTIC_LENS_DELAY_SECONDS?: number; + } +} + function getExpressionForLayer( layer: IndexPatternLayer, indexPattern: IndexPattern, @@ -139,8 +148,27 @@ function getExpressionForLayer( } }); + if (window.ELASTIC_LENS_DELAY_SECONDS) { + aggs.push( + buildExpression({ + type: 'expression', + chain: [ + buildExpressionFunction('aggShardDelay', { + id: 'the-delay', + enabled: true, + schema: 'metric', + delay: `${window.ELASTIC_LENS_DELAY_SECONDS}s`, + }).toAst(), + ], + }) + ); + } + const idMap = esAggEntries.reduce((currentIdMap, [colId, column], index) => { - const esAggsId = `col-${index}-${index}`; + const esAggsId = window.ELASTIC_LENS_DELAY_SECONDS + ? `col-${index + (column.isBucketed ? 0 : 1)}-${index}` + : `col-${index}-${index}`; + return { ...currentIdMap, [esAggsId]: { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 08786b181f3e7..5bb4d58ed20fa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -36,6 +36,7 @@ export type { FormulaIndexPatternColumn, MathIndexPatternColumn, OverallSumIndexPatternColumn, + StaticValueIndexPatternColumn, } from './operations'; export type { FormulaPublicApi } from './operations/definitions/formula/formula_public_api'; diff --git a/x-pack/plugins/lens/public/metric_visualization/dimension_editor.test.tsx b/x-pack/plugins/lens/public/metric_visualization/dimension_editor.test.tsx index ef0687a2cd519..b296313086d7f 100644 --- a/x-pack/plugins/lens/public/metric_visualization/dimension_editor.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/dimension_editor.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiButtonGroup } from '@elastic/eui'; import { FramePublicAPI, VisualizationDimensionEditorProps } from '../types'; import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { MetricDimensionEditor } from './dimension_editor'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; import { ColorMode, PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx index 1b1d83ef2892d..3365657fea34e 100644 --- a/x-pack/plugins/lens/public/mocks/store_mocks.tsx +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -9,7 +9,7 @@ import React from 'react'; // eslint-disable-next-line import/no-extraneous-dependencies import { ReactWrapper } from 'enzyme'; // eslint-disable-next-line import/no-extraneous-dependencies -import { mountWithIntl as mount } from '@kbn/test/jest'; +import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { Provider } from 'react-redux'; import { act } from 'react-dom/test-utils'; import { PreloadedState } from '@reduxjs/toolkit'; diff --git a/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx b/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx index 91886c6cf19d6..df0648aa40d74 100644 --- a/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/get_legend_action.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { LegendActionProps, SeriesIdentifier } from '@elastic/charts'; import { EuiPopover } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ComponentType, ReactWrapper } from 'enzyme'; import type { Datatable } from 'src/plugins/expressions/public'; import { getLegendAction } from './get_legend_action'; diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx index e7c5e2f78920b..eb154abe6d14d 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx @@ -12,6 +12,7 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import { ThemeServiceStart } from 'kibana/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import type { Visualization, OperationMetadata, @@ -102,6 +103,8 @@ export const getPieVisualization = ({ shape: visualizationTypeId as PieVisualizationState['shape'], }), + triggers: [VIS_EVENT_TO_TRIGGER.filter], + initialize(addNewLayer, state, mainPalette) { return ( state || { diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index decd9d8c69510..b3b78ffc4c2e8 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -70,7 +70,7 @@ import { } from '../../../../src/plugins/ui_actions/public'; import { APP_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common/constants'; import type { FormatFactory } from '../common/types'; -import type { VisualizationType } from './types'; +import type { Visualization, VisualizationType, EditorFrameSetup } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; @@ -117,6 +117,23 @@ export interface LensPluginStartDependencies { usageCollection?: UsageCollectionStart; } +export interface LensPublicSetup { + /** + * Register 3rd party visualization type + * See `x-pack/examples/3rd_party_lens_vis` for exemplary usage. + * + * In case the visualization is a function returning a promise, it will only be called once Lens is actually requiring it. + * This can be used to lazy-load parts of the code to keep the initial bundle as small as possible. + * + * This API might undergo breaking changes even in minor versions. + * + * @experimental + */ + registerVisualization: ( + visualization: Visualization | (() => Promise>) + ) => void; +} + export interface LensPublicStart { /** * React component which can be used to embed a Lens visualization into another application. @@ -173,6 +190,8 @@ export interface LensPublicStart { export class LensPlugin { private datatableVisualization: DatatableVisualizationType | undefined; private editorFrameService: EditorFrameServiceType | undefined; + private editorFrameSetup: EditorFrameSetup | undefined; + private queuedVisualizations: Array Promise)> = []; private indexpatternDatasource: IndexPatternDatasourceType | undefined; private xyVisualization: XyVisualizationType | undefined; private metricVisualization: MetricVisualizationType | undefined; @@ -301,6 +320,17 @@ export class LensPlugin { } urlForwarding.forwardApp('lens', 'lens'); + + return { + registerVisualization: (vis: Visualization | (() => Promise)) => { + if (this.editorFrameSetup) { + this.editorFrameSetup.registerVisualization(vis); + } else { + // queue visualizations if editor frame is not yet ready as it's loaded async + this.queuedVisualizations.push(vis); + } + }, + }; } private async initParts( @@ -351,6 +381,11 @@ export class LensPlugin { this.pieVisualization.setup(core, dependencies); this.heatmapVisualization.setup(core, dependencies); this.gaugeVisualization.setup(core, dependencies); + + this.queuedVisualizations.forEach((queuedVis) => { + editorFrameSetupInterface.registerVisualization(queuedVis); + }); + this.editorFrameSetup = editorFrameSetupInterface; } start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart { diff --git a/x-pack/plugins/lens/public/shared_components/axis_title_settings.test.tsx b/x-pack/plugins/lens/public/shared_components/axis_title_settings.test.tsx index 4cb7f6d1314b6..433a0f821f273 100644 --- a/x-pack/plugins/lens/public/shared_components/axis_title_settings.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/axis_title_settings.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { AxisTitleSettings, AxisTitleSettingsProps } from './axis_title_settings'; describe('Axes Title settings', () => { diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges.test.tsx index 3010c9fcc47e9..aafcc3fce8dde 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { ColorRanges, ColorRangesProps } from './color_ranges'; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges_item.tsx b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges_item.tsx index 7264704cc5251..604ecdb363522 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges_item.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/color_ranges_item.tsx @@ -212,6 +212,7 @@ export function ColorRangeItem({ index: index + 1, }, })} + step="any" /> {ActionButton ? ( diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.test.ts b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.test.ts index 582ca454f82ce..d0b650eba0140 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.test.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.test.ts @@ -30,6 +30,20 @@ describe('utils', () => { { color: '#ccc', start: 60, end: 80 }, { color: '#bbb', start: 80, end: 90 }, ]); + + colorRanges = [ + { color: '#aaa', start: 55, end: 90 }, + { color: '#bbb', start: 90, end: 60 }, + { color: '#ccc', start: 60, end: Infinity }, + ]; + expect(sortColorRanges(colorRanges)).toEqual([ + { color: '#aaa', start: 55, end: 60 }, + { color: '#ccc', start: 60, end: 90 }, + { color: '#bbb', start: 90, end: Infinity }, + ]); + + colorRanges = [{ color: '#aaa', start: 90, end: 55 }]; + expect(sortColorRanges(colorRanges)).toEqual([{ color: '#aaa', start: 55, end: 90 }]); }); it('calculateMaxStep', () => { diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.ts index 6f0f3d5038916..4c8dd17c77c61 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_ranges/utils/utils.ts @@ -27,10 +27,17 @@ export const isLastItem = (accessor: ColorRangeAccessor) => accessor === 'end'; export const sortColorRanges = (colorRanges: ColorRange[]) => { const lastRange = colorRanges[colorRanges.length - 1]; - return [...colorRanges, { start: lastRange.end, color: lastRange.color }] + // we should add last end as new start because we should include it on sorting + return [...colorRanges, { start: lastRange.end, color: lastRange.color, end: undefined }] .sort(({ start: startA }, { start: startB }) => Number(startA) - Number(startB)) .reduce((sortedColorRange, newColorRange, i, array) => { - const color = i === array.length - 2 ? array[i + 1].color : newColorRange.color; + // we should pick correct color for the last range. + // If after sorting we don't change last value we should just take color in array order + // In another case we should get the next one. + let color = newColorRange.color; + if (i === array.length - 2 && array[i + 1].start !== lastRange.end) { + color = array[i + 1].color; + } if (i !== array.length - 1) { sortedColorRange.push({ color, diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx index a97f3d3f04112..cb7ce91b84aeb 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiButtonGroup, EuiColorPalettePickerPaletteProps } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { ReactWrapper } from 'enzyme'; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts index d855999ecfcb6..6a90119e4ca71 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts @@ -414,7 +414,8 @@ export function roundValue(value: number, fractionDigits: number = 2) { export function getStepValue(colorStops: ColorStop[], newColorStops: ColorStop[], max: number) { const length = newColorStops.length; // workout the steps from the last 2 items - const dataStep = newColorStops[length - 1].stop - newColorStops[length - 2].stop || 1; + const dataStep = + length > 1 ? newColorStops[length - 1].stop - newColorStops[length - 2].stop || 1 : 1; let step = Number(dataStep.toFixed(2)); if (max < colorStops[length - 1].stop + step) { const diffToMax = max - colorStops[length - 1].stop; diff --git a/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx b/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx index 0c494f4d0090d..49a53c1abf66f 100644 --- a/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend_location_settings.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Position } from '@elastic/charts'; -import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test/jest'; +import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { LegendLocationSettings, LegendLocationSettingsProps } from './legend_location_settings'; describe('Legend Location Settings', () => { diff --git a/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx b/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx index 4265dee2251b5..6791d5586d327 100644 --- a/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend_location_settings.tsx @@ -170,6 +170,7 @@ const FloatingColumnsInput = ({ onChange={(e) => { handleInputChange(Number(e.target.value)); }} + step={1} /> ); }; diff --git a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx index 95739c294b320..0072a6cd2dcce 100644 --- a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { LegendSettingsPopover, LegendSettingsPopoverProps, diff --git a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx index ba5e93c3f8952..875fd2ab26313 100644 --- a/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx +++ b/x-pack/plugins/lens/public/shared_components/legend_settings_popover.tsx @@ -140,6 +140,7 @@ export const MaxLinesInput = ({ value={inputValue} min={MIN_TRUNCATE_LINES} max={MAX_TRUNCATE_LINES} + step={1} compressed disabled={isDisabled} onChange={(e) => { diff --git a/x-pack/plugins/lens/public/shared_components/value_labels_settings.test.tsx b/x-pack/plugins/lens/public/shared_components/value_labels_settings.test.tsx index ae68a40d8cea6..7f5ebcf7aa281 100644 --- a/x-pack/plugins/lens/public/shared_components/value_labels_settings.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/value_labels_settings.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { ValueLabelsSettings, VisualOptionsProps } from './value_labels_settings'; describe('Value labels Settings', () => { diff --git a/x-pack/plugins/lens/public/state_management/context_middleware/index.test.ts b/x-pack/plugins/lens/public/state_management/context_middleware/index.test.ts index 0c3f53cec3a70..f115cb59e6121 100644 --- a/x-pack/plugins/lens/public/state_management/context_middleware/index.test.ts +++ b/x-pack/plugins/lens/public/state_management/context_middleware/index.test.ts @@ -122,5 +122,26 @@ describe('contextMiddleware', () => { expect(store.dispatch).not.toHaveBeenCalled(); expect(next).toHaveBeenCalledWith(action); }); + + it('does not trigger another update on active data update', () => { + const data = mockDataPlugin(); + (data.nowProvider.get as jest.Mock).mockReturnValue(new Date(Date.now() - 30000)); + (data.query.timefilter.timefilter.getTime as jest.Mock).mockReturnValue({ + from: 'now-2m', + to: 'now', + }); + (data.query.timefilter.timefilter.getBounds as jest.Mock).mockReturnValue({ + min: moment(Date.now() - 100000), + max: moment(Date.now() - 30000), + }); + const { next, invoke, store } = createMiddleware(data); + const action = { + type: 'lens/onActiveDataChange', + payload: {}, + }; + invoke(action); + expect(store.dispatch).not.toHaveBeenCalled(); + expect(next).toHaveBeenCalledWith(action); + }); }); }); diff --git a/x-pack/plugins/lens/public/state_management/context_middleware/index.ts b/x-pack/plugins/lens/public/state_management/context_middleware/index.ts index 55d066f6db55b..25dea5527d061 100644 --- a/x-pack/plugins/lens/public/state_management/context_middleware/index.ts +++ b/x-pack/plugins/lens/public/state_management/context_middleware/index.ts @@ -12,6 +12,7 @@ import { setState, LensDispatch, LensStoreDeps, navigateAway } from '..'; import { LensAppState } from '../types'; import { getResolvedDateRange, containsDynamicMath } from '../../utils'; import { subscribeToExternalContext } from './subscribe_to_external_context'; +import { onActiveDataChange } from '../lens_slice'; export const contextMiddleware = (storeDeps: LensStoreDeps) => (store: MiddlewareAPI) => { const unsubscribeFromExternalContext = subscribeToExternalContext( @@ -20,7 +21,7 @@ export const contextMiddleware = (storeDeps: LensStoreDeps) => (store: Middlewar store.dispatch ); return (next: Dispatch) => (action: PayloadAction>) => { - if (!action.payload?.searchSessionId) { + if (!action.payload?.searchSessionId && !onActiveDataChange.match(action)) { updateTimeRange(storeDeps.lensServices.data, store.dispatch); } if (navigateAway.match(action)) { diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index aa41025b818eb..91009dffe2081 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -654,6 +654,10 @@ export interface Visualization { getMainPalette?: (state: T) => undefined | PaletteOutput; + /** + * Supported triggers of this visualization type when embedded somewhere + */ + triggers?: string[]; /** * Visualizations must provide at least one type for the chart switcher, * but can register multiple subtypes diff --git a/x-pack/plugins/lens/public/visualizations/gauge/toolbar_component/gauge_toolbar.test.tsx b/x-pack/plugins/lens/public/visualizations/gauge/toolbar_component/gauge_toolbar.test.tsx index 93d9eb04775ea..1c3a5fdea5593 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/toolbar_component/gauge_toolbar.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/toolbar_component/gauge_toolbar.test.tsx @@ -6,7 +6,7 @@ */ import React, { FormEvent } from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ToolbarButton } from 'src/plugins/kibana_react/public'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 027165a2eb5d0..bc57547bc0ee6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -43,7 +43,7 @@ import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/ import React from 'react'; import { shallow } from 'enzyme'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public'; import { XyEndzones } from './x_domain'; diff --git a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx index 5d1b45e481499..c15c0916bee0b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { LegendActionProps, SeriesIdentifier } from '@elastic/charts'; import { EuiPopover } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ComponentType, ReactWrapper } from 'enzyme'; import type { LensMultiTable } from '../../common'; import { layerTypes } from '../../common'; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 8957a522303e0..49c8705c5cb5e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -15,6 +15,7 @@ import { PaletteRegistry } from 'src/plugins/charts/public'; import { FieldFormatsStart } from 'src/plugins/field_formats/public'; import { ThemeServiceStart } from 'kibana/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; +import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; import { getSuggestions } from './xy_suggestions'; import { XyToolbar, DimensionEditor } from './xy_config_panel'; import { LayerHeader } from './xy_config_panel/layer_header'; @@ -177,6 +178,8 @@ export const getXyVisualization = ({ getSuggestions, + triggers: [VIS_EVENT_TO_TRIGGER.filter, VIS_EVENT_TO_TRIGGER.brush], + initialize(addNewLayer, state) { return ( state || { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx index 81e82bd1fb904..a57298787b2e3 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { AxisSettingsPopover, AxisSettingsPopoverProps } from './axis_settings_popover'; import { ToolbarPopover } from '../../shared_components'; import { layerTypes } from '../../../common'; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx index 6f0adf3d33810..20d2bd31c7c64 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/axis_settings_popover.tsx @@ -459,6 +459,7 @@ export const AxisSettingsPopover: React.FunctionComponent @@ -504,6 +505,7 @@ export const AxisSettingsPopover: React.FunctionComponent diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.test.tsx index 3ba29e4f72c83..f9920647478be 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/fill_opacity_option.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest'; +import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { EuiRange } from '@elastic/eui'; import { FillOpacityOption } from './fill_opacity_option'; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.test.tsx index c37a36a42fa47..272adc3b17f05 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/line_curve_option.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest'; +import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { EuiSwitch } from '@elastic/eui'; import { LineCurveOption } from './line_curve_option'; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_value_option.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_value_option.test.tsx index ce4e05223b5a3..d3960acd56377 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_value_option.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/missing_value_option.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test/jest'; +import { shallowWithIntl as shallow, mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { EuiSuperSelect } from '@elastic/eui'; import { MissingValuesOptions } from './missing_values_option'; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx index 6b48738e22a60..fe8a71ad9f326 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/visual_options_popover/visual_options_popover.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl as shallow } from '@kbn/test/jest'; +import { shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { Position } from '@elastic/charts'; import type { FramePublicAPI } from '../../../types'; import { createMockDatasource, createMockFramePublicAPI } from '../../../mocks'; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx index e5b1870c73404..0ffa2d394bcaf 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/xy_config_panel.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test/jest'; +import { mountWithIntl as mount, shallowWithIntl as shallow } from '@kbn/test-jest-helpers'; import { EuiButtonGroupProps, EuiButtonGroup } from '@elastic/eui'; import { LayerContextMenu, XyToolbar, DimensionEditor } from '.'; import { AxisSettingsPopover } from './axis_settings_popover'; diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index 927c4bbef290c..c85005c09754e 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -24,6 +24,8 @@ Run all tests from the `x-pack` root directory Lens state is kept in the Redux Store. To enable redux logger, open Chrome Developer Tools and type in the console: `window.ELASTIC_LENS_LOGGER=true`. +To simulate long running searches, set `data.search.aggs.shardDelay.enabled` in your `kibana.dev.yml` to true and set the dealy via console in the browser (e.g. for a 20 seconds delay): `window.ELASTIC_LENS_DELAY_SECONDS=20`. + ## UI Terminology Lens has a lot of UI elements – to make it easier to refer to them in issues or bugs, this is a hopefully complete list: diff --git a/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.test.ts b/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.test.ts index 397853ee2183c..655291ec357f4 100644 --- a/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.test.ts +++ b/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.test.ts @@ -13,11 +13,11 @@ import { GetMigrationFunctionObjectFn } from 'src/plugins/kibana_utils/common'; describe('embeddable migrations', () => { test('should have all saved object migrations versions (>7.13.0)', () => { - const savedObjectMigrationVersions = Object.keys(getAllMigrations({})).filter((version) => { + const savedObjectMigrationVersions = Object.keys(getAllMigrations({}, {})).filter((version) => { return semverGte(version, '7.13.1'); }); const embeddableMigrationVersions = ( - makeLensEmbeddableFactory(() => ({}))()?.migrations as GetMigrationFunctionObjectFn + makeLensEmbeddableFactory(() => ({}), {})()?.migrations as GetMigrationFunctionObjectFn )(); if (embeddableMigrationVersions) { expect(savedObjectMigrationVersions.sort()).toEqual( @@ -47,14 +47,17 @@ describe('embeddable migrations', () => { }; const migrations = ( - makeLensEmbeddableFactory(() => ({ - [migrationVersion]: (filters: Filter[]) => { - return filters.map((filterState) => ({ - ...filterState, - migrated: true, - })); - }, - }))()?.migrations as GetMigrationFunctionObjectFn + makeLensEmbeddableFactory( + () => ({ + [migrationVersion]: (filters: Filter[]) => { + return filters.map((filterState) => ({ + ...filterState, + migrated: true, + })); + }, + }), + {} + )()?.migrations as GetMigrationFunctionObjectFn )(); const migratedLensDoc = migrations[migrationVersion](lensVisualizationDoc); @@ -76,4 +79,57 @@ describe('embeddable migrations', () => { }, }); }); + + test('should properly apply a custom visualization migration', () => { + const migrationVersion = 'some-version'; + + const lensVisualizationDoc = { + attributes: { + visualizationType: 'abc', + state: { + visualization: { oldState: true }, + }, + }, + }; + + const migrationFn = jest.fn((oldState: { oldState: boolean }) => ({ + newState: oldState.oldState, + })); + + const embeddableMigrationVersions = ( + makeLensEmbeddableFactory(() => ({}), { + abc: () => ({ + [migrationVersion]: migrationFn, + }), + })()?.migrations as GetMigrationFunctionObjectFn + )(); + + const migratedLensDoc = embeddableMigrationVersions?.[migrationVersion](lensVisualizationDoc); + const otherLensDoc = embeddableMigrationVersions?.[migrationVersion]({ + ...lensVisualizationDoc, + attributes: { + ...lensVisualizationDoc.attributes, + visualizationType: 'def', + }, + }); + + expect(migrationFn).toHaveBeenCalledTimes(1); + + expect(migratedLensDoc).toEqual({ + attributes: { + visualizationType: 'abc', + state: { + visualization: { newState: true }, + }, + }, + }); + expect(otherLensDoc).toEqual({ + attributes: { + visualizationType: 'def', + state: { + visualization: { oldState: true }, + }, + }, + }); + }); }); diff --git a/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts b/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts index db912584fdcdb..39d36a9f306a2 100644 --- a/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts +++ b/x-pack/plugins/lens/server/embeddable/make_lens_embeddable_factory.ts @@ -19,9 +19,11 @@ import { commonRenameOperationsForFormula, commonRenameRecordsField, commonUpdateVisLayerType, + getLensCustomVisualizationMigrations, getLensFilterMigrations, } from '../migrations/common_migrations'; import { + CustomVisualizationMigrations, LensDocShape713, LensDocShape715, LensDocShapePre712, @@ -31,55 +33,64 @@ import { import { extract, inject } from '../../common/embeddable_factory'; export const makeLensEmbeddableFactory = - (getFilterMigrations: () => MigrateFunctionsObject) => (): EmbeddableRegistryDefinition => { + ( + getFilterMigrations: () => MigrateFunctionsObject, + customVisualizationMigrations: CustomVisualizationMigrations + ) => + (): EmbeddableRegistryDefinition => { return { id: DOC_TYPE, migrations: () => - mergeMigrationFunctionMaps(getLensFilterMigrations(getFilterMigrations()), { - // This migration is run in 7.13.1 for `by value` panels because the 7.13 release window was missed. - '7.13.1': (state) => { - const lensState = state as unknown as { attributes: LensDocShapePre712 }; - const migratedLensState = commonRenameOperationsForFormula(lensState.attributes); - return { - ...lensState, - attributes: migratedLensState, - } as unknown as SerializableRecord; - }, - '7.14.0': (state) => { - const lensState = state as unknown as { attributes: LensDocShape713 }; - const migratedLensState = commonRemoveTimezoneDateHistogramParam(lensState.attributes); - return { - ...lensState, - attributes: migratedLensState, - } as unknown as SerializableRecord; - }, - '7.15.0': (state) => { - const lensState = state as unknown as { attributes: LensDocShape715 }; - const migratedLensState = commonUpdateVisLayerType(lensState.attributes); - return { - ...lensState, - attributes: migratedLensState, - } as unknown as SerializableRecord; - }, - '7.16.0': (state) => { - const lensState = state as unknown as { attributes: LensDocShape715 }; - const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes); - return { - ...lensState, - attributes: migratedLensState, - } as unknown as SerializableRecord; - }, - '8.1.0': (state) => { - const lensState = state as unknown as { attributes: LensDocShape715 }; - const migratedLensState = commonRenameRecordsField( - commonRenameFilterReferences(lensState.attributes) - ); - return { - ...lensState, - attributes: migratedLensState, - } as unknown as SerializableRecord; - }, - }), + mergeMigrationFunctionMaps( + mergeMigrationFunctionMaps(getLensFilterMigrations(getFilterMigrations()), { + // This migration is run in 7.13.1 for `by value` panels because the 7.13 release window was missed. + '7.13.1': (state) => { + const lensState = state as unknown as { attributes: LensDocShapePre712 }; + const migratedLensState = commonRenameOperationsForFormula(lensState.attributes); + return { + ...lensState, + attributes: migratedLensState, + } as unknown as SerializableRecord; + }, + '7.14.0': (state) => { + const lensState = state as unknown as { attributes: LensDocShape713 }; + const migratedLensState = commonRemoveTimezoneDateHistogramParam( + lensState.attributes + ); + return { + ...lensState, + attributes: migratedLensState, + } as unknown as SerializableRecord; + }, + '7.15.0': (state) => { + const lensState = state as unknown as { attributes: LensDocShape715 }; + const migratedLensState = commonUpdateVisLayerType(lensState.attributes); + return { + ...lensState, + attributes: migratedLensState, + } as unknown as SerializableRecord; + }, + '7.16.0': (state) => { + const lensState = state as unknown as { attributes: LensDocShape715 }; + const migratedLensState = commonMakeReversePaletteAsCustom(lensState.attributes); + return { + ...lensState, + attributes: migratedLensState, + } as unknown as SerializableRecord; + }, + '8.1.0': (state) => { + const lensState = state as unknown as { attributes: LensDocShape715 }; + const migratedLensState = commonRenameRecordsField( + commonRenameFilterReferences(lensState.attributes) + ); + return { + ...lensState, + attributes: migratedLensState, + } as unknown as SerializableRecord; + }, + }), + getLensCustomVisualizationMigrations(customVisualizationMigrations) + ), extract, inject, }; diff --git a/x-pack/plugins/lens/server/migrations/common_migrations.ts b/x-pack/plugins/lens/server/migrations/common_migrations.ts index ad4f3406c5b3b..87edc94fd1ae6 100644 --- a/x-pack/plugins/lens/server/migrations/common_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/common_migrations.ts @@ -7,7 +7,12 @@ import { cloneDeep, mapValues } from 'lodash'; import { PaletteOutput } from 'src/plugins/charts/common'; -import { MigrateFunctionsObject } from '../../../../../src/plugins/kibana_utils/common'; +import { SerializableRecord } from '@kbn/utility-types'; +import { + mergeMigrationFunctionMaps, + MigrateFunction, + MigrateFunctionsObject, +} from '../../../../../src/plugins/kibana_utils/common'; import { LensDocShapePre712, OperationTypePre712, @@ -18,6 +23,7 @@ import { VisStatePost715, VisStatePre715, VisState716, + CustomVisualizationMigrations, LensDocShape810, } from './types'; import { CustomPaletteParams, DOCUMENT_FIELD_NAME, layerTypes } from '../../common'; @@ -186,6 +192,51 @@ export const commonRenameFilterReferences = (attributes: LensDocShape715): LensD return newAttributes as LensDocShape810; }; +const getApplyCustomVisualizationMigrationToLens = (id: string, migration: MigrateFunction) => { + return (savedObject: { attributes: LensDocShape }) => { + if (savedObject.attributes.visualizationType !== id) return savedObject; + return { + ...savedObject, + attributes: { + ...savedObject.attributes, + state: { + ...savedObject.attributes.state, + visualization: migration( + savedObject.attributes.state.visualization as SerializableRecord + ), + }, + }, + }; + }; +}; + +/** + * This creates a migration map that applies custom visualization migrations + */ +export const getLensCustomVisualizationMigrations = ( + customVisualizationMigrations: CustomVisualizationMigrations +) => { + return Object.entries(customVisualizationMigrations) + .map(([id, migrationGetter]) => { + const migrationMap: MigrateFunctionsObject = {}; + const currentMigrations = migrationGetter(); + for (const version in currentMigrations) { + if (currentMigrations.hasOwnProperty(version)) { + migrationMap[version] = getApplyCustomVisualizationMigrationToLens( + id, + currentMigrations[version] + ); + } + } + return migrationMap; + }) + .reduce( + (fullMigrationMap, currentVisualizationTypeMigrationMap) => + mergeMigrationFunctionMaps(fullMigrationMap, currentVisualizationTypeMigrationMap), + {} + ); +}; + /** * This creates a migration map that applies filter migrations to Lens visualizations */ diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts index a78cfa5e72237..11572b7a1f7d9 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.test.ts @@ -24,7 +24,7 @@ import { PaletteOutput } from 'src/plugins/charts/common'; import { Filter } from '@kbn/es-query'; describe('Lens migrations', () => { - const migrations = getAllMigrations({}); + const migrations = getAllMigrations({}, {}); describe('7.7.0 missing dimensions in XY', () => { const context = {} as SavedObjectMigrationContext; @@ -1611,14 +1611,17 @@ describe('Lens migrations', () => { }, }; - const migrationFunctionsObject = getAllMigrations({ - [migrationVersion]: (filters: Filter[]) => { - return filters.map((filterState) => ({ - ...filterState, - migrated: true, - })); + const migrationFunctionsObject = getAllMigrations( + { + [migrationVersion]: (filters: Filter[]) => { + return filters.map((filterState) => ({ + ...filterState, + migrated: true, + })); + }, }, - }); + {} + ); const migratedLensDoc = migrationFunctionsObject[migrationVersion]( lensVisualizationDoc as SavedObjectUnsanitizedDoc, @@ -1642,4 +1645,63 @@ describe('Lens migrations', () => { }, }); }); + + test('should properly apply a custom visualization migration', () => { + const migrationVersion = 'some-version'; + + const lensVisualizationDoc = { + attributes: { + visualizationType: 'abc', + state: { + visualization: { oldState: true }, + }, + }, + }; + + const migrationFn = jest.fn((oldState: { oldState: boolean }) => ({ + newState: oldState.oldState, + })); + + const migrationFunctionsObject = getAllMigrations( + {}, + { + abc: () => ({ + [migrationVersion]: migrationFn, + }), + } + ); + const migratedLensDoc = migrationFunctionsObject[migrationVersion]( + lensVisualizationDoc as SavedObjectUnsanitizedDoc, + {} as SavedObjectMigrationContext + ); + const otherLensDoc = migrationFunctionsObject[migrationVersion]( + { + ...lensVisualizationDoc, + attributes: { + ...lensVisualizationDoc.attributes, + visualizationType: 'def', + }, + } as SavedObjectUnsanitizedDoc, + {} as SavedObjectMigrationContext + ); + + expect(migrationFn).toHaveBeenCalledTimes(1); + + expect(migratedLensDoc).toEqual({ + attributes: { + visualizationType: 'abc', + state: { + visualization: { newState: true }, + }, + }, + }); + expect(otherLensDoc).toEqual({ + attributes: { + visualizationType: 'def', + state: { + visualization: { oldState: true }, + }, + }, + }); + }); }); diff --git a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts index d0abe3877c1df..8e7d555b33694 100644 --- a/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts +++ b/x-pack/plugins/lens/server/migrations/saved_object_migrations.ts @@ -27,6 +27,7 @@ import { VisStatePost715, VisStatePre715, VisState716, + CustomVisualizationMigrations, LensDocShape810, } from './types'; import { @@ -36,6 +37,7 @@ import { commonMakeReversePaletteAsCustom, commonRenameFilterReferences, getLensFilterMigrations, + getLensCustomVisualizationMigrations, commonRenameRecordsField, } from './common_migrations'; @@ -473,9 +475,13 @@ const lensMigrations: SavedObjectMigrationMap = { }; export const getAllMigrations = ( - filterMigrations: MigrateFunctionsObject + filterMigrations: MigrateFunctionsObject, + customVisualizationMigrations: CustomVisualizationMigrations ): SavedObjectMigrationMap => mergeSavedObjectMigrationMaps( - lensMigrations, - getLensFilterMigrations(filterMigrations) as unknown as SavedObjectMigrationMap + mergeSavedObjectMigrationMaps( + lensMigrations, + getLensFilterMigrations(filterMigrations) as unknown as SavedObjectMigrationMap + ), + getLensCustomVisualizationMigrations(customVisualizationMigrations) ); diff --git a/x-pack/plugins/lens/server/migrations/types.ts b/x-pack/plugins/lens/server/migrations/types.ts index de643f9234156..11c23d98dab37 100644 --- a/x-pack/plugins/lens/server/migrations/types.ts +++ b/x-pack/plugins/lens/server/migrations/types.ts @@ -8,8 +8,11 @@ import type { PaletteOutput } from 'src/plugins/charts/common'; import { Filter } from '@kbn/es-query'; import { Query } from 'src/plugins/data/public'; +import type { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common'; import type { CustomPaletteParams, LayerType, PersistableFilter } from '../../common'; +export type CustomVisualizationMigrations = Record MigrateFunctionsObject>; + export type OperationTypePre712 = | 'avg' | 'cardinality' diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index 13def5c0557c0..b63ea52f115f8 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -14,6 +14,8 @@ import { } from 'src/plugins/data/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { FieldFormatsStart } from 'src/plugins/field_formats/server'; +import type { MigrateFunctionsObject } from 'src/plugins/kibana_utils/common'; + import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { setupRoutes } from './routes'; import { getUiSettings } from './ui_settings'; @@ -26,6 +28,7 @@ import { setupSavedObjects } from './saved_objects'; import { EmbeddableSetup } from '../../../../src/plugins/embeddable/server'; import { setupExpressions } from './expressions'; import { makeLensEmbeddableFactory } from './embeddable/make_lens_embeddable_factory'; +import type { CustomVisualizationMigrations } from './migrations/types'; export interface PluginSetupContract { usageCollection?: UsageCollectionSetup; @@ -43,11 +46,22 @@ export interface PluginStartContract { } export interface LensServerPluginSetup { + /** + * Server side embeddable definition which provides migrations to run if Lens state is embedded into another saved object somewhere + */ lensEmbeddableFactory: ReturnType; + /** + * Register custom migration functions for custom third party Lens visualizations + */ + registerVisualizationMigration: ( + id: string, + migrationsGetter: () => MigrateFunctionsObject + ) => void; } export class LensServerPlugin implements Plugin { private readonly telemetryLogger: Logger; + private customVisualizationMigrations: CustomVisualizationMigrations = {}; constructor(private initializerContext: PluginInitializerContext) { this.telemetryLogger = initializerContext.logger.get('usage'); @@ -57,7 +71,7 @@ export class LensServerPlugin implements Plugin MigrateFunctionsObject + ) => { + if (this.customVisualizationMigrations[id]) { + throw new Error(`Migrations object for visualization ${id} registered already`); + } + this.customVisualizationMigrations[id] = migrationsGetter; + }, }; } diff --git a/x-pack/plugins/lens/server/saved_objects.ts b/x-pack/plugins/lens/server/saved_objects.ts index d9a851947c4d1..e36421d26e3c2 100644 --- a/x-pack/plugins/lens/server/saved_objects.ts +++ b/x-pack/plugins/lens/server/saved_objects.ts @@ -9,10 +9,12 @@ import { CoreSetup } from 'kibana/server'; import { MigrateFunctionsObject } from '../../../../src/plugins/kibana_utils/common'; import { getEditPath } from '../common'; import { getAllMigrations } from './migrations/saved_object_migrations'; +import { CustomVisualizationMigrations } from './migrations/types'; export function setupSavedObjects( core: CoreSetup, - getFilterMigrations: () => MigrateFunctionsObject + getFilterMigrations: () => MigrateFunctionsObject, + customVisualizationMigrations: CustomVisualizationMigrations ) { core.savedObjects.registerType({ name: 'lens', @@ -29,7 +31,7 @@ export function setupSavedObjects( uiCapabilitiesPath: 'visualize.show', }), }, - migrations: () => getAllMigrations(getFilterMigrations()), + migrations: () => getAllMigrations(getFilterMigrations(), customVisualizationMigrations), mappings: { properties: { title: { diff --git a/x-pack/plugins/license_management/__jest__/upload_license.test.tsx b/x-pack/plugins/license_management/__jest__/upload_license.test.tsx index a5f6a866bc9de..62b7dbf6c1a78 100644 --- a/x-pack/plugins/license_management/__jest__/upload_license.test.tsx +++ b/x-pack/plugins/license_management/__jest__/upload_license.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { Provider } from 'react-redux'; import { LocationDescriptorObject } from 'history'; import { httpServiceMock, scopedHistoryMock } from '../../../../src/core/public/mocks'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; // @ts-ignore import { uploadLicense } from '../public/application/store/actions/upload_license'; diff --git a/x-pack/plugins/license_management/__jest__/util/util.js b/x-pack/plugins/license_management/__jest__/util/util.js index eacd397d60016..25ef77612ca2c 100644 --- a/x-pack/plugins/license_management/__jest__/util/util.js +++ b/x-pack/plugins/license_management/__jest__/util/util.js @@ -10,7 +10,7 @@ import React from 'react'; import { Provider } from 'react-redux'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { httpServiceMock, scopedHistoryMock } from '../../../../../src/core/public/mocks'; import { licenseManagementStore } from '../../public/application/store/store'; import { AppContextProvider } from '../../public/application/app_context'; diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js index b9f4c6d707a3b..32103896134ad 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_editor/pipeline_editor.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import 'brace'; import { PipelineEditor } from './pipeline_editor'; diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.test.js index 398310e72be02..a99f8ce5519f5 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/add_role_alert.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { AddRoleAlert } from './add_role_alert'; describe('AddRoleAlert component', () => { diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.test.js index a8ee367e8d36f..7889a91f925f9 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/alert_call_out.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { AlertCallOut } from './alert_call_out'; describe('AlertCallOut component', () => { diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.test.js index 3d7babb95eeb9..9186d8138c094 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/confirm_delete_modal.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { ConfirmDeleteModal } from './confirm_delete_modal'; describe('ConfirmDeleteModal component', () => { diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.test.js index 13db9e6e73e60..2f0a707d880c2 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/enable_monitoring_alert.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { EnableMonitoringAlert } from './enable_monitoring_alert'; describe('EnableMonitoringAlert component', () => { diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js index fb8a5e2a627c7..620e8081c6744 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipeline_list.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { PipelineList } from './pipeline_list'; describe('PipelineList component', () => { diff --git a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js index 38213733d5e72..79c49319bfb45 100644 --- a/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js +++ b/x-pack/plugins/logstash/public/application/components/pipeline_list/pipelines_table.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { PipelinesTable } from './pipelines_table'; describe('PipelinesTable component', () => { diff --git a/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts index 55600ca307ff2..3daca585d4991 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/es_agg_utils.ts @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import type { IndexPattern, IndexPatternField } from 'src/plugins/data/common'; +import type { DataView, DataViewField } from 'src/plugins/data/common'; import { AGG_TYPE, JOIN_FIELD_NAME_PREFIX, TOP_TERM_PERCENTAGE_SUFFIX } from '../constants'; export type BucketProperties = Record; export type PropertiesMap = Map; -export function getField(indexPattern: IndexPattern, fieldName: string): IndexPatternField { +export function getField(indexPattern: DataView, fieldName: string): DataViewField { const field = indexPattern.fields.getByName(fieldName); if (!field) { throw new Error( @@ -26,7 +26,7 @@ export function getField(indexPattern: IndexPattern, fieldName: string): IndexPa return field; } -export function addFieldToDSL(dsl: object, field: IndexPatternField) { +export function addFieldToDSL(dsl: object, field: DataViewField) { return !field.scripted ? { ...dsl, field: field.name } : { diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts index 4be07c542359a..5b54cc3666278 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndexPattern } from 'src/plugins/data/public'; +import { DataView } from 'src/plugins/data/common'; import { AGG_TYPE } from '../../../../common/constants'; import { TileMetaFeature } from '../../../../common/descriptor_types'; import { CountAggField } from './count_agg_field'; @@ -61,7 +61,7 @@ export class AggField extends CountAggField { return this._aggType; } - getValueAggDsl(indexPattern: IndexPattern): unknown { + getValueAggDsl(indexPattern: DataView): unknown { const field = getField(indexPattern, this.getRootName()); const aggType = this._getAggType(); const aggBody = aggType === AGG_TYPE.TERMS ? { size: 1, shard_size: TERMS_AGG_SHARD_SIZE } : {}; diff --git a/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts b/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts index 4ab2f1b211a30..ae30e339f88ba 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/agg_field_types.ts @@ -6,12 +6,12 @@ */ import { IField } from '../field'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataView } from '../../../../../../../src/plugins/data/common'; import { IESAggSource } from '../../sources/es_agg_source'; import { FIELD_ORIGIN } from '../../../../common/constants'; export interface IESAggField extends IField { - getValueAggDsl(indexPattern: IndexPattern): unknown | null; + getValueAggDsl(indexPattern: DataView): unknown | null; getBucketCount(): number; } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts index bbbd512c4fa49..d35a61c232adc 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/count_agg_field.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndexPattern } from 'src/plugins/data/public'; +import { DataView } from 'src/plugins/data/common'; import { IESAggSource } from '../../sources/es_agg_source'; import { IVectorSource } from '../../sources/vector_source'; import { AGG_TYPE, FIELD_ORIGIN } from '../../../../common/constants'; @@ -83,7 +83,7 @@ export class CountAggField implements IESAggField { ); } - getValueAggDsl(indexPattern: IndexPattern): unknown | null { + getValueAggDsl(indexPattern: DataView): unknown | null { return null; } diff --git a/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts b/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts index 4591b252de279..b2b5f01c31bed 100644 --- a/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts +++ b/x-pack/plugins/maps/public/classes/fields/agg/percentile_agg_field.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndexPattern } from 'src/plugins/data/common'; +import { DataView } from 'src/plugins/data/common'; import { i18n } from '@kbn/i18n'; import { AGG_TYPE } from '../../../../common/constants'; import { IESAggField, CountAggFieldParams } from './agg_field_types'; @@ -67,7 +67,7 @@ export class PercentileAggField extends AggField implements IESAggField { return `${super.getName()}_${this._percentile}`; } - getValueAggDsl(indexPattern: IndexPattern): unknown { + getValueAggDsl(indexPattern: DataView): unknown { const field = getField(indexPattern, this.getRootName()); const dsl: Record = addFieldToDSL({}, field); dsl.percents = [this._percentile]; diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/choropleth_layer_wizard/layer_template.tsx b/x-pack/plugins/maps/public/classes/layers/wizards/choropleth_layer_wizard/layer_template.tsx index 2f075b0e0ccad..c935bf0b182c7 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/choropleth_layer_wizard/layer_template.tsx +++ b/x-pack/plugins/maps/public/classes/layers/wizards/choropleth_layer_wizard/layer_template.tsx @@ -18,7 +18,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; +import { DataViewField, DataView } from 'src/plugins/data/common'; import { getDataViewLabel, getDataViewSelectPlaceholder } from '../../../../../common/i18n_getters'; import { RenderWizardArguments } from '../layer_wizard_registry'; import { EMSFileSelect } from '../../../../components/ems_file_select'; @@ -59,15 +59,15 @@ interface State { leftSource: BOUNDARIES_SOURCE; leftEmsFileId: string | null; leftEmsFields: Array>; - leftIndexPattern: IndexPattern | null; - leftGeoFields: IndexPatternField[]; - leftJoinFields: IndexPatternField[]; + leftIndexPattern: DataView | null; + leftGeoFields: DataViewField[]; + leftJoinFields: DataViewField[]; leftGeoField: string | null; leftEmsJoinField: string | null; leftElasticsearchJoinField: string | null; rightIndexPatternId: string; rightIndexPatternTitle: string | null; - rightTermsFields: IndexPatternField[]; + rightTermsFields: DataViewField[]; rightJoinField: string | null; } @@ -157,7 +157,7 @@ export class LayerTemplate extends Component { ); }; - _onLeftIndexPatternChange = (indexPattern: IndexPattern) => { + _onLeftIndexPatternChange = (indexPattern: DataView) => { this.setState( { leftIndexPattern: indexPattern, diff --git a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts index 7f99cd59f1d61..610655f0daee5 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_agg_source/es_agg_source.ts @@ -14,7 +14,7 @@ import { esAggFieldsFactory, IESAggField } from '../../fields/agg'; import { AGG_TYPE, COUNT_PROP_LABEL, FIELD_ORIGIN } from '../../../../common/constants'; import { getSourceAggKey } from '../../../../common/get_agg_key'; import { AbstractESAggSourceDescriptor, AggDescriptor } from '../../../../common/descriptor_types'; -import { IndexPattern } from '../../../../../../../src/plugins/data/public'; +import { DataView } from '../../../../../../../src/plugins/data/common'; import { IField } from '../../fields/field'; import { ITooltipProperty } from '../../tooltips/tooltip_property'; @@ -25,7 +25,7 @@ export interface IESAggSource extends IESSource { getAggLabel(aggType: AGG_TYPE, fieldLabel: string): string; getMetricFields(): IESAggField[]; getMetricFieldForName(fieldName: string): IESAggField | null; - getValueAggsDsl(indexPattern: IndexPattern): { [key: string]: unknown }; + getValueAggsDsl(indexPattern: DataView): { [key: string]: unknown }; } export abstract class AbstractESAggSource extends AbstractESSource implements IESAggSource { @@ -107,7 +107,7 @@ export abstract class AbstractESAggSource extends AbstractESSource implements IE return this.getMetricFields(); } - getValueAggsDsl(indexPattern: IndexPattern, metricsFilter?: (metric: IESAggField) => boolean) { + getValueAggsDsl(indexPattern: DataView, metricsFilter?: (metric: IESAggField) => boolean) { const valueAggsDsl: { [key: string]: unknown } = {}; this.getMetricFields() .filter((esAggMetric) => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index bb2a61f5cd1ad..2b9ab6df2d1d0 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -41,7 +41,7 @@ import { } from '../../../../common/descriptor_types'; import { ImmutableSourceProperty, SourceEditorArgs } from '../source'; import { ISearchSource } from '../../../../../../../src/plugins/data/common/search/search_source'; -import { IndexPattern } from '../../../../../../../src/plugins/data/common'; +import { DataView } from '../../../../../../../src/plugins/data/common'; import { Adapters } from '../../../../../../../src/plugins/inspector/common/adapters'; import { isValidStringConfig } from '../../util/valid_string_config'; import { makePublicExecutionContext } from '../../../util'; @@ -211,7 +211,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo }: { searchSource: ISearchSource; searchSessionId?: string; - indexPattern: IndexPattern; + indexPattern: DataView; precision: number; layerName: string; registerCancelCallback: (callback: () => void) => void; @@ -317,7 +317,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo }: { searchSource: ISearchSource; searchSessionId?: string; - indexPattern: IndexPattern; + indexPattern: DataView; precision: number; layerName: string; registerCancelCallback: (callback: () => void) => void; @@ -385,7 +385,7 @@ export class ESGeoGridSource extends AbstractESAggSource implements IMvtVectorSo throw new Error('Cannot get GeoJson without searchFilter.buffer'); } - const indexPattern: IndexPattern = await this.getIndexPattern(); + const indexPattern: DataView = await this.getIndexPattern(); const searchSource: ISearchSource = await this.makeSearchSource(searchFilters, 0); searchSource.setField('trackTotalHits', false); diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/create_source_editor.tsx index 766c71939afdb..dff515a5192dc 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/create_source_editor.tsx @@ -7,7 +7,7 @@ import React, { Component } from 'react'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataView } from 'src/plugins/data/common'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiPanel } from '@elastic/eui'; import { SingleFieldSelect } from '../../../components/single_field_select'; @@ -28,7 +28,7 @@ interface Props { } interface State { - indexPattern: IndexPattern | null; + indexPattern: DataView | null; geoField: string; splitField: string; sortField: string; @@ -42,7 +42,7 @@ export class CreateSourceEditor extends Component { sortField: '', }; - _onIndexPatternSelect = (indexPattern: IndexPattern) => { + _onIndexPatternSelect = (indexPattern: DataView) => { const pointFields = getGeoPointFields(indexPattern.fields); this.setState( { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx index 081272f40b344..129301a96e3b3 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataView } from 'src/plugins/data/common'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SingleFieldSelect } from '../../../components/single_field_select'; @@ -15,7 +15,7 @@ import { getTermsFields } from '../../../index_pattern_util'; import { indexPatterns } from '../../../../../../../src/plugins/data/public'; interface Props { - indexPattern: IndexPattern; + indexPattern: DataView; onSortFieldChange: (fieldName: string) => void; onSplitFieldChange: (fieldName: string) => void; sortField: string; diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx index d04f16a5c59e2..e5af92ccec7ef 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/update_source_editor.tsx @@ -9,7 +9,7 @@ import React, { Fragment, Component } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; -import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; +import type { DataViewField, DataView } from 'src/plugins/data/common'; import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { MetricsEditor } from '../../../components/metrics_editor'; import { getIndexPatternService } from '../../../kibana_services'; @@ -26,8 +26,8 @@ interface Props { } interface State { - indexPattern: IndexPattern | null; - fields: IndexPatternField[]; + indexPattern: DataView | null; + fields: DataViewField[]; } export class UpdateSourceEditor extends Component { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index e165fdcdaac5f..5c44c5612bf53 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -11,7 +11,7 @@ import rison from 'rison-node'; import { i18n } from '@kbn/i18n'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import { type Filter, buildPhraseFilter } from '@kbn/es-query'; -import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; +import type { DataViewField, DataView } from 'src/plugins/data/common'; import { AbstractESSource } from '../es_source'; import { getHttp, @@ -193,7 +193,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource async getFields(): Promise { try { const indexPattern = await this.getIndexPattern(); - const fields: IndexPatternField[] = indexPattern.fields.filter((field) => { + const fields: DataViewField[] = indexPattern.fields.filter((field) => { // Ensure fielddata is enabled for field. // Search does not request _source return field.aggregatable; @@ -279,7 +279,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource throw new Error('Cannot _getTopHits without topHitsSplitField'); } - const indexPattern: IndexPattern = await this.getIndexPattern(); + const indexPattern: DataView = await this.getIndexPattern(); const fieldNames = searchFilters.fieldNames.filter( (fieldName) => fieldName !== this._descriptor.geoField @@ -315,7 +315,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource }; } - const topHitsSplitField: IndexPatternField = getField(indexPattern, topHitsSplitFieldName); + const topHitsSplitField: DataViewField = getField(indexPattern, topHitsSplitFieldName); const cardinalityAgg = { precision_threshold: 1 }; const termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT, @@ -570,7 +570,7 @@ export class ESSearchSource extends AbstractESSource implements IMvtVectorSource return this._tooltipFields.length > 0; } - async _loadTooltipProperties(docId: string | number, index: string, indexPattern: IndexPattern) { + async _loadTooltipProperties(docId: string | number, index: string, indexPattern: DataView) { if (this._tooltipFields.length === 0) { return {}; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx index f6262e426033a..95836d76a2c60 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/top_hits/create_source_editor.tsx @@ -8,7 +8,7 @@ import React, { Component } from 'react'; import { EuiPanel } from '@elastic/eui'; -import type { IndexPattern, IndexPatternField } from 'src/plugins/data/public'; +import type { DataView, DataViewField } from 'src/plugins/data/common'; import { SCALING_TYPES } from '../../../../../common/constants'; import { GeoFieldSelect } from '../../../../components/geo_field_select'; import { GeoIndexPatternSelect } from '../../../../components/geo_index_pattern_select'; @@ -23,13 +23,13 @@ interface Props { } interface State { - indexPattern: IndexPattern | null; - geoFields: IndexPatternField[]; + indexPattern: DataView | null; + geoFields: DataViewField[]; geoFieldName: string | null; sortField: string | null; - sortFields: IndexPatternField[]; + sortFields: DataViewField[]; sortOrder: SortDirection; - termFields: IndexPatternField[]; + termFields: DataViewField[]; topHitsSplitField: string | null; topHitsSize: number; } @@ -47,7 +47,7 @@ export class CreateSourceEditor extends Component { topHitsSize: 1, }; - _onIndexPatternSelect = (indexPattern: IndexPattern) => { + _onIndexPatternSelect = (indexPattern: DataView) => { const geoFields = getGeoFields(indexPattern.fields); this.setState( diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts index 23f896a4b9e8f..c0ce7488d01ed 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/get_docvalue_source_fields.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; +import { DataView } from '../../../../../../../../src/plugins/data/common'; import { getField } from '../../../../../common/elasticsearch_util'; export interface ScriptField { @@ -14,7 +14,7 @@ export interface ScriptField { } export function getDocValueAndSourceFields( - indexPattern: IndexPattern, + indexPattern: DataView, fieldNames: string[], dateFormat: string ): { diff --git a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts index 54746773b6317..e1090a16ec665 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_source/es_source.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { Filter } from '@kbn/es-query'; -import { IndexPatternField, IndexPattern, ISearchSource } from 'src/plugins/data/public'; +import { DataViewField, DataView, ISearchSource } from 'src/plugins/data/common'; import type { Query } from 'src/plugins/data/common'; import type { KibanaExecutionContext } from 'kibana/public'; import { AbstractVectorSource, BoundsRequestMeta } from '../vector_source'; @@ -48,7 +48,7 @@ export function isSearchSourceAbortError(error: Error) { export interface IESSource extends IVectorSource { isESSource(): true; getId(): string; - getIndexPattern(): Promise; + getIndexPattern(): Promise; getIndexPatternId(): string; getGeoFieldName(): string; loadStylePropsMeta({ @@ -71,7 +71,7 @@ export interface IESSource extends IVectorSource { } export class AbstractESSource extends AbstractVectorSource implements IESSource { - indexPattern?: IndexPattern; + indexPattern?: DataView; readonly _descriptor: AbstractESSourceDescriptor; @@ -346,7 +346,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource return this._descriptor.geoField; } - async getIndexPattern(): Promise { + async getIndexPattern(): Promise { // Do we need this cache? Doesn't the IndexPatternService take care of this? if (this.indexPattern) { return this.indexPattern; @@ -369,7 +369,7 @@ export class AbstractESSource extends AbstractVectorSource implements IESSource } } - async _getGeoField(): Promise { + async _getGeoField(): Promise { const indexPattern = await this.getIndexPattern(); const geoField = indexPattern.fields.getByName(this.getGeoFieldName()); if (!geoField) { 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 index 4e6b8ea73beae..0681d12007c4a 100644 --- 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 @@ -50,6 +50,9 @@ describe('renderLegendDetailRow', () => { supportsFieldMetaFromEs: () => { return true; }, + supportsFieldMetaFromLocalData: () => { + return true; + }, } as unknown as IField; const sizeProp = new DynamicSizeProperty( { minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } }, diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx index 543429ce77a26..517bb68a0025d 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_style_property.tsx @@ -295,7 +295,20 @@ export class DynamicStyleProperty } getFieldMetaOptions() { - return _.get(this.getOptions(), 'fieldMetaOptions', { isEnabled: true }); + const fieldMetaOptions = _.get(this.getOptions(), 'fieldMetaOptions', { isEnabled: true }); + + // In 8.0, UI changed to not allow setting isEnabled to false when fieldMeta from local not supported + // Saved objects created prior to 8.0 may have a configuration where + // fieldMetaOptions.isEnabled is false and the field does not support fieldMeta from local. + // In these cases, force isEnabled to true + // The exact case that spawned this fix is with ES_SEARCH sources and 8.0 where vector tiles switched + // from vector tiles generated via Kibana server to vector tiles generated via Elasticsearch. + // Kibana vector tiles supported fieldMeta from local while Elasticsearch vector tiles do not support fieldMeta from local. + if (this._field && !this._field.supportsFieldMetaFromLocalData()) { + fieldMetaOptions.isEnabled = true; + } + + return fieldMetaOptions; } getDataMappingFunction() { diff --git a/x-pack/plugins/maps/public/classes/tooltips/es_agg_tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/es_agg_tooltip_property.ts index da70fdefdd2ca..54d5495db2389 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/es_agg_tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/es_agg_tooltip_property.ts @@ -9,7 +9,7 @@ import { ESTooltipProperty } from './es_tooltip_property'; import { AGG_TYPE } from '../../../common/constants'; import { ITooltipProperty } from './tooltip_property'; import { IESAggField } from '../fields/agg'; -import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { DataView } from '../../../../../../src/plugins/data/common'; export class ESAggTooltipProperty extends ESTooltipProperty { private readonly _aggType: AGG_TYPE; @@ -17,7 +17,7 @@ export class ESAggTooltipProperty extends ESTooltipProperty { constructor( tooltipProperty: ITooltipProperty, - indexPattern: IndexPattern, + indexPattern: DataView, field: IESAggField, aggType: AGG_TYPE, applyGlobalQuery: boolean diff --git a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts index ab42e9a12f420..04e8086ea6480 100644 --- a/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/classes/tooltips/es_tooltip_property.ts @@ -9,17 +9,17 @@ import _ from 'lodash'; import { type Filter, buildExistsFilter, buildPhraseFilter } from '@kbn/es-query'; import { ITooltipProperty } from './tooltip_property'; import { IField } from '../fields/field'; -import { IndexPattern, IndexPatternField } from '../../../../../../src/plugins/data/public'; +import { DataView, DataViewField } from '../../../../../../src/plugins/data/common'; export class ESTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; - private readonly _indexPattern: IndexPattern; + private readonly _indexPattern: DataView; private readonly _field: IField; private readonly _applyGlobalQuery: boolean; constructor( tooltipProperty: ITooltipProperty, - indexPattern: IndexPattern, + indexPattern: DataView, field: IField, applyGlobalQuery: boolean ) { @@ -41,7 +41,7 @@ export class ESTooltipProperty implements ITooltipProperty { return this._tooltipProperty.getRawValue(); } - _getIndexPatternField(): IndexPatternField | undefined { + _getIndexPatternField(): DataViewField | undefined { return this._indexPattern.fields.getByName(this._field.getRootName()); } diff --git a/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx b/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx index 325b619c4f37a..57f1eee3acf49 100644 --- a/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx +++ b/x-pack/plugins/maps/public/components/geo_index_pattern_select.tsx @@ -9,7 +9,7 @@ import React, { Component } from 'react'; import { EuiCallOut, EuiFormRow, EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { IndexPattern } from 'src/plugins/data/public'; +import { DataView } from 'src/plugins/data/common'; import { getIndexPatternSelectComponent, getIndexPatternService, @@ -19,7 +19,7 @@ import { getDataViewLabel, getDataViewSelectPlaceholder } from '../../common/i18 import { ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPES } from '../../common/constants'; interface Props { - onChange: (indexPattern: IndexPattern) => void; + onChange: (indexPattern: DataView) => void; value: string | null; isGeoPointsOnly?: boolean; } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx index 2cb8d9659be6c..07550c76b0895 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/filter_editor/filter_editor.tsx @@ -23,7 +23,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; -import type { IndexPattern, Query } from 'src/plugins/data/public'; +import type { DataView, Query } from 'src/plugins/data/common'; import { APP_ID } from '../../../../common/constants'; import { getIndexPatternService, getData } from '../../../kibana_services'; import { GlobalFilterCheckbox } from '../../../components/global_filter_checkbox'; @@ -39,7 +39,7 @@ export interface Props { interface State { isPopoverOpen: boolean; - indexPatterns: IndexPattern[]; + indexPatterns: DataView[]; isSourceTimeAware: boolean; } @@ -64,7 +64,7 @@ export class FilterEditor extends Component { async _loadIndexPatterns() { // Filter only effects source so only load source indices. const indexPatternIds = this.props.layer.getSource().getIndexPatternIds(); - const indexPatterns: IndexPattern[] = []; + const indexPatterns: DataView[] = []; const getIndexPatternPromises = indexPatternIds.map(async (indexPatternId) => { try { const indexPattern = await getIndexPatternService().get(indexPatternId); diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx index a33f461acb700..bb6df355e3ee7 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/join.tsx @@ -9,7 +9,7 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { IndexPatternField, IndexPattern, Query } from 'src/plugins/data/public'; +import type { DataViewField, DataView, Query } from 'src/plugins/data/common'; import { JoinExpression } from './join_expression'; import { MetricsExpression } from './metrics_expression'; import { WhereExpression } from './where_expression'; @@ -39,8 +39,8 @@ interface Props { } interface State { - rightFields: IndexPatternField[]; - indexPattern?: IndexPattern; + rightFields: DataViewField[]; + indexPattern?: DataView; loadError?: string; } diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/where_expression.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/where_expression.tsx index 41acd7286c568..883be0e890cb7 100644 --- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/where_expression.tsx +++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/join_editor/resources/where_expression.tsx @@ -9,12 +9,12 @@ import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiPopover, EuiExpression, EuiFormHelpText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { IndexPattern, Query } from 'src/plugins/data/public'; +import { DataView, Query } from 'src/plugins/data/common'; import { APP_ID } from '../../../../../common/constants'; import { getData } from '../../../../kibana_services'; interface Props { - indexPattern: IndexPattern; + indexPattern: DataView; onChange: (whereQuery?: Query) => void; whereQuery?: Query; } diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index ce1856123d6ed..b401f50c00b07 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -6,7 +6,7 @@ */ import type { Filter } from '@kbn/es-query'; -import type { IndexPattern } from '../../../../../src/plugins/data/common'; +import type { DataView } from '../../../../../src/plugins/data/common'; import { Embeddable, EmbeddableInput, @@ -43,7 +43,7 @@ export type MapByReferenceInput = SavedObjectEmbeddableInput & { export type MapEmbeddableInput = MapByValueInput | MapByReferenceInput; export type MapEmbeddableOutput = EmbeddableOutput & { - indexPatterns: IndexPattern[]; + indexPatterns: DataView[]; }; export type MapEmbeddableType = Embeddable & { diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index 5778c8d3b654f..071300b7784fb 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -20,6 +20,8 @@ export const plugin: PluginInitializer = ( export { MAP_SAVED_OBJECT_TYPE } from '../common/constants'; export type { PreIndexedShape } from '../common/elasticsearch_util'; +export { GEOJSON_FEATURE_ID_PROPERTY_NAME } from './classes/layers/vector_layer/geojson_vector_layer/assign_feature_ids'; + export type { ITooltipProperty, RenderTooltipContentParams, diff --git a/x-pack/plugins/maps/public/index_pattern_util.ts b/x-pack/plugins/maps/public/index_pattern_util.ts index 844d810f61a66..f0762f743184d 100644 --- a/x-pack/plugins/maps/public/index_pattern_util.ts +++ b/x-pack/plugins/maps/public/index_pattern_util.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { IndexPatternField, IndexPattern } from 'src/plugins/data/public'; +import type { DataViewField, DataView } from 'src/plugins/data/common'; import { i18n } from '@kbn/i18n'; import { asyncMap } from '@kbn/std'; import { getIndexPatternService } from './kibana_services'; @@ -13,7 +13,7 @@ import { indexPatterns } from '../../../../src/plugins/data/public'; import { ES_GEO_FIELD_TYPE, ES_GEO_FIELD_TYPES } from '../common/constants'; import { getIsGoldPlus } from './licensed_features'; -export function getGeoTileAggNotSupportedReason(field: IndexPatternField): string | null { +export function getGeoTileAggNotSupportedReason(field: DataViewField): string | null { if (!field.aggregatable) { return i18n.translate('xpack.maps.geoTileAgg.disabled.docValues', { defaultMessage: @@ -30,12 +30,10 @@ export function getGeoTileAggNotSupportedReason(field: IndexPatternField): strin return null; } -export async function getIndexPatternsFromIds( - indexPatternIds: string[] = [] -): Promise { +export async function getIndexPatternsFromIds(indexPatternIds: string[] = []): Promise { const results = await asyncMap(indexPatternIds, async (indexPatternId) => { try { - return (await getIndexPatternService().get(indexPatternId)) as IndexPattern; + return (await getIndexPatternService().get(indexPatternId)) as DataView; } catch (error) { // Unable to load index pattern, better to not throw error so map can render // Error will be surfaced by layer since it too will be unable to locate the index pattern @@ -43,10 +41,10 @@ export async function getIndexPatternsFromIds( } }); - return results.filter((r): r is IndexPattern => r !== null); + return results.filter((r): r is DataView => r !== null); } -export function getTermsFields(fields: IndexPatternField[]): IndexPatternField[] { +export function getTermsFields(fields: DataViewField[]): DataViewField[] { return fields.filter((field) => { return ( field.aggregatable && @@ -56,7 +54,7 @@ export function getTermsFields(fields: IndexPatternField[]): IndexPatternField[] }); } -export function getSortFields(fields: IndexPatternField[]): IndexPatternField[] { +export function getSortFields(fields: DataViewField[]): DataViewField[] { return fields.filter((field) => { return field.sortable && !indexPatterns.isNestedField(field); }); @@ -70,23 +68,23 @@ export function getAggregatableGeoFieldTypes(): string[] { return aggregatableFieldTypes; } -export function getGeoFields(fields: IndexPatternField[]): IndexPatternField[] { +export function getGeoFields(fields: DataViewField[]): DataViewField[] { return fields.filter((field) => { return !indexPatterns.isNestedField(field) && ES_GEO_FIELD_TYPES.includes(field.type); }); } -export function getGeoPointFields(fields: IndexPatternField[]): IndexPatternField[] { +export function getGeoPointFields(fields: DataViewField[]): DataViewField[] { return fields.filter((field) => { return !indexPatterns.isNestedField(field) && ES_GEO_FIELD_TYPE.GEO_POINT === field.type; }); } -export function getFieldsWithGeoTileAgg(fields: IndexPatternField[]): IndexPatternField[] { +export function getFieldsWithGeoTileAgg(fields: DataViewField[]): DataViewField[] { return fields.filter(supportsGeoTileAgg); } -export function supportsGeoTileAgg(field?: IndexPatternField): boolean { +export function supportsGeoTileAgg(field?: DataViewField): boolean { return ( !!field && !!field.aggregatable && @@ -95,7 +93,7 @@ export function supportsGeoTileAgg(field?: IndexPatternField): boolean { ); } -export function getSourceFields(fields: IndexPatternField[]): IndexPatternField[] { +export function getSourceFields(fields: DataViewField[]): DataViewField[] { return fields.filter((field) => { // Multi fields are not stored in _source and only exist in index. return !field.isSubtypeMulti() && !field.isSubtypeNested(); diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 6a2881a5dd274..9aede248e1877 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -13,7 +13,7 @@ import { AppLeaveAction, AppMountParameters } from 'kibana/public'; import { Adapters } from 'src/plugins/embeddable/public'; import { Subscription } from 'rxjs'; import { type Filter, FilterStateStore } from '@kbn/es-query'; -import type { Query, TimeRange, IndexPattern } from 'src/plugins/data/common'; +import type { Query, TimeRange, DataView } from 'src/plugins/data/common'; import { getData, getCoreChrome, @@ -87,7 +87,7 @@ export interface Props { export interface State { initialized: boolean; - indexPatterns: IndexPattern[]; + indexPatterns: DataView[]; savedQuery?: SavedQuery; isRefreshPaused: boolean; refreshInterval: number; diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index 35c3ad044216c..50b21433ebf2c 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -5,12 +5,12 @@ * 2.0. */ -import _ from 'lodash'; import { CoreStart, Logger } from 'src/core/server'; import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { Stream } from 'stream'; import { isAbortError } from './util'; import { makeExecutionContext } from '../../common/execution_context'; +import { Field, mergeFields } from './merge_fields'; export async function getEsTile({ url, @@ -39,14 +39,19 @@ export async function getEsTile({ }): Promise { try { const path = `/${encodeURIComponent(index)}/_mvt/${geometryFieldName}/${z}/${x}/${y}`; - let fields = _.uniq(requestBody.docvalue_fields.concat(requestBody.stored_fields)); - fields = fields.filter((f) => f !== geometryFieldName); + const body = { grid_precision: 0, // no aggs exact_bounds: true, extent: 4096, // full resolution, query: requestBody.query, - fields, + fields: mergeFields( + [ + requestBody.docvalue_fields as Field[] | undefined, + requestBody.stored_fields as Field[] | undefined, + ], + [geometryFieldName] + ), runtime_mappings: requestBody.runtime_mappings, track_total_hits: requestBody.size + 1, }; diff --git a/x-pack/plugins/maps/server/mvt/merge_fields.ts b/x-pack/plugins/maps/server/mvt/merge_fields.ts new file mode 100644 index 0000000000000..e371f3ff0715b --- /dev/null +++ b/x-pack/plugins/maps/server/mvt/merge_fields.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// can not use "import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey" +// SearchRequest is incorrectly typed and does not support Field as object +// https://github.com/elastic/elasticsearch-js/issues/1615 +export type Field = + | string + | { + field: string; + format: string; + }; + +export function mergeFields( + fieldsList: Array, + excludeNames: string[] +): Field[] { + const fieldNames: string[] = []; + const mergedFields: Field[] = []; + + fieldsList.forEach((fields) => { + if (!fields) { + return; + } + + fields.forEach((field) => { + const fieldName = typeof field === 'string' ? field : field.field; + if (!excludeNames.includes(fieldName) && !fieldNames.includes(fieldName)) { + fieldNames.push(fieldName); + mergedFields.push(field); + } + }); + }); + + return mergedFields; +} diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 3c39f0367c54f..2b00d53e35e54 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -135,7 +135,7 @@ export const renderApp = ( application: coreStart.application, http: coreStart.http, security: deps.security, - urlGenerators: deps.share.urlGenerators, + dashboard: deps.dashboard, maps: deps.maps, dataVisualizer: deps.dataVisualizer, dataViews: deps.data.dataViews, diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotation_description_list/index.test.tsx b/x-pack/plugins/ml/public/application/components/annotations/annotation_description_list/index.test.tsx index 430b56b4b52b9..bb9fa3a8ec4d6 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotation_description_list/index.test.tsx +++ b/x-pack/plugins/ml/public/application/components/annotations/annotation_description_list/index.test.tsx @@ -9,7 +9,7 @@ import mockAnnotations from '../annotations_table/__mocks__/mock_annotations.jso import moment from 'moment-timezone'; import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { AnnotationDescriptionList } from './index'; diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.test.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.test.js index 0caf92baca88d..1681d151cd31f 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.test.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.test.js @@ -8,7 +8,7 @@ import jobConfig from '../../../../../common/types/__mocks__/job_config_farequote'; import mockAnnotations from './__mocks__/mock_annotations.json'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { AnnotationsTable } from './annotations_table'; diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js index 668d392bffbbe..67f6a9b12dfa1 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomaly_details.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { AnomalyDetails } from './anomaly_details'; const props = { diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx index 8e8ee3fd4ae8c..acca495a9c900 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/links_menu.tsx @@ -8,8 +8,14 @@ import { cloneDeep } from 'lodash'; import moment from 'moment'; import rison, { RisonValue } from 'rison-node'; -import React, { useEffect, useState } from 'react'; -import { EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; +import React, { FC, useEffect, useMemo, useState } from 'react'; +import { + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiPopover, + EuiProgress, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { ES_FIELD_TYPES } from '@kbn/field-types'; @@ -29,13 +35,15 @@ import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; import { escapeDoubleQuotes } from '../../explorer/explorer_utils'; import { isCategorizationAnomaly, isRuleSupported } from '../../../../common/util/anomaly_utils'; import { checkPermission } from '../../capabilities/check_capabilities'; -import { withKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { CustomUrlAnomalyRecordDoc, KibanaUrlConfig } from '../../../../common/types/custom_urls'; -import { TimeRangeBounds } from '../../util/time_buckets'; -import { MlKibanaReactContextValue } from '../../contexts/kibana'; +import type { + CustomUrlAnomalyRecordDoc, + KibanaUrlConfig, +} from '../../../../common/types/custom_urls'; +import type { TimeRangeBounds } from '../../util/time_buckets'; +import { useMlKibana } from '../../contexts/kibana'; // @ts-ignore import { getFieldTypeFromMapping } from '../../services/mapping_service'; -import { AnomaliesTableRecord } from '../../../../common/types/anomalies'; +import type { AnomaliesTableRecord } from '../../../../common/types/anomalies'; interface LinksMenuProps { anomaly: AnomaliesTableRecord; @@ -44,19 +52,25 @@ interface LinksMenuProps { isAggregatedData: boolean; interval: 'day' | 'hour' | 'second'; showRuleEditorFlyout: (anomaly: AnomaliesTableRecord) => void; - kibana: MlKibanaReactContextValue; + onItemClick: () => void; } export const LinksMenuUI = (props: LinksMenuProps) => { - const [isPopoverOpen, setPopoverOpen] = useState(false); const [openInDiscoverUrl, setOpenInDiscoverUrl] = useState(); + const isCategorizationAnomalyRecord = isCategorizationAnomaly(props.anomaly); + + const closePopover = props.onItemClick; + + const kibana = useMlKibana(); + useEffect(() => { let unmounted = false; + const generateDiscoverUrl = async () => { const { services: { share }, - } = props.kibana; + } = kibana; const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR'); if (!discoverLocator) { @@ -125,7 +139,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { } }; - if (!isCategorizationAnomaly(props.anomaly)) { + if (!isCategorizationAnomalyRecord) { generateDiscoverUrl(); } @@ -134,10 +148,6 @@ export const LinksMenuUI = (props: LinksMenuProps) => { }; }, [JSON.stringify(props.anomaly)]); - const onButtonClick = () => setPopoverOpen(!isPopoverOpen); - - const closePopover = () => setPopoverOpen(false); - const openCustomUrl = (customUrl: KibanaUrlConfig) => { const { anomaly, interval, isAggregatedData } = props; @@ -151,7 +161,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { const configuredUrlValue = customUrl.url_value; const timeRangeInterval = customUrl.time_range !== undefined ? parseInterval(customUrl.time_range) : null; - const basePath = props.kibana.services.http.basePath.get(); + const basePath = kibana.services.http.basePath.get(); if (configuredUrlValue.includes('$earliest$')) { let earliestMoment = moment(timestamp); @@ -213,7 +223,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { .catch((resp) => { // eslint-disable-next-line no-console console.log('openCustomUrl(): error loading categoryDefinition:', resp); - const { toasts } = props.kibana.services.notifications; + const { toasts } = kibana.services.notifications; toasts.addDanger( i18n.translate('xpack.ml.anomaliesTable.linksMenu.unableToOpenLinkErrorMessage', { defaultMessage: @@ -235,7 +245,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { const viewSeries = async () => { const { services: { share }, - } = props.kibana; + } = kibana; const mlLocator = share.url.locators.get(ML_APP_LOCATOR); const record = props.anomaly.source; @@ -319,7 +329,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { if (job === undefined) { // eslint-disable-next-line no-console console.log(`viewExamples(): no job found with ID: ${props.anomaly.jobId}`); - const { toasts } = props.kibana.services.notifications; + const { toasts } = kibana.services.notifications; toasts.addDanger( i18n.translate('xpack.ml.anomaliesTable.linksMenu.unableToViewExamplesErrorMessage', { defaultMessage: 'Unable to view examples as no details could be found for job ID {jobId}', @@ -346,7 +356,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { `viewExamples(): error finding type of field ${categorizationFieldName} in indices:`, datafeedIndices ); - const { toasts } = props.kibana.services.notifications; + const { toasts } = kibana.services.notifications; toasts.addDanger( i18n.translate('xpack.ml.anomaliesTable.linksMenu.noMappingCouldBeFoundErrorMessage', { defaultMessage: @@ -425,7 +435,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { const _a = rison.encode(appStateProps); // Need to encode the _a parameter as it will contain characters such as '+' if using the regex. - const { basePath } = props.kibana.services.http; + const { basePath } = kibana.services.http; let path = basePath.get(); path += '/app/discover#/'; path += '?_g=' + _g; @@ -435,7 +445,7 @@ export const LinksMenuUI = (props: LinksMenuProps) => { .catch((resp) => { // eslint-disable-next-line no-console console.log('viewExamples(): error loading categoryDefinition:', resp); - const { toasts } = props.kibana.services.notifications; + const { toasts } = kibana.services.notifications; toasts.addDanger( i18n.translate('xpack.ml.anomaliesTable.linksMenu.loadingDetailsErrorMessage', { defaultMessage: @@ -471,6 +481,127 @@ export const LinksMenuUI = (props: LinksMenuProps) => { const { anomaly, showViewSeriesLink } = props; const canConfigureRules = isRuleSupported(anomaly.source) && checkPermission('canUpdateJob'); + const contextMenuItems = useMemo(() => { + const items = []; + if (anomaly.customUrls !== undefined) { + anomaly.customUrls.forEach((customUrl, index) => { + items.push( + { + closePopover(); + openCustomUrl(customUrl); + }} + data-test-subj={`mlAnomaliesListRowActionCustomUrlButton_${index}`} + > + {customUrl.url_name} + + ); + }); + } + + if (!isCategorizationAnomalyRecord) { + // Add item from the start, but disable it during the URL generation. + const isLoading = openInDiscoverUrl === undefined; + + items.push( + + + {isLoading ? : null} + + ); + } + + if (showViewSeriesLink === true && anomaly.isTimeSeriesViewRecord === true) { + items.push( + { + closePopover(); + viewSeries(); + }} + data-test-subj="mlAnomaliesListRowActionViewSeriesButton" + > + + + ); + } + + if (isCategorizationAnomalyRecord) { + items.push( + { + closePopover(); + viewExamples(); + }} + data-test-subj="mlAnomaliesListRowActionViewExamplesButton" + > + + + ); + } + + if (canConfigureRules) { + items.push( + { + closePopover(); + props.showRuleEditorFlyout(anomaly); + }} + data-test-subj="mlAnomaliesListRowActionConfigureRulesButton" + > + + + ); + } + return items; + }, [ + openInDiscoverUrl, + viewExamples, + viewSeries, + canConfigureRules, + isCategorizationAnomalyRecord, + ]); + + return ( + + ); +}; + +export const LinksMenu: FC> = (props) => { + const [isPopoverOpen, setPopoverOpen] = useState(false); + + const onButtonClick = setPopoverOpen.bind(null, !isPopoverOpen); + const closePopover = setPopoverOpen.bind(null, false); + const button = ( { iconType="gear" aria-label={i18n.translate('xpack.ml.anomaliesTable.linksMenu.selectActionAriaLabel', { defaultMessage: 'Select action for anomaly at {time}', - values: { time: formatHumanReadableDateTimeSeconds(anomaly.time) }, + values: { time: formatHumanReadableDateTimeSeconds(props.anomaly.time) }, })} data-test-subj="mlAnomaliesListRowActionsButton" /> ); - const items = []; - if (anomaly.customUrls !== undefined) { - anomaly.customUrls.forEach((customUrl, index) => { - items.push( - { - closePopover(); - openCustomUrl(customUrl); - }} - data-test-subj={`mlAnomaliesListRowActionCustomUrlButton_${index}`} - > - {customUrl.url_name} - - ); - }); - } - - if (openInDiscoverUrl) { - items.push( - - - - ); - } - - if (showViewSeriesLink === true && anomaly.isTimeSeriesViewRecord === true) { - items.push( - { - closePopover(); - viewSeries(); - }} - data-test-subj="mlAnomaliesListRowActionViewSeriesButton" - > - - - ); - } - - if (isCategorizationAnomaly(anomaly)) { - items.push( - { - closePopover(); - viewExamples(); - }} - data-test-subj="mlAnomaliesListRowActionViewExamplesButton" - > - - - ); - } - - if (canConfigureRules) { - items.push( - { - closePopover(); - props.showRuleEditorFlyout(anomaly); - }} - data-test-subj="mlAnomaliesListRowActionConfigureRulesButton" - > - - - ); - } - return ( - - - +
+ + + +
); }; - -export const LinksMenu = withKibana(LinksMenuUI); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts index 1c249ee1b5037..2b3a4a2dcbb50 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.test.ts @@ -5,9 +5,7 @@ * 2.0. */ -import { EuiDataGridSorting } from '@elastic/eui'; - -import { multiColumnSortFactory } from './common'; +import { MultiColumnSorter, multiColumnSortFactory } from './common'; describe('Data Frame Analytics: Data Grid Common', () => { test('multiColumnSortFactory()', () => { @@ -18,7 +16,7 @@ describe('Data Frame Analytics: Data Grid Common', () => { { s: 'b', n: 4 }, ]; - const sortingColumns1: EuiDataGridSorting['columns'] = [{ id: 's', direction: 'desc' }]; + const sortingColumns1: MultiColumnSorter[] = [{ id: 's', direction: 'desc', type: 'number' }]; const multiColumnSort1 = multiColumnSortFactory(sortingColumns1); data.sort(multiColumnSort1); @@ -29,9 +27,9 @@ describe('Data Frame Analytics: Data Grid Common', () => { { s: 'a', n: 2 }, ]); - const sortingColumns2: EuiDataGridSorting['columns'] = [ - { id: 's', direction: 'asc' }, - { id: 'n', direction: 'desc' }, + const sortingColumns2: MultiColumnSorter[] = [ + { id: 's', direction: 'asc', type: 'number' }, + { id: 'n', direction: 'desc', type: 'number' }, ]; const multiColumnSort2 = multiColumnSortFactory(sortingColumns2); data.sort(multiColumnSort2); @@ -43,9 +41,9 @@ describe('Data Frame Analytics: Data Grid Common', () => { { s: 'b', n: 3 }, ]); - const sortingColumns3: EuiDataGridSorting['columns'] = [ - { id: 'n', direction: 'desc' }, - { id: 's', direction: 'desc' }, + const sortingColumns3: MultiColumnSorter[] = [ + { id: 'n', direction: 'desc', type: 'number' }, + { id: 's', direction: 'desc', type: 'number' }, ]; const multiColumnSort3 = multiColumnSortFactory(sortingColumns3); data.sort(multiColumnSort3); diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index d49442b9864d4..31979000f4a60 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -9,11 +9,7 @@ import moment from 'moment-timezone'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { useEffect, useMemo } from 'react'; -import { - EuiDataGridCellValueElementProps, - EuiDataGridSorting, - EuiDataGridStyle, -} from '@elastic/eui'; +import { EuiDataGridCellValueElementProps, EuiDataGridStyle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -178,7 +174,7 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results export const NON_AGGREGATABLE = 'non-aggregatable'; export const getDataGridSchemaFromESFieldType = ( - fieldType: ES_FIELD_TYPES | undefined | estypes.MappingRuntimeField['type'] + fieldType: ES_FIELD_TYPES | undefined | estypes.MappingRuntimeField['type'] | 'number' ): string | undefined => { // Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json'] // To fall back to the default string schema it needs to be undefined. @@ -204,6 +200,7 @@ export const getDataGridSchemaFromESFieldType = ( case ES_FIELD_TYPES.LONG: case ES_FIELD_TYPES.SCALED_FLOAT: case ES_FIELD_TYPES.SHORT: + case 'number': schema = 'numeric'; break; // keep schema undefined for text based columns @@ -417,6 +414,16 @@ export const useRenderCellValue = ( return renderCellValue; }; +// Value can be nested or the fieldName itself might contain other special characters like `.` +export const getNestedOrEscapedVal = (obj: any, sortId: string) => + getNestedProperty(obj, sortId, null) ?? obj[sortId]; + +export interface MultiColumnSorter { + id: string; + direction: 'asc' | 'desc'; + type: string; +} + /** * Helper to sort an array of objects based on an EuiDataGrid sorting configuration. * `sortFn()` is recursive to support sorting on multiple columns. @@ -424,17 +431,17 @@ export const useRenderCellValue = ( * @param sortingColumns - The EUI data grid sorting configuration * @returns The sorting function which can be used with an array's sort() function. */ -export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['columns']) => { - const isString = (arg: any): arg is string => { - return typeof arg === 'string'; - }; - +export const multiColumnSortFactory = (sortingColumns: MultiColumnSorter[]) => { const sortFn = (a: any, b: any, sortingColumnIndex = 0): number => { const sort = sortingColumns[sortingColumnIndex]; - const aValue = getNestedProperty(a, sort.id, null); - const bValue = getNestedProperty(b, sort.id, null); - if (typeof aValue === 'number' && typeof bValue === 'number') { + // Value can be nested or the fieldName itself might contain `.` + let aValue = getNestedOrEscapedVal(a, sort.id); + let bValue = getNestedOrEscapedVal(b, sort.id); + + if (sort.type === 'number') { + aValue = aValue ?? 0; + bValue = bValue ?? 0; if (aValue < bValue) { return sort.direction === 'asc' ? -1 : 1; } @@ -443,7 +450,10 @@ export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['colum } } - if (isString(aValue) && isString(bValue)) { + if (sort.type === 'string') { + aValue = aValue ?? ''; + bValue = bValue ?? ''; + if (aValue.localeCompare(bValue) === -1) { return sort.direction === 'asc' ? -1 : 1; } diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts index b2bd1ff228923..8b09617aa817e 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts @@ -12,6 +12,7 @@ export { getFieldsFromKibanaIndexPattern, getCombinedRuntimeMappings, multiColumnSortFactory, + getNestedOrEscapedVal, showDataGridColumnChartErrorMessageToast, useRenderCellValue, getProcessedFields, diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx index 633c3d9aab002..55c12338b8fd4 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx +++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx @@ -87,15 +87,15 @@ export const useDataGrid = ( const onSort: OnSort = useCallback( (sc) => { // Check if an unsupported column type for sorting was selected. - const updatedInvalidSortingColumnns = sc.reduce((arr, current) => { + const updatedInvalidSortingColumns = sc.reduce((arr, current) => { const columnType = columns.find((dgc) => dgc.id === current.id); if (columnType?.schema === 'json') { arr.push(current.id); } return arr; }, []); - setInvalidSortingColumnns(updatedInvalidSortingColumnns); - if (updatedInvalidSortingColumnns.length === 0) { + setInvalidSortingColumnns(updatedInvalidSortingColumns); + if (updatedInvalidSortingColumns.length === 0) { setSortingColumns(sc); } }, diff --git a/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.test.tsx b/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.test.tsx index efe405044f45e..d726356624b35 100644 --- a/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.test.tsx +++ b/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EntityCell } from './entity_cell'; const defaultProps = { diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index 3f64ff794d9ab..ea444193db0b1 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { FullTimeRangeSelector } from './index'; import type { Query } from 'src/plugins/data/public'; import type { DataView } from '../../../../../../../src/plugins/data_views/public'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/actions_section.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/actions_section.test.js index 149cdf9bb9aa0..37315b61c2d02 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/actions_section.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/actions_section.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ActionsSection } from './actions_section'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/components/detector_description_list/detector_description_list.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/components/detector_description_list/detector_description_list.test.js index f4752f5407370..1c979525ca9eb 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/components/detector_description_list/detector_description_list.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/components/detector_description_list/detector_description_list.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { DetectorDescriptionList } from './detector_description_list'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.test.js index f13b2d010a370..2fbf69c67f26a 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/condition_expression.test.js @@ -8,7 +8,7 @@ // Mock the mlJobService that is imported for saving rules. jest.mock('../../services/job_service.js', () => 'mlJobService'); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ConditionExpression } from './condition_expression'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/conditions_section.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/conditions_section.test.js index eaa352b33253d..69a71dda38cb4 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/conditions_section.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/conditions_section.test.js @@ -8,7 +8,7 @@ // Mock the mlJobService that is imported for saving rules. jest.mock('../../services/job_service.js', () => 'mlJobService'); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ConditionsSection } from './conditions_section'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js index 20a66bcf5b140..2553baa11da33 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/rule_editor_flyout.test.js @@ -56,7 +56,7 @@ jest.mock('../../../../../../../src/plugins/kibana_react/public', () => ({ }, })); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { RuleEditorFlyout } from './rule_editor_flyout'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.test.js index 59e5000dee7fd..c030dcfa1f0a4 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/scope_expression.test.js @@ -8,7 +8,7 @@ // Mock the mlJobService that is imported for saving rules. jest.mock('../../services/job_service.js', () => 'mlJobService'); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ScopeExpression } from './scope_expression'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js index 5a9c6bb3af4cd..86a777f65a338 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/scope_section.test.js @@ -16,7 +16,7 @@ jest.mock('../../capabilities/check_capabilities', () => ({ checkPermission: (privilege) => mockCheckPermission(privilege), })); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ScopeSection } from './scope_section'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/add_to_filter_list_link.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/add_to_filter_list_link.test.js index 802cd51575c78..2c255728f2eb3 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/add_to_filter_list_link.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/add_to_filter_list_link.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { AddToFilterListLink } from './add_to_filter_list_link'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.test.js index cf06149b728b0..457a552bad6c9 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/delete_rule_modal.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { DeleteRuleModal } from './delete_rule_modal'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.test.js index 80b77e41a6964..9ae947c404c2e 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/edit_condition_link.test.js @@ -7,7 +7,7 @@ jest.mock('../../../services/job_service.js', () => 'mlJobService'); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EditConditionLink } from './edit_condition_link'; diff --git a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.test.js b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.test.js index d4d93506a4d9d..7df277ae0b6ab 100644 --- a/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.test.js +++ b/x-pack/plugins/ml/public/application/components/rule_editor/select_rule_action/rule_action_panel.test.js @@ -29,7 +29,7 @@ jest.mock('../../../services/ml_api_service', () => ({ }, })); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { RuleActionPanel } from './rule_action_panel'; diff --git a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js index 8ec83d8679e87..b1755660960ad 100644 --- a/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js +++ b/x-pack/plugins/ml/public/application/components/validate_job/validate_job_view.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ValidateJob } from './validate_job_view'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/__mocks__/analytics_list_item.json b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/__mocks__/analytics_list_item.json index 1b7d353d9f303..cf56dedf664ee 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/__mocks__/analytics_list_item.json +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/__mocks__/analytics_list_item.json @@ -22,7 +22,7 @@ }, "model_memory_limit": "50mb", "create_time": 1568974998023, - "version": "8.1.0" + "version": "8.2.0" }, "id": "fq_outlier_1222", "checkpointing": {}, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx index f848aaf716641..36cc01d4462b9 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountHook } from '@kbn/test/jest'; +import { mountHook } from '@kbn/test-jest-helpers'; import { MlContext } from '../../../../../contexts/ml'; import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js index 8f089ab8c07ce..8e39120e36411 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.test.js @@ -13,7 +13,7 @@ jest.mock('../../services/field_format_service', () => ({ }, })); -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ExplorerChartDistribution } from './explorer_chart_distribution'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.test.js index ef0482806f423..c2149097c8733 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_info_tooltip.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ExplorerChartInfoTooltip } from './explorer_chart_info_tooltip'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js index 133ca3e8eb628..2582dcfb05c16 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.test.js @@ -13,7 +13,7 @@ jest.mock('../../services/field_format_service', () => ({ }, })); -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ExplorerChartSingleMetric } from './explorer_chart_single_metric'; diff --git a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js index 623e9a69cca93..f4df795a527fa 100644 --- a/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/components/custom_url_editor/utils.js @@ -10,14 +10,12 @@ import { TIME_RANGE_TYPE, URL_TYPE } from './constants'; import rison from 'rison-node'; import url from 'url'; -import { DASHBOARD_APP_URL_GENERATOR } from '../../../../../../../../src/plugins/dashboard/public'; - import { getPartitioningFieldNames } from '../../../../../common/util/job_utils'; import { parseInterval } from '../../../../../common/util/parse_interval'; import { replaceTokensInUrlValue, isValidLabel } from '../../../util/custom_url_utils'; import { ml } from '../../../services/ml_api_service'; import { escapeForElasticsearchQuery } from '../../../util/string_utils'; -import { getSavedObjectsClient, getGetUrlGenerator } from '../../../util/dependency_cache'; +import { getSavedObjectsClient, getDashboard } from '../../../util/dependency_cache'; export function getNewCustomUrlDefaults(job, dashboards, dataViews) { // Returns the settings object in the format used by the custom URL editor @@ -152,10 +150,10 @@ function buildDashboardUrlFromSettings(settings) { query = queryFromEntityFieldNames; } - const getUrlGenerator = getGetUrlGenerator(); - const generator = getUrlGenerator(DASHBOARD_APP_URL_GENERATOR); - return generator - .createUrl({ + const dashboard = getDashboard(); + + dashboard.locator + .getUrl({ dashboardId, timeRange: { from: '$earliest$', diff --git a/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx index 21d30ef76c458..a8f92e8ae9f71 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/trained_models/models_list.tsx @@ -62,7 +62,7 @@ const PageWrapper: FC = ({ location, deps }) => { = ({ location, deps }) => { 'xpack.ml.navMenu.trainedModelsTabBetaTooltipContent', { defaultMessage: - "Model Management is an experimental feature and subject to change. We'd love to hear your feedback.", + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', } )} tooltipPosition={'right'} diff --git a/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx index 88df1e0b07f58..33791f1e2aa81 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/trained_models/nodes_list.tsx @@ -60,7 +60,7 @@ const PageWrapper: FC = ({ location, deps }) => { = ({ location, deps }) => { 'xpack.ml.navMenu.trainedModelsTabBetaTooltipContent', { defaultMessage: - "Model Management is an experimental feature and subject to change. We'd love to hear your feedback.", + 'This functionality is in technical preview and may be changed or removed completely in a future release. Elastic will take a best effort approach to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.', } )} tooltipPosition={'right'} diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.test.js index 302dc63da7e52..e5c66bcdd59bd 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/calendar_form/calendar_form.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { CalendarForm } from './calendar_form'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.test.js index a2341edf38f58..d45d93043e16b 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/events_table/events_table.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EventsTable } from './events_table'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/import_modal.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/import_modal.test.js index 10bc1c7a96601..bcbbdec7b65c6 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/import_modal.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/import_modal/import_modal.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ImportModal } from './import_modal'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/imported_events.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/imported_events.test.js index 5604ef4046240..8762cb4603807 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/imported_events.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/imported_events/imported_events.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ImportedEvents } from './imported_events'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js index 0107af3cf8c1e..73391f3cba87f 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_calendar.test.js @@ -67,7 +67,7 @@ jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ }, })); -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { NewCalendar } from './new_calendar'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js index f33879bfa872f..0aa91a44458e7 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/edit/new_event_modal/new_event_modal.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { NewEventModal } from './new_event_modal'; import moment from 'moment'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js index 8e66dbd274273..ee1879a8a3c7a 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/calendars_list.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ml } from '../../../services/ml_api_service'; import { CalendarsList } from './calendars_list'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js index edbee43151f21..da470441caa98 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/header.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { CalendarsListHeader } from './header'; diff --git a/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.test.js b/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.test.js index 543ae2b85abfc..13e2c1e2dc37b 100644 --- a/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.test.js +++ b/x-pack/plugins/ml/public/application/settings/calendars/list/table/table.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { CalendarsListTable } from './table'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.test.js index b4d32c387d183..0bbb3f443f1ae 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/add_item_popover/add_item_popover.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { AddItemPopover } from './add_item_popover'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js index 65fc96a62a9cb..d46de282c59d9 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/delete_filter_list_modal/delete_filter_list_modal.test.js @@ -14,7 +14,7 @@ jest.mock('../../../../capabilities/check_capabilities', () => ({ })); jest.mock('../../../../services/ml_api_service', () => 'ml'); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { DeleteFilterListModal } from './delete_filter_list_modal'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js index 6082a2bbf6799..ed6ae3764fd2a 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/components/edit_description_popover/edit_description_popover.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EditDescriptionPopover } from './edit_description_popover'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js index 09c37c724b7c9..c20b8485b7cd1 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/edit_filter_list.test.js @@ -51,7 +51,7 @@ jest.mock('../../../../../../../../src/plugins/kibana_react/public', () => ({ }, })); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EditFilterList } from './edit_filter_list'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.test.js index dc756fb4686bb..039acc0bef1eb 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/header.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EditFilterListHeader } from './header'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/toolbar.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/toolbar.test.js index 83226ad236a6d..7935b7dce3185 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/edit/toolbar.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/edit/toolbar.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EditFilterListToolbar } from './toolbar'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js index 0ae4a758278f1..e6a69e971a864 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/filter_lists.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { FilterLists } from './filter_lists'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/header.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/header.test.js index 204cb6036afd3..4357dad093dad 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/header.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/header.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { FilterListsHeader } from './header'; diff --git a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.test.js b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.test.js index 164d4d7eecbd4..add92b804ee39 100644 --- a/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.test.js +++ b/x-pack/plugins/ml/public/application/settings/filter_lists/list/table.test.js @@ -12,7 +12,7 @@ jest.mock('../../../capabilities/check_capabilities', () => ({ })); jest.mock('../../../services/ml_api_service', () => 'ml'); -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { FilterListsTable } from './table'; diff --git a/x-pack/plugins/ml/public/application/settings/settings.test.tsx b/x-pack/plugins/ml/public/application/settings/settings.test.tsx index a12e733631830..ad3847e011371 100644 --- a/x-pack/plugins/ml/public/application/settings/settings.test.tsx +++ b/x-pack/plugins/ml/public/application/settings/settings.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { AnomalyDetectionSettingsContext } from './anomaly_detection_settings_context'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js index 4faae22f98153..850a585509003 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js @@ -8,7 +8,7 @@ //import mockOverallSwimlaneData from './__mocks__/mock_overall_swimlane.json'; import moment from 'moment-timezone'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { TimeseriesChart } from './timeseries_chart'; diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index c928b554c05ad..bba71a23f98c3 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -21,7 +21,7 @@ import type { IBasePath, } from 'kibana/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; -import type { SharePluginStart } from 'src/plugins/share/public'; +import type { DashboardStart } from 'src/plugins/dashboard/public'; import type { FieldFormatsStart } from 'src/plugins/field_formats/public'; import type { DataViewsContract } from '../../../../../../src/plugins/data_views/public'; import type { SecurityPluginSetup } from '../../../../security/public'; @@ -45,7 +45,7 @@ export interface DependencyCache { http: HttpStart | null; security: SecurityPluginSetup | undefined | null; i18n: I18nStart | null; - urlGenerators: SharePluginStart['urlGenerators'] | null; + dashboard: DashboardStart | null; maps: MapsStartApi | null; dataVisualizer: DataVisualizerPluginStart | null; dataViews: DataViewsContract | null; @@ -68,7 +68,7 @@ const cache: DependencyCache = { http: null, security: null, i18n: null, - urlGenerators: null, + dashboard: null, maps: null, dataVisualizer: null, dataViews: null, @@ -91,7 +91,7 @@ export function setDependencyCache(deps: Partial) { cache.http = deps.http || null; cache.security = deps.security || null; cache.i18n = deps.i18n || null; - cache.urlGenerators = deps.urlGenerators || null; + cache.dashboard = deps.dashboard || null; cache.dataVisualizer = deps.dataVisualizer || null; cache.dataViews = deps.dataViews || null; } @@ -214,11 +214,11 @@ export function getI18n() { return cache.i18n; } -export function getGetUrlGenerator() { - if (cache.urlGenerators === null) { - throw new Error("urlGenerators hasn't been initialized"); +export function getDashboard() { + if (cache.dashboard === null) { + throw new Error("dashboard hasn't been initialized"); } - return cache.urlGenerators.getUrlGenerator; + return cache.dashboard; } export function getDataViews() { diff --git a/x-pack/plugins/ml/public/maps/anomaly_source.tsx b/x-pack/plugins/ml/public/maps/anomaly_source.tsx index fa3fce1747a2e..e2d92a730d95a 100644 --- a/x-pack/plugins/ml/public/maps/anomaly_source.tsx +++ b/x-pack/plugins/ml/public/maps/anomaly_source.tsx @@ -15,7 +15,7 @@ import { VectorSourceRequestMeta, } from '../../../maps/common'; import { AbstractSourceDescriptor, MapExtent } from '../../../maps/common/descriptor_types'; -import { ITooltipProperty } from '../../../maps/public'; +import { ITooltipProperty, GEOJSON_FEATURE_ID_PROPERTY_NAME } from '../../../maps/public'; import { AnomalySourceField, AnomalySourceTooltipProperty, @@ -247,6 +247,9 @@ export class AnomalySource implements IVectorSource { async getTooltipProperties(properties: { [p: string]: any } | null): Promise { const tooltipProperties: ITooltipProperty[] = []; for (const key in properties) { + if (key === GEOJSON_FEATURE_ID_PROPERTY_NAME) { + continue; + } if (properties.hasOwnProperty(key)) { const label = ANOMALY_SOURCE_FIELDS[key]?.label; if (label) { diff --git a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx index a95eedb2b4cb1..ff58887c88c12 100644 --- a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx @@ -10,7 +10,7 @@ */ import React, { Fragment, lazy } from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ReactWrapper, mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { coreMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx index 1a4d88d690b84..1f133176a743e 100644 --- a/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx +++ b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/use_derived_index_pattern.tsx @@ -5,15 +5,16 @@ * 2.0. */ +import { DataViewFieldBase } from '@kbn/es-query'; import { useEffect, useState } from 'react'; -import { DataPublicPluginStart, IFieldType, IIndexPattern } from 'src/plugins/data/public'; +import { DataPublicPluginStart, IIndexPattern } from 'src/plugins/data/public'; +import { prefixIndexPattern } from '../../../../common/ccs_utils'; import { INDEX_PATTERN_BEATS, INDEX_PATTERN_ELASTICSEARCH, INDEX_PATTERN_KIBANA, INDEX_PATTERN_LOGSTASH, } from '../../../../common/constants'; -import { prefixIndexPattern } from '../../../../common/ccs_utils'; import { MonitoringConfig } from '../../../types'; const INDEX_PATTERNS = `${INDEX_PATTERN_ELASTICSEARCH},${INDEX_PATTERN_KIBANA},${INDEX_PATTERN_LOGSTASH},${INDEX_PATTERN_BEATS}`; @@ -24,7 +25,7 @@ export const useDerivedIndexPattern = ( ): { loading: boolean; derivedIndexPattern: IIndexPattern } => { const indexPattern = prefixIndexPattern(config || ({} as MonitoringConfig), INDEX_PATTERNS, '*'); const [loading, setLoading] = useState(true); - const [fields, setFields] = useState([]); + const [fields, setFields] = useState([]); useEffect(() => { (async function fetchData() { const result = await data.indexPatterns.getFieldsForWildcard({ diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index c951d325c13f4..37ac69a56e40d 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -20,8 +20,9 @@ import { } from '../../lib/setup_mode'; import { SetupModeFeature } from '../../../common/enums'; import { AlertsDropdown } from '../../alerts/alerts_dropdown'; -import { ActionMenu } from '../../components/action_menu'; import { useRequestErrorHandler } from '../hooks/use_request_error_handler'; +import { HeaderMenuPortal } from '../../../../observability/public'; +import { HeaderActionMenuContext } from '../../application/contexts/header_action_menu_context'; export interface TabMenuItem { id: string; @@ -53,6 +54,7 @@ export const PageTemplate: React.FC = ({ const history = useHistory(); const [hasError, setHasError] = useState(false); const handleRequestError = useRequestErrorHandler(); + const { setHeaderActionMenu, theme$ } = useContext(HeaderActionMenuContext); const getPageDataResponseHandler = useCallback( (result: any) => { @@ -104,9 +106,11 @@ export const PageTemplate: React.FC = ({ return (
- - - + {setHeaderActionMenu && theme$ && ( + + + + )} {tabs && ( diff --git a/x-pack/plugins/monitoring/public/components/action_menu/index.tsx b/x-pack/plugins/monitoring/public/components/action_menu/index.tsx deleted file mode 100644 index c1de80ccaf242..0000000000000 --- a/x-pack/plugins/monitoring/public/components/action_menu/index.tsx +++ /dev/null @@ -1,35 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useContext, useEffect } from 'react'; -import { - KibanaContextProvider, - toMountPoint, - useKibana, -} from '../../../../../../src/plugins/kibana_react/public'; -import { HeaderActionMenuContext } from '../../application/contexts/header_action_menu_context'; - -export const ActionMenu: React.FC<{}> = ({ children }) => { - const { services } = useKibana(); - const { setHeaderActionMenu } = useContext(HeaderActionMenuContext); - useEffect(() => { - if (setHeaderActionMenu) { - setHeaderActionMenu((element) => { - const mount = toMountPoint( - {children}, - { theme$: services.theme?.theme$ } - ); - return mount(element); - }); - return () => { - setHeaderActionMenu(undefined); - }; - } - }, [children, setHeaderActionMenu, services]); - - return null; -}; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.test.js b/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.test.js index 47cc1bef53caa..942065263912d 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.test.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/helpers.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { BytesUsage, BytesPercentageUsage } from './helpers'; describe('Bytes Usage', () => { diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__snapshots__/cells.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__snapshots__/cells.test.js.snap index ce94dd2a61a80..5535520c67a8e 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__snapshots__/cells.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/__snapshots__/cells.test.js.snap @@ -2,7 +2,7 @@ exports[`Node Listing Metric Cell should format N/A as the metric for an offline node 1`] = `
N/A
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js index 528b3bed3df7b..1b6121f278496 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.js @@ -27,7 +27,7 @@ const TRENDING_UP = i18n.translate('xpack.monitoring.elasticsearch.node.cells.tr }); function OfflineCell() { - return
N/A
; + return
N/A
; } const getDirection = (slope) => { @@ -56,88 +56,80 @@ function MetricCell({ isOnline, metric = {}, isPercent, ...props }) { const onButtonClick = () => setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); const closePopover = () => setIsPopoverOpen(false); - if (isOnline) { - const { lastVal, maxVal, minVal, slope } = get(metric, 'summary', {}); - const format = get(metric, 'metric.format'); - const units = get(metric, 'metric.units'); + if (!isOnline) { + return ; + } - const tooltipItems = [ - { - title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.trending', { - defaultMessage: 'Trending', - }), - description: getDirection(slope), - }, - { - title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.max', { - defaultMessage: 'Max value', - }), - description: metricVal(maxVal, format, isPercent, units), - }, - { - title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.min', { - defaultMessage: 'Min value', - }), - description: metricVal(minVal, format, isPercent, units), - }, - ]; + const { lastVal, maxVal, minVal, slope } = get(metric, 'summary', {}); + const format = get(metric, 'metric.format'); + const units = get(metric, 'metric.units'); - const iconLabel = i18n.translate( - 'xpack.monitoring.elasticsearch.node.cells.tooltip.iconLabel', - { - defaultMessage: 'More information about this metric', - } - ); + const tooltipItems = [ + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.trending', { + defaultMessage: 'Trending', + }), + description: getDirection(slope), + }, + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.max', { + defaultMessage: 'Max value', + }), + description: metricVal(maxVal, format, isPercent, units), + }, + { + title: i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.min', { + defaultMessage: 'Min value', + }), + description: metricVal(minVal, format, isPercent, units), + }, + ]; - const button = ( - - ); + const iconLabel = i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.iconLabel', { + defaultMessage: 'More information about this metric', + }); - return ( - - - - - - -
- - - - {i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.preface', { - defaultMessage: 'Applies to current time period', - })} - -
-
-
- - {metricVal(lastVal, format, isPercent)} - -
-
-
- ); - } + const button = ( + + ); - return ; + return ( + + + + + + +
+ + + + {i18n.translate('xpack.monitoring.elasticsearch.node.cells.tooltip.preface', { + defaultMessage: 'Applies to current time period', + })} + +
+
+
+ + {metricVal(lastVal, format, isPercent)} + +
+
+
+ ); } export { OfflineCell, MetricCell }; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.test.js b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.test.js index 250b9f6391221..f2ef0484928fc 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.test.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/nodes/cells.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { MetricCell } from './cells'; describe('Node Listing Metric Cell', () => { diff --git a/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx b/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx index ca0a8122772f3..6d79266fe3cc6 100644 --- a/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx +++ b/x-pack/plugins/monitoring/public/components/kuery_bar/index.tsx @@ -5,12 +5,12 @@ * 2.0. */ +import { fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; - import React, { useEffect, useState } from 'react'; -import { WithKueryAutocompletion } from './with_kuery_autocompletion'; +import { IIndexPattern, QuerySuggestion } from '../../../../../../src/plugins/data/public'; import { AutocompleteField } from './autocomplete_field'; -import { esKuery, IIndexPattern, QuerySuggestion } from '../../../../../../src/plugins/data/public'; +import { WithKueryAutocompletion } from './with_kuery_autocompletion'; type LoadSuggestionsFn = ( e: string, @@ -31,7 +31,7 @@ interface Props { function validateQuery(query: string) { try { - esKuery.fromKueryExpression(query); + fromKueryExpression(query); } catch (err) { return false; } diff --git a/x-pack/plugins/monitoring/public/components/no_data/checker_errors.test.js b/x-pack/plugins/monitoring/public/components/no_data/checker_errors.test.js index d293494fcd4c4..1c33f68458fd0 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/checker_errors.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/checker_errors.test.js @@ -7,7 +7,7 @@ import React from 'react'; import { boomify, forbidden } from '@hapi/boom'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { CheckerErrors } from './checker_errors'; describe('CheckerErrors', () => { diff --git a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/collection_enabled.test.js b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/collection_enabled.test.js index b66793d840ebb..95dc62abdf9d2 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/collection_enabled.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_enabled/collection_enabled.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExplainCollectionEnabled } from './collection_enabled'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/collection_interval.test.js b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/collection_interval.test.js index a5af179a050b7..95ffad81b902d 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/collection_interval.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/explanations/collection_interval/collection_interval.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExplainCollectionInterval } from './collection_interval'; import { findTestSubject } from '@elastic/eui/lib/test'; diff --git a/x-pack/plugins/monitoring/public/components/no_data/explanations/exporters/exporters.test.js b/x-pack/plugins/monitoring/public/components/no_data/explanations/exporters/exporters.test.js index 9958c6671d30b..47dbf5f1ab586 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/explanations/exporters/exporters.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/explanations/exporters/exporters.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { ExplainExporters, ExplainExportersCloud } from './exporters'; jest.mock('../../../../legacy_shims', () => ({ diff --git a/x-pack/plugins/monitoring/public/components/no_data/explanations/plugin_enabled/plugin_enabled.test.js b/x-pack/plugins/monitoring/public/components/no_data/explanations/plugin_enabled/plugin_enabled.test.js index c28c7563e5974..dd57cd3bd1c50 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/explanations/plugin_enabled/plugin_enabled.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/explanations/plugin_enabled/plugin_enabled.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { ExplainPluginEnabled } from './plugin_enabled'; describe('ExplainPluginEnabled', () => { diff --git a/x-pack/plugins/monitoring/public/components/no_data/no_data.test.js b/x-pack/plugins/monitoring/public/components/no_data/no_data.test.js index 17ee5148e5d11..a522d01037603 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/no_data.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/no_data.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { NoData } from '.'; const enabler = {}; diff --git a/x-pack/plugins/monitoring/public/components/no_data/reasons/reason_found.test.js b/x-pack/plugins/monitoring/public/components/no_data/reasons/reason_found.test.js index 6cec326109308..96b0ceb4cb83d 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/reasons/reason_found.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/reasons/reason_found.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { ReasonFound } from '.'; jest.mock('../../../legacy_shims', () => ({ diff --git a/x-pack/plugins/monitoring/public/components/no_data/reasons/we_tried.test.js b/x-pack/plugins/monitoring/public/components/no_data/reasons/we_tried.test.js index 186676776021e..5fd3947c3bb1c 100644 --- a/x-pack/plugins/monitoring/public/components/no_data/reasons/we_tried.test.js +++ b/x-pack/plugins/monitoring/public/components/no_data/reasons/we_tried.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { WeTried } from '.'; describe('WeTried', () => { diff --git a/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js b/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js index f7973231f41b0..07e3e89dce9a1 100644 --- a/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js +++ b/x-pack/plugins/monitoring/public/components/page_loading/page_loading.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { PageLoading } from '.'; describe('PageLoading', () => { diff --git a/x-pack/plugins/monitoring/public/components/summary_status/summary_status.test.js b/x-pack/plugins/monitoring/public/components/summary_status/summary_status.test.js index 1428418ad3705..dcb4821e1819a 100644 --- a/x-pack/plugins/monitoring/public/components/summary_status/summary_status.test.js +++ b/x-pack/plugins/monitoring/public/components/summary_status/summary_status.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { SummaryStatus } from './summary_status'; describe('Summary Status Component', () => { diff --git a/x-pack/plugins/monitoring/public/lib/kuery.ts b/x-pack/plugins/monitoring/public/lib/kuery.ts index 19706d7664c22..67f58360bc27a 100644 --- a/x-pack/plugins/monitoring/public/lib/kuery.ts +++ b/x-pack/plugins/monitoring/public/lib/kuery.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { esKuery, IIndexPattern } from '../../../../../src/plugins/data/public'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { IIndexPattern } from '../../../../../src/plugins/data/public'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, @@ -13,9 +14,7 @@ export const convertKueryToElasticSearchQuery = ( ) => { try { return kueryExpression - ? JSON.stringify( - esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kueryExpression), indexPattern) - ) + ? JSON.stringify(toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern)) : ''; } catch (err) { return ''; diff --git a/x-pack/plugins/monitoring/server/lib/details/get_series.ts b/x-pack/plugins/monitoring/server/lib/details/get_series.ts index 3a053b16aad7c..58e1aac0884b6 100644 --- a/x-pack/plugins/monitoring/server/lib/details/get_series.ts +++ b/x-pack/plugins/monitoring/server/lib/details/get_series.ts @@ -22,28 +22,10 @@ import { import { formatUTCTimestampForTimezone } from '../format_timezone'; import { getNewIndexPatterns } from '../cluster/get_index_patterns'; import { Globals } from '../../static_globals'; +import type { Metric } from '../metrics/metrics'; type SeriesBucket = Bucket & { metric_mb_deriv?: { normalized_value: number } }; -interface Metric { - app: string; - derivative: boolean; - mbField?: string; - aggs: any; - getDateHistogramSubAggs?: Function; - dateHistogramSubAggs?: any; - metricAgg: string; - field: string; - timestampField: string; - calculation: ( - b: SeriesBucket, - key: string, - metric: Metric, - defaultSizeInSeconds: number - ) => number | null; - serialize: () => string; -} - /** * Derivative metrics for the first two agg buckets are unusable. For the first bucket, there * simply is no derivative metric (as calculating a derivative requires two adjacent buckets). For @@ -72,7 +54,12 @@ function getUuid(req: LegacyRequest, metric: Metric) { } } -function defaultCalculation(bucket: SeriesBucket, key: string) { +function defaultCalculation( + bucket: SeriesBucket, + key: string, + metric?: Metric, + defaultSizeInSeconds?: number +) { const legacyValue: number = get(bucket, key, null); const mbValue = bucket.metric_mb_deriv?.normalized_value ?? null; let value; @@ -142,7 +129,7 @@ async function fetchSeries( } else { dateHistogramSubAggs = { metric: { - [metric.metricAgg]: { + [metric.metricAgg!]: { field: metric.field, }, }, @@ -150,7 +137,7 @@ async function fetchSeries( }; if (metric.mbField) { Reflect.set(dateHistogramSubAggs, 'metric_mb', { - [metric.metricAgg]: { + [metric.metricAgg!]: { field: metric.mbField, }, }); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.ts index ea2940ee1589c..4ed6e8970edad 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_metric_aggs.ts @@ -33,7 +33,7 @@ export function getMetricAggs(listingMetrics: string[]) { // if metric does not have custom agg defined metricAgg = { metric: { - [metric.metricAgg]: { + [metric.metricAgg!]: { // max, sum, etc field: metric.field, }, @@ -49,7 +49,7 @@ export function getMetricAggs(listingMetrics: string[]) { aggItems = { ...aggItems, - ...convertMetricNames(metricName, metric.aggs || metricAgg), + ...convertMetricNames(metricName, metric.aggs ? metric.aggs : metricAgg!), }; }); diff --git a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.ts b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.ts index 099b32dc1e65d..8313689b38fec 100644 --- a/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.ts +++ b/x-pack/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_metrics.ts @@ -8,6 +8,7 @@ import { get, map, min, max, last } from 'lodash'; import { filterPartialBuckets } from '../../../filter_partial_buckets'; import { metrics } from '../../../metrics'; +import { Metric } from '../../../metrics/metrics'; import { Bucket } from '../../../../types'; type MetricBucket = Bucket & { metric_deriv?: { value: number; normalized_value: number } }; @@ -35,10 +36,7 @@ function calcSlope(data: Array<{ x: number; y: number }>) { return null; // convert possible NaN to `null` for JSON-friendliness } -const mapBuckets = ( - bucket: MetricBucket, - metric: { derivative: boolean; calculation: (b: Bucket) => number | null } -) => { +const mapBuckets = (bucket: MetricBucket, metric: Metric) => { const x = bucket.key; if (metric.calculation) { diff --git a/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap b/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap index edb76085e6834..50fff3734b1a5 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap +++ b/x-pack/plugins/monitoring/server/lib/metrics/__snapshots__/metrics.test.js.snap @@ -32,15 +32,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests received by agent configuration managemen", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.request.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Requests Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_count": ApmEventsRateClusterMetric { @@ -73,15 +82,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP requests responded to by APM Server", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Count Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_errors_count": ApmEventsRateClusterMetric { @@ -114,15 +132,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP errors count", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.errors.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Error Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Error Count Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_errors_forbidden": ApmEventsRateClusterMetric { @@ -155,15 +182,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Forbidden HTTP requests rejected count", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.errors.forbidden", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Errors Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_errors_invalidquery": ApmEventsRateClusterMetric { @@ -196,15 +232,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Invalid HTTP query", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.errors.invalidquery", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Invalid Query", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Invalid Query Errors Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_errors_method": ApmEventsRateClusterMetric { @@ -237,15 +282,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP requests rejected due to incorrect HTTP method", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.errors.method", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Method", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Method Errors Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_errors_unauthorized": ApmEventsRateClusterMetric { @@ -278,15 +332,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Unauthorized HTTP requests rejected count", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.errors.unauthorized", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Unauthorized", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Unauthorized Errors Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_errors_unavailable": ApmEventsRateClusterMetric { @@ -319,15 +382,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Unavailable HTTP response count. Possible misconfiguration or unsupported version of Kibana", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.errors.unavailable", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Unavailable", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Unavailable Errors Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_valid_notmodified": ApmEventsRateClusterMetric { @@ -360,15 +432,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "304 Not modified response count", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.valid.notmodified", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Not Modified", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Not Modified Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_acm_response_valid_ok": ApmEventsRateClusterMetric { @@ -401,15 +482,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "200 OK response count", + "docType": undefined, "field": "beats_stats.metrics.apm-server.acm.response.valid.ok", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "OK", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response OK Count Agent Configuration Management", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_cgroup_cpu": QuotaMetric { @@ -446,12 +536,16 @@ Object { }, "app": "apm", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", + "docType": undefined, "field": "beats_stats.metrics.beat.cpu.total.value", "fieldSource": "beats_stats.metrics.beat.cgroup", "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup CPU Utilization", + "mbField": undefined, "metricAgg": "max", "periodsField": "cpu.stats.periods", "quotaField": "cpu.cfs.quota.us", @@ -462,82 +556,141 @@ Object { "uuidField": "beats_stats.beat.uuid", }, "apm_cgroup_memory_limit": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Memory limit of the container", + "docType": undefined, "field": "beats_stats.metrics.beat.cgroup.memory.mem.limit.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Memory Limit", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_cgroup_memory_usage": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Memory usage of the container", + "docType": undefined, "field": "beats_stats.metrics.beat.cgroup.memory.mem.usage.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Memory Utilization (cgroup)", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_cpu_total": ApmCpuUtilizationMetric { + "aggs": undefined, "app": "apm", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Percentage of CPU time spent executing (user+kernel mode) for the APM process", + "docType": undefined, "field": "beats_stats.metrics.beat.cpu.total.value", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "CPU Utilization", "units": "%", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_mem_alloc": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Allocated memory", + "docType": undefined, "field": "beats_stats.metrics.beat.memstats.memory_alloc", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Allocated Memory", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_mem_gc_next": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Limit of allocated memory at which garbage collection will occur", + "docType": undefined, "field": "beats_stats.metrics.beat.memstats.gc_next", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "GC Next", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_mem_rss": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Resident set size of memory reserved by the APM service from the OS", + "docType": undefined, "field": "beats_stats.metrics.beat.memstats.rss", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Process Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_output_events_acked": ApmEventsRateClusterMetric { @@ -570,15 +723,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.acked", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Acked", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Acked Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_output_events_active": ApmEventsRateClusterMetric { @@ -611,15 +773,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.active", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Active", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Active Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_output_events_dropped": ApmEventsRateClusterMetric { @@ -652,15 +823,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.dropped", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Dropped", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Dropped Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_output_events_failed": ApmEventsRateClusterMetric { @@ -693,15 +873,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.failed", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Failed", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Failed Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_output_events_total": ApmEventsRateClusterMetric { @@ -734,15 +923,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_processor_error_transformations": ApmEventsRateClusterMetric { @@ -775,15 +973,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Error events processed", + "docType": undefined, "field": "beats_stats.metrics.apm-server.processor.error.transformations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Error", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Transformations", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_processor_metric_transformations": ApmEventsRateClusterMetric { @@ -816,15 +1023,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Metric events processed", + "docType": undefined, "field": "beats_stats.metrics.apm-server.processor.metric.transformations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Metric", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Transformations", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_processor_span_transformations": ApmEventsRateClusterMetric { @@ -857,15 +1073,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Span events processed", + "docType": undefined, "field": "beats_stats.metrics.apm-server.processor.span.transformations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Span", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Transformations", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_processor_transaction_transformations": ApmEventsRateClusterMetric { @@ -898,15 +1123,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Transaction events processed", + "docType": undefined, "field": "beats_stats.metrics.apm-server.processor.transaction.transformations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Transaction", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Processed Events", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_requests": ApmEventsRateClusterMetric { @@ -939,15 +1173,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests received by server", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.request.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Requested", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Request Count Intake API", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_count": ApmEventsRateClusterMetric { @@ -980,15 +1223,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests responded to by server", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Count Intake API", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_closed": ApmEventsRateClusterMetric { @@ -1021,15 +1273,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected during server shutdown", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.closed", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Closed", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Closed", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_concurrency": ApmEventsRateClusterMetric { @@ -1062,15 +1323,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected due to overall concurrency limit breach", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.concurrency", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Concurrency", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Concurrency", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_decode": ApmEventsRateClusterMetric { @@ -1103,15 +1373,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected to due decoding errors - invalid json, incorrect data type for entity", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.decode", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Decode", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Decode", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_forbidden": ApmEventsRateClusterMetric { @@ -1144,15 +1423,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Forbidden HTTP Requests rejected - CORS violation, disabled enpoint", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.forbidden", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Forbidden", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Forbidden", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_internal": ApmEventsRateClusterMetric { @@ -1185,15 +1473,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected due to a miscellaneous internal error", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.internal", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Internal", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Internal", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_method": ApmEventsRateClusterMetric { @@ -1226,15 +1523,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected due to incorrect HTTP method", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.method", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Method", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Method", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_queue": ApmEventsRateClusterMetric { @@ -1267,15 +1573,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected to due internal queue filling up", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.queue", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Queue", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Queue", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_ratelimit": ApmEventsRateClusterMetric { @@ -1308,15 +1623,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected to due excessive rate limit", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.ratelimit", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Rate limit", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Rate limit", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_toolarge": ApmEventsRateClusterMetric { @@ -1349,15 +1673,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected due to excessive payload size", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.toolarge", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Too large", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Response Errors Intake API", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_unauthorized": ApmEventsRateClusterMetric { @@ -1390,15 +1723,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected due to invalid secret token", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.unauthorized", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Unauthorized", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Unauthorized", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_errors_validate": ApmEventsRateClusterMetric { @@ -1431,15 +1773,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests rejected due to payload validation error", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.errors.validate", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Validate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Validate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_valid_accepted": ApmEventsRateClusterMetric { @@ -1472,15 +1823,24 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "HTTP Requests successfully reporting new events", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.valid.accepted", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Accepted", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Accepted", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_responses_valid_ok": ApmEventsRateClusterMetric { @@ -1513,79 +1873,138 @@ Object { }, }, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "200 OK response count", + "docType": undefined, "field": "beats_stats.metrics.apm-server.server.response.valid.ok", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Ok", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Ok", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "apm_system_os_load_1": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 1 minute", + "docType": undefined, "field": "beats_stats.metrics.system.load.1", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "1m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_system_os_load_15": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 15 minutes", + "docType": undefined, "field": "beats_stats.metrics.system.load.15", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "15m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "apm_system_os_load_5": ApmMetric { + "aggs": undefined, "app": "apm", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 5 minutes", + "docType": undefined, "field": "beats_stats.metrics.system.load.5", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "5m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "app_search_total_engines": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Current number of App Search engines within the Enterprise Search deployment.", + "docType": undefined, "field": "enterprisesearch.stats.product_usage.app_search.total_engines", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "App Search Engines", + "mbField": undefined, "metricAgg": "avg", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "beat_bytes_written": BeatsByteRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Bytes written to the output (consists of size of network headers and compressed payload)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.write.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Bytes Sent", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Throughput", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_cluster_output_events_ack_rate": BeatsEventsRateClusterMetric { @@ -1618,15 +2037,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events acknowledged by the output (includes events dropped by the output)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.acked", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Acknowledged", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_output_events_dropped_rate": BeatsEventsRateClusterMetric { @@ -1659,15 +2087,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "(Fatal drop) Events dropped by the output as being \\"invalid.\\" The output still acknowledges the event for the Beat to remove it from the queue.", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.dropped", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Dropped in Output", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_output_events_total": BeatsEventsRateClusterMetric { @@ -1700,15 +2137,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Emitted", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_output_read_bytes_rate": BeatsByteRateClusterMetric { @@ -1741,15 +2187,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Bytes read in response from the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.read.bytes", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Bytes Received", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Throughput", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_output_receiving_errors": BeatsEventsRateClusterMetric { @@ -1782,15 +2237,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Errors in reading the response from the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.read.errors", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Receiving", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Errors", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_output_sending_errors": BeatsEventsRateClusterMetric { @@ -1823,15 +2287,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Errors in writing the response from the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.write.errors", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Sending", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Errors", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_output_write_bytes_rate": BeatsByteRateClusterMetric { @@ -1864,15 +2337,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Bytes written to the output (consists of size of network headers and compressed payload)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.write.bytes", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Bytes Sent", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Throughput", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_pipeline_events_dropped_rate": BeatsEventsRateClusterMetric { @@ -1905,15 +2387,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events that have been dropped after N retries (N = max_retries setting)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.dropped", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Dropped in Pipeline", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_pipeline_events_emitted_rate": BeatsEventsRateClusterMetric { @@ -1946,15 +2437,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events added to the event pipeline queue", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.published", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Queued", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_pipeline_events_failed_rate": BeatsEventsRateClusterMetric { @@ -1987,15 +2487,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Failures that happened before event was added to the publishing pipeline (output was disabled or publisher client closed)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.failed", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Failed in Pipeline", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_pipeline_events_retry_rate": BeatsEventsRateClusterMetric { @@ -2028,15 +2537,24 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events in the pipeline that are trying again to be sent to the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.retry", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Retry in Pipeline", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cluster_pipeline_events_total_rate": BeatsEventsRateClusterMetric { @@ -2069,263 +2587,461 @@ Object { }, }, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "All events newly created in the publishing pipeline", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "beat_cpu_utilization": BeatsCpuUtilizationMetric { + "aggs": undefined, "app": "beats", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Percentage of CPU time spent executing (user+kernel mode) for the Beat process", + "docType": undefined, "field": "beats_stats.metrics.beat.cpu.total.value", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "CPU Utilization", "units": "%", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_handles_open": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Count of open file handlers", + "docType": undefined, "field": "beats_stats.metrics.beat.handles.open", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Open Handles", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Open Handles", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_mem_alloc": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Private memory in active use by the Beat", + "docType": undefined, "field": "beats_stats.metrics.beat.memstats.memory_alloc", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Active", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_mem_gc_next": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Limit of allocated memory at which garbage collection will occur", + "docType": undefined, "field": "beats_stats.metrics.beat.memstats.gc_next", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "GC Next", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_mem_rss": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Resident set size of memory reserved by the Beat from the OS", + "docType": undefined, "field": "beats_stats.metrics.beat.memstats.rss", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Process Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Memory", "units": "B", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_output_events_ack_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events acknowledged by the output (includes events dropped by the output)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.acked", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Acknowledged", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_output_events_dropped_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "(Fatal drop) Events dropped by the output as being \\"invalid.\\" The output still acknowledges the event for the Beat to remove it from the queue.", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.dropped", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Dropped in Output", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_output_events_total": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events processed by the output (including retries)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.events.total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Emitted", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_output_receiving_errors": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Errors in reading the response from the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.read.errors", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Receiving", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Errors", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_output_sending_errors": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Errors in writing the response from the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.write.errors", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Sending", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Output Errors", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_output_write_bytes_rate": BeatsByteRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Bytes read in response from the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.output.read.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Bytes Received", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Throughput", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_pipeline_events_dropped_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events that have been dropped after N retries (N = max_retries setting)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.dropped", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Dropped in Pipeline", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_pipeline_events_emitted_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events added to the event pipeline queue", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.published", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Queued", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_pipeline_events_failed_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Failures that happened before event was added to the publishing pipeline (output was disabled or publisher client closed)", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.failed", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Failed in Pipeline", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_pipeline_events_retry_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Events in the pipeline that are trying again to be sent to the output", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.retry", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Retry in Pipeline", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Fail Rates", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_pipeline_events_total_rate": BeatsEventsRateMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "New events sent to the publishing pipeline", + "docType": undefined, "field": "beats_stats.metrics.libbeat.pipeline.events.total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "New", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "Events Rate", "units": "/s", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_system_os_load_1": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 1 minute", + "docType": undefined, "field": "beats_stats.metrics.system.load.1", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "1m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_system_os_load_15": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 15 minutes", + "docType": undefined, "field": "beats_stats.metrics.system.load.15", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "15m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "beat_system_os_load_5": BeatsMetric { + "aggs": undefined, "app": "beats", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 5 minutes", + "docType": undefined, "field": "beats_stats.metrics.system.load.5", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "5m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "beats_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "beats_stats.beat.uuid", }, "ccr_sync_lag_ops": DifferenceMetric { @@ -2343,32 +3059,49 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "The number of operations the follower index is lagging behind the leader.", + "docType": undefined, "field": "", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "getFields": [Function], "label": "Ops delay", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Ops delay", "type": "ccr", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "ccr_sync_lag_time": MillisecondsToSecondsMetric { + "aggs": undefined, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "The amount of time the follower index is lagging behind the leader.", + "docType": undefined, "field": "ccr_stats.time_since_last_read_millis", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Fetch delay", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Fetch delay", "type": "ccr", "units": "s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "cluster_index_latency": LatencyMetric { @@ -2400,43 +3133,71 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This only considers primary shards.", + "docType": undefined, "field": "indices_stats._all.primaries.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing Latency", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "cluster", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "cluster_index_request_rate_primary": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of documents being indexed for primary shards.", + "docType": undefined, "field": "indices_stats._all.primaries.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Primary Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Rate", "type": "index", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "cluster_index_request_rate_total": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of documents being indexed for primary and replica shards.", + "docType": undefined, "field": "indices_stats._all.total.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Rate", "type": "index", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "cluster_query_latency": LatencyMetric { @@ -2468,311 +3229,559 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", + "docType": undefined, "field": "indices_stats._all.total.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search Latency", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "cluster", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "cluster_search_request_rate": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of search requests being executed across primary and replica shards. A single search can run against multiple shards!", + "docType": undefined, "field": "indices_stats._all.total.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Search Rate", "type": "cluster", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "crawler_workers_active": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Currently active App Search crawler workers.", + "docType": undefined, "field": "enterprisesearch.health.crawler.workers.active", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Active", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "crawler_workers_total": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "The number of crawler workers configured across all instances of App Search.", + "docType": undefined, "field": "enterprisesearch.health.crawler.workers.pool_size", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "title": "Crawler Workers", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_daemon_threads_current": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Currently running JVM daemon threads used by the application.", + "docType": undefined, "field": "enterprisesearch.health.jvm.threads.daemon", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Daemon Threads", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_gc_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The rate of JVM garbage collector invocations across the fleet.", + "docType": undefined, "field": "enterprisesearch.health.jvm.gc.collection_count", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "JVM GC Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_gc_time": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Time spent performing JVM garbage collections.", + "docType": undefined, "field": "enterprisesearch.health.jvm.gc.collection_time.ms", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Time spent on JVM garbage collection", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "ms", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_heap_committed": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "The amount of memory JVM has allocated from the OS and is available to the application.", + "docType": undefined, "field": "enterprisesearch.health.jvm.memory_usage.heap_committed.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Committed", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "bytes", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_heap_total": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Maximum amount of JVM heap memory available to the application.", + "docType": undefined, "field": "enterprisesearch.health.jvm.memory_usage.heap_max.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "title": "JVM Heap Usage", "units": "bytes", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_heap_used": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Current amount of JVM Heam memory used by the application.", + "docType": undefined, "field": "enterprisesearch.health.jvm.memory_usage.heap_used.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Used", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "bytes", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_1xx_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Outgoing HTTP 1xx responses across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.responses.1xx", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "1xx", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "title": "HTTP Responses", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_2xx_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Outgoing HTTP 2xx responses across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.responses.2xx", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "2xx", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_3xx_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Outgoing HTTP 3xx responses across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.responses.3xx", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "3xx", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_4xx_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Outgoing HTTP 4xx responses across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.responses.4xx", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "4xx", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_5xx_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Outgoing HTTP 5xx responses across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.responses.5xx", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "5xx", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_bytes_received_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Incoming HTTP traffic rate across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.network.received.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Received", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "title": "HTTP Traffic", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_bytes_received_total": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of bytes received by all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.network.received.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "HTTP Bytes Received", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "bytes", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_bytes_sent_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Outgoing HTTP traffic across all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.network.sent.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Sent", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_bytes_sent_total": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of bytes sent by all instances in the deployment.", + "docType": undefined, "field": "enterprisesearch.stats.http.network.sent.bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "HTTP Bytes Sent", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "bytes", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_connections_current": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Currently open incoming HTTP connections across all instances.", + "docType": undefined, "field": "enterprisesearch.stats.http.connections.current", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Open HTTP Connections", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_http_connections_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The rate of incoming HTTP connections across all instances.", + "docType": undefined, "field": "enterprisesearch.stats.http.connections.total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "HTTP Connections Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_jvm_finalizer_queue": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of objects within the JVM heap waiting for the finalizer thread.", + "docType": undefined, "field": "enterprisesearch.health.jvm.memory_usage.object_pending_finalization_count", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "JVM Objects Pending Finalization", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_threads_current": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Currently running JVM threads used by the application.", + "docType": undefined, "field": "enterprisesearch.health.jvm.threads.current", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Active Threads", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "title": "JVM Threads", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "enterprise_search_threads_rate": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Currently running JVM threads used by the application.", + "docType": undefined, "field": "enterprisesearch.health.jvm.threads.total_started", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Thread Creation Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "/s", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "index_document_count": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of documents, only including primary shards.", + "docType": undefined, "field": "index_stats.primaries.docs.count", + "fieldSource": undefined, "format": "0,0.[0]a", + "getDateHistogramSubAggs": undefined, "label": "Document Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "index", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_index_latency": LatencyMetric { @@ -2804,155 +3813,263 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This only considers primary shards.", + "docType": undefined, "field": "index_stats.primaries.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing Latency", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Latency", "type": "cluster", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_indexing_primaries_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent performing index operations on primary shards only.", + "docType": undefined, "field": "index_stats.primaries.indexing.index_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing (Primaries)", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_indexing_total": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of indexing operations.", + "docType": undefined, "field": "index_stats.primaries.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Index Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Rate", "type": "index", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_indexing_total_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent performing index operations on primary and replica shards.", + "docType": undefined, "field": "index_stats.total.indexing.index_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_mem_fielddata": IndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Fielddata (e.g., global ordinals or explicitly enabled fielddata on text fields). This is for the same shards, but not a part of Lucene Total.", + "docType": undefined, "field": "index_stats.total.fielddata.memory_size_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Fielddata", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_mem_fixed_bit_set": SingleIndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Fixed Bit Sets (e.g., deeply nested documents). This is a part of Lucene Total.", + "docType": undefined, "field": "index_stats.total.segments.fixed_bit_set_memory_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Fixed Bitsets", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory - Lucene", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_mem_query_cache": IndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Query Cache (e.g., cached filters). This is for the same shards, but not a part of Lucene Total.", + "docType": undefined, "field": "index_stats.total.query_cache.memory_size_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Query Cache", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory - Elasticsearch", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_mem_request_cache": IndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Request Cache (e.g., instant aggregations). This is for the same shards, but not a part of Lucene Total.", + "docType": undefined, "field": "index_stats.total.request_cache.memory_size_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Request Cache", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_mem_versions": SingleIndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Versioning (e.g., updates and deletes). This is NOT a part of Lucene Total.", + "docType": undefined, "field": "index_stats.total.segments.version_map_memory_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Version Map", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_mem_writer": SingleIndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by the Index Writer. This is NOT a part of Lucene Total.", + "docType": undefined, "field": "index_stats.total.segments.index_writer_memory_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Index Writer", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_merge_rate": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount in bytes of merged segments. Larger numbers indicate heavier disk activity.", + "docType": undefined, "field": "index_stats.total.merges.total_size_in_bytes", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Merge Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "index", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_query_latency": LatencyMetric { @@ -2984,276 +4101,474 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", + "docType": undefined, "field": "index_stats.total.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search Latency", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "cluster", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_refresh_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Time spent on Elasticsearch refresh for primary and replica shards.", + "docType": undefined, "field": "index_stats.total.refresh.total_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Refresh Time", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "index", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_request_rate_primary": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of documents being indexed for primary shards.", + "docType": undefined, "field": "index_stats.primaries.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Primary Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Rate", "type": "index", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_request_rate_total": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of documents being indexed for primary and replica shards.", + "docType": undefined, "field": "index_stats.total.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Rate", "type": "index", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_search_request_rate": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of search requests being executed across primary and replica shards. A single search can run against multiple shards!", + "docType": undefined, "field": "index_stats.total.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Search Rate", "type": "index", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_searching_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent performing search operations (per shard).", + "docType": undefined, "field": "index_stats.total.search.query_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_searching_total": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of search operations (per shard).", + "docType": undefined, "field": "index_stats.total.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Rate", "type": "index", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_segment_count_primaries": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of segments for primary shards.", + "docType": undefined, "field": "index_stats.primaries.segments.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Primaries", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Segment Count", "type": "index", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_segment_count_total": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of segments for primary and replica shards.", + "docType": undefined, "field": "index_stats.total.segments.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Segment Count", "type": "index", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_segment_merge_primaries_size": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Size of merges on primary shards.", + "docType": undefined, "field": "index_stats.primaries.merges.total_size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Merges (Primaries)", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Disk", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_segment_merge_total_size": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Size of merges on primary and replica shards.", + "docType": undefined, "field": "index_stats.total.merges.total_size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Merges", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Disk", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_segment_refresh_primaries_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent to perform refresh operations on primary shards.", + "docType": undefined, "field": "index_stats.primaries.refresh.total_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Primaries", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Refresh Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_segment_refresh_total_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent to perform refresh operations on primary and replica shards.", + "docType": undefined, "field": "index_stats.total.refresh.total_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Refresh Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_store_primaries_size": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Size of primary shards on disk.", + "docType": undefined, "field": "index_stats.primaries.store.size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Store (Primaries)", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Disk", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_store_total_size": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Size of primary and replica shards on disk.", + "docType": undefined, "field": "index_stats.total.store.size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Store", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Disk", "type": "index", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_throttle_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent with index throttling, which indicates slow merging.", + "docType": undefined, "field": "index_stats.primaries.indexing.throttle_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Index Throttling Time", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_throttling_indexing_primaries_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent throttling index operations on primary shards.", + "docType": undefined, "field": "index_stats.primaries.indexing.throttle_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing (Primaries)", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Throttle Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "index_throttling_indexing_total_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent throttling index operations on primary and replica shards.", + "docType": undefined, "field": "index_stats.total.indexing.throttle_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Throttle Time", "type": "index", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "kibana_average_concurrent_connections": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of open socket connections to the Kibana instance.", + "docType": undefined, "field": "kibana_stats.concurrent_connections", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "HTTP Connections", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "units": "", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_average_response_times": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average response time for client requests to the Kibana instance.", + "docType": undefined, "field": "kibana_stats.response_times.average", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Average", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "Client Response Time", "units": "ms", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_cluster_average_response_times": KibanaEventsRateClusterMetric { @@ -3286,15 +4601,24 @@ Object { }, }, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average response time for client requests to the Kibana instance.", + "docType": undefined, "field": "kibana_stats.response_times.average", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Average", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "Client Response Time", "units": "ms", + "usageField": undefined, "uuidField": "cluster_uuid", }, "kibana_cluster_max_response_times": KibanaEventsRateClusterMetric { @@ -3327,15 +4651,24 @@ Object { }, }, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Maximum response time for client requests to the Kibana instance.", + "docType": undefined, "field": "kibana_stats.response_times.max", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Max", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "Client Response Time", "units": "ms", + "usageField": undefined, "uuidField": "cluster_uuid", }, "kibana_cluster_requests": KibanaEventsRateClusterMetric { @@ -3368,128 +4701,227 @@ Object { }, }, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of client requests received by the Kibana instance.", + "docType": undefined, "field": "kibana_stats.requests.total", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Client Requests", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "units": "", + "usageField": undefined, "uuidField": "cluster_uuid", }, "kibana_max_response_times": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Maximum response time for client requests to the Kibana instance.", + "docType": undefined, "field": "kibana_stats.response_times.max", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Max", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "Client Response Time", "units": "ms", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_memory_heap_size_limit": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Limit of memory usage before garbage collection.", + "docType": undefined, "field": "kibana_stats.process.memory.heap.size_limit", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Heap Size Limit", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "Memory Size", "units": "B", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_memory_size": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total heap used by Kibana running in Node.js.", + "docType": undefined, "field": "kibana_stats.process.memory.resident_set_size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Memory Size", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "Memory Size", "units": "B", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_os_load_15m": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 15 minutes.", + "docType": undefined, "field": "kibana_stats.os.load.15m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "15m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_os_load_1m": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last minute.", + "docType": undefined, "field": "kibana_stats.os.load.1m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "1m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_os_load_5m": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 5 minutes.", + "docType": undefined, "field": "kibana_stats.os.load.5m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "5m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_process_delay": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Delay in Kibana server event loops. Longer delays may indicate blocking events in server thread, such as synchronous functions taking large amount of CPU time.", + "docType": undefined, "field": "kibana_stats.process.event_loop_delay", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Event Loop Delay", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "units": "ms", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_requests_disconnects": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of client disconnects to the Kibana instance.", + "docType": undefined, "field": "kibana_stats.requests.disconnects", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Client Disconnects", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "units": "", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "kibana_requests_total": KibanaMetric { + "aggs": undefined, "app": "kibana", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total number of client requests received by the Kibana instance.", + "docType": undefined, "field": "kibana_stats.requests.total", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Client Requests", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "kibana_stats.timestamp", "units": "", + "usageField": undefined, "uuidField": "kibana_stats.kibana.uuid", }, "logstash_cluster_events_input_rate": LogstashEventsRateClusterMetric { @@ -3522,14 +4954,23 @@ Object { }, }, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of events received per second by all Logstash nodes at the inputs stage.", + "docType": undefined, "field": "logstash_stats.events.in", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Events Received Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "logstash_cluster_events_latency": LogstashEventsLatencyClusterMetric { @@ -3581,14 +5022,22 @@ Object { }, "app": "logstash", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average time spent by events in the filter and output stages, which is the total time it takes to process events divided by number of events emitted.", + "docType": undefined, "field": "logstash_stats.events.out", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Event Latency", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "ms", + "usageField": undefined, "uuidField": "cluster_uuid", }, "logstash_cluster_events_output_rate": LogstashEventsRateClusterMetric { @@ -3621,52 +5070,89 @@ Object { }, }, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of events emitted per second by all Logstash nodes at the outputs stage.", + "docType": undefined, "field": "logstash_stats.events.out", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Events Emitted Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "/s", + "usageField": undefined, "uuidField": "cluster_uuid", }, "logstash_cluster_pipeline_nodes_count": LogstashPipelineNodeCountMetric { + "aggs": undefined, "app": "logstash", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of nodes on which the Logstash pipeline is running.", + "docType": undefined, "field": "logstash_stats.logstash.uuid", + "fieldSource": undefined, "format": "0,0.[00]", "getDateHistogramSubAggs": [Function], "label": "Pipeline Node Count", + "mbField": undefined, + "metricAgg": undefined, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_cluster_pipeline_throughput": LogstashPipelineThroughputMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of events emitted per second by the Logstash pipeline at the outputs stage.", + "docType": undefined, "field": "logstash_stats.pipelines.events.out", + "fieldSource": undefined, "format": "0,0.[00]", "getDateHistogramSubAggs": [Function], "label": "Pipeline Throughput", "mbField": "logstash.node.stats.pipelines.events.out", + "metricAgg": undefined, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "e/s", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_events_input_rate": LogstashEventsRateMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of events received per second by the Logstash node at the inputs stage.", + "docType": undefined, "field": "logstash_stats.events.in", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Events Received Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "/s", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_events_latency": LogstashEventsLatencyMetric { @@ -3698,39 +5184,67 @@ Object { }, "app": "logstash", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average time spent by events in the filter and output stages, which is the total time it takes to process events divided by number of events emitted.", + "docType": undefined, "field": "logstash_stats.events.out", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Event Latency", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "ms", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_events_output_rate": LogstashEventsRateMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of events emitted per second by the Logstash node at the outputs stage.", + "docType": undefined, "field": "logstash_stats.events.out", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Events Emitted Rate", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "/s", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_cgroup_periods": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The number of sampling periods from the Completely Fair Scheduler (CFS). Compare against the number of times throttled.", + "docType": undefined, "field": "logstash_stats.os.cgroup.cpu.stat.number_of_elapsed_periods", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Elapsed Periods", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "Cgroup CFS Stats", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_cgroup_quota": QuotaMetric { @@ -3767,12 +5281,16 @@ Object { }, "app": "logstash", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", + "docType": undefined, "field": "logstash_stats.process.cpu.percent", "fieldSource": "logstash_stats.os.cgroup", "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup CPU Utilization", + "mbField": undefined, "metricAgg": "max", "periodsField": "cpu.stat.number_of_elapsed_periods", "quotaField": "cpu.cfs_quota_micros", @@ -3816,12 +5334,16 @@ Object { }, "app": "logstash", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", + "docType": undefined, "field": "logstash_stats.process.cpu.percent", "fieldSource": "logstash_stats.os.cgroup", "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "CPU Utilization", + "mbField": undefined, "metricAgg": "max", "periodsField": "cpu.stat.number_of_elapsed_periods", "quotaField": "cpu.cfs_quota_micros", @@ -3831,148 +5353,257 @@ Object { "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_cgroup_throttled": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The amount of throttled time, reported in nanoseconds, of the Cgroup.", + "docType": undefined, "field": "logstash_stats.os.cgroup.cpu.stat.time_throttled_nanos", + "fieldSource": undefined, "format": "0,0.[0]a", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Throttling", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "Cgroup CPU Performance", "units": "ns", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_cgroup_throttled_count": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The number of times that the CPU was throttled by the Cgroup.", + "docType": undefined, "field": "logstash_stats.os.cgroup.cpu.stat.number_of_times_throttled", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Throttled Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "Cgroup CFS Stats", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_cgroup_usage": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The usage, reported in nanoseconds, of the Cgroup. Compare this with the throttling to discover issues.", + "docType": undefined, "field": "logstash_stats.os.cgroup.cpuacct.usage_nanos", + "fieldSource": undefined, "format": "0,0.[0]a", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Usage", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "Cgroup CPU Performance", "units": "ns", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_cpu_utilization": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Percentage of CPU usage reported by the OS (100% is the max).", + "docType": undefined, "field": "logstash_stats.process.cpu.percent", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "CPU Utilization", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "%", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_jvm_mem_max_in_bytes": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total heap available to Logstash running in the JVM.", + "docType": undefined, "field": "logstash_stats.jvm.mem.heap_max_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Max Heap", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "JVM Heap", "units": "B", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_jvm_mem_used_in_bytes": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total heap used by Logstash running in the JVM.", + "docType": undefined, "field": "logstash_stats.jvm.mem.heap_used_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Used Heap", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "JVM Heap", "units": "B", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_pipeline_nodes_count": LogstashPipelineNodeCountMetric { + "aggs": undefined, "app": "logstash", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of nodes on which the Logstash pipeline is running.", + "docType": undefined, "field": "logstash_stats.logstash.uuid", + "fieldSource": undefined, "format": "0,0.[00]", "getDateHistogramSubAggs": [Function], "label": "Pipeline Node Count", + "mbField": undefined, + "metricAgg": undefined, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_node_pipeline_throughput": LogstashPipelineThroughputMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of events emitted per second by the Logstash pipeline at the outputs stage.", + "docType": undefined, "field": "logstash_stats.pipelines.events.out", + "fieldSource": undefined, "format": "0,0.[00]", "getDateHistogramSubAggs": [Function], "label": "Pipeline Throughput", "mbField": "logstash.node.stats.pipelines.events.out", + "metricAgg": undefined, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "e/s", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_os_load_15m": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 15 minutes.", + "docType": undefined, "field": "logstash_stats.os.cpu.load_average.15m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "15m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_os_load_1m": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last minute.", + "docType": undefined, "field": "logstash_stats.os.cpu.load_average.1m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "1m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_os_load_5m": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last 5 minutes.", + "docType": undefined, "field": "logstash_stats.os.cpu.load_average.5m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "5m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "System Load", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_pipeline_max_queue_size": LogstashPipelineQueueSizeMetric { + "aggs": undefined, "app": "logstash", "calculation": [Function], "dateHistogramSubAggs": Object { @@ -4004,14 +5635,23 @@ Object { }, "derivative": false, "description": "Maximum size set for the persistent queues on this node.", + "docType": undefined, "field": "logstash_stats.pipelines.queue.max_queue_size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Max Queue Size", + "mbField": undefined, + "metricAgg": undefined, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "units": "B", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_pipeline_queue_size": LogstashPipelineQueueSizeMetric { + "aggs": undefined, "app": "logstash", "calculation": [Function], "dateHistogramSubAggs": Object { @@ -4043,39 +5683,67 @@ Object { }, "derivative": false, "description": "Current size of all persistent queues in the Logstash pipelines on this node.", + "docType": undefined, "field": "logstash_stats.pipelines.queue.queue_size_in_bytes", + "fieldSource": undefined, "format": "0,0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Queue Size", + "mbField": undefined, + "metricAgg": undefined, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "Persistent Queue Size", "units": "B", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "logstash_queue_events_count": LogstashMetric { + "aggs": undefined, "app": "logstash", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average number of events in the persistent queue waiting to be processed by the filter and output stages.", + "docType": undefined, "field": "logstash_stats.queue.events_count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Events Queued", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "logstash_stats.timestamp", "title": "Persistent Queue Events", "units": "", + "usageField": undefined, "uuidField": "logstash_stats.logstash.uuid", }, "node_cgroup_periods": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The number of sampling periods from the Completely Fair Scheduler (CFS). Compare against the number of times throttled.", + "docType": undefined, "field": "node_stats.os.cgroup.cpu.stat.number_of_elapsed_periods", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Elapsed Periods", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Cgroup CFS Stats", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_cgroup_quota": QuotaMetric { @@ -4112,12 +5780,16 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", + "docType": undefined, "field": "node_stats.process.cpu.percent", "fieldSource": "node_stats.os.cgroup", "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup CPU Utilization", + "mbField": undefined, "metricAgg": "max", "periodsField": "cpu.stat.number_of_elapsed_periods", "quotaField": "cpu.cfs_quota_micros", @@ -4162,12 +5834,16 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": true, "description": "CPU Usage time compared to the CPU quota shown in percentage. If CPU quotas are not set, then no data will be shown.", + "docType": undefined, "field": "node_stats.process.cpu.percent", "fieldSource": "node_stats.os.cgroup", "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "CPU Utilization", + "mbField": undefined, "metricAgg": "max", "periodsField": "cpu.stat.number_of_elapsed_periods", "quotaField": "cpu.cfs_quota_micros", @@ -4178,71 +5854,121 @@ Object { "uuidField": "source_node.uuid", }, "node_cgroup_throttled": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The amount of throttled time, reported in nanoseconds, of the cgroup.", + "docType": undefined, "field": "node_stats.os.cgroup.cpu.stat.time_throttled_nanos", + "fieldSource": undefined, "format": "0,0.[0]a", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Throttling", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Cgroup CPU Performance", "type": "node", "units": "ns", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_cgroup_throttled_count": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The number of times that the CPU was throttled by the cgroup.", + "docType": undefined, "field": "node_stats.os.cgroup.cpu.stat.number_of_times_throttled", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Throttled Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Cgroup CFS Stats", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_cgroup_usage": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "The usage, reported in nanoseconds, of the cgroup. Compare this with the throttling to discover issues.", + "docType": undefined, "field": "node_stats.os.cgroup.cpuacct.usage_nanos", + "fieldSource": undefined, "format": "0,0.[0]a", + "getDateHistogramSubAggs": undefined, "label": "Cgroup Usage", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Cgroup CPU Performance", "type": "node", "units": "ns", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_cpu_utilization": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Percentage of CPU usage for the Elasticsearch process.", + "docType": undefined, "field": "node_stats.process.cpu.percent", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "CPU Utilization", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "node", "units": "%", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_free_space": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Free disk space available on the node.", + "docType": undefined, "field": "node_stats.fs.total.available_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Disk Free Space", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_latency": LatencyMetric { @@ -4274,163 +6000,272 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This considers any shard located on this node, including replicas.", + "docType": undefined, "field": "node_stats.indices.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Latency", "type": "node", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_mem_fielddata": IndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Fielddata (e.g., global ordinals or explicitly enabled fielddata on text fields). This is for the same shards, but not a part of Lucene Total.", + "docType": undefined, "field": "node_stats.indices.fielddata.memory_size_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Fielddata", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_mem_fixed_bit_set": NodeIndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Fixed Bit Sets (e.g., deeply nested documents). This is a part of Lucene Total.", + "docType": undefined, "field": "node_stats.indices.segments.fixed_bit_set_memory_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Fixed Bitsets", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory - Lucene", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_mem_query_cache": IndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Query Cache (e.g., cached filters). This is for the same shards, but not a part of Lucene Total.", + "docType": undefined, "field": "node_stats.indices.query_cache.memory_size_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Query Cache", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory - Elasticsearch", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_mem_request_cache": IndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Request Cache (e.g., instant aggregations). This is for the same shards, but not a part of Lucene Total.", + "docType": undefined, "field": "node_stats.indices.request_cache.memory_size_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Request Cache", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_mem_versions": NodeIndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by Versioning (e.g., updates and deletes). This is NOT a part of Lucene Total.", + "docType": undefined, "field": "node_stats.indices.segments.version_map_memory_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Version Map", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_mem_writer": NodeIndexMemoryMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Heap memory used by the Index Writer. This is NOT a part of Lucene Total.", + "docType": undefined, "field": "node_stats.indices.segments.index_writer_memory_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Index Writer", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Index Memory", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_threads_get_queue": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of GET operations in the queue.", + "docType": undefined, "field": "node_stats.thread_pool.get.queue", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "GET Queue", + "mbField": undefined, "metricAgg": "max", "min": 0, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Read Threads", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_threads_get_rejected": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of GET operations that have been rejected, which occurs when the queue is full.", + "docType": undefined, "field": "node_stats.thread_pool.get.rejected", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "GET Rejections", + "mbField": undefined, "metricAgg": "max", "min": 0, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Read Threads", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_threads_search_queue": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of search operations in the queue (e.g., shard level searches).", + "docType": undefined, "field": "node_stats.thread_pool.search.queue", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search Queue", + "mbField": undefined, "metricAgg": "max", "min": 0, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Read Threads", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_threads_search_rejected": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of search operations that have been rejected, which occurs when the queue is full.", + "docType": undefined, "field": "node_stats.thread_pool.search.rejected", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search Rejections", + "mbField": undefined, "metricAgg": "max", "min": 0, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Read Threads", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_threads_write_queue": WriteThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", "calculation": [Function], "dateHistogramSubAggs": Object { @@ -4452,17 +6287,25 @@ Object { }, "derivative": false, "description": "Number of index, bulk, and write operations in the queue. The bulk threadpool was renamed to write in 6.3, and the index threadpool is deprecated.", + "docType": undefined, "field": "node_stats.thread_pool.write.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Write Queue", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Threads", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_threads_write_rejected": WriteThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", "calculation": [Function], "dateHistogramSubAggs": Object { @@ -4505,154 +6348,261 @@ Object { }, "derivative": false, "description": "Number of index, bulk, and write operations that have been rejected, which occurs when the queue is full. The bulk threadpool was renamed to write in 6.3, and the index threadpool is deprecated.", + "docType": undefined, "field": "node_stats.thread_pool.write.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Write Rejections", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Threads", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent on indexing operations.", + "docType": undefined, "field": "node_stats.indices.indexing.index_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Index Time", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Time", "type": "node", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_index_total": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of indexing operations.", + "docType": undefined, "field": "node_stats.indices.indexing.index_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Indexing Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Rate", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_gc_old_count": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of old Garbage Collections.", + "docType": undefined, "field": "node_stats.jvm.gc.collectors.old.collection_count", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Old", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "GC Rate", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_gc_old_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Time spent performing old Garbage Collections.", + "docType": undefined, "field": "node_stats.jvm.gc.collectors.old.collection_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Old", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "GC Duration", "type": "node", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_gc_young_count": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of young Garbage Collections.", + "docType": undefined, "field": "node_stats.jvm.gc.collectors.young.collection_count", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Young", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "GC Rate", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_gc_young_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Time spent performing young Garbage Collections.", + "docType": undefined, "field": "node_stats.jvm.gc.collectors.young.collection_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Young", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "GC Duration", "type": "node", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_mem_max_in_bytes": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total heap available to Elasticsearch running in the JVM.", + "docType": undefined, "field": "node_stats.jvm.mem.heap_max_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Max Heap", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "JVM Heap", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_mem_percent": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total heap used by Elasticsearch running in the JVM.", + "docType": undefined, "field": "node_stats.jvm.mem.heap_used_percent", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Used Heap", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "JVM Heap", "type": "node", "units": "%", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_jvm_mem_used_in_bytes": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Total heap used by Elasticsearch running in the JVM.", + "docType": undefined, "field": "node_stats.jvm.mem.heap_used_in_bytes", + "fieldSource": undefined, "format": "0.0 b", + "getDateHistogramSubAggs": undefined, "label": "Used Heap", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "JVM Heap", "type": "node", "units": "B", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_load_average": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Load average over the last minute.", + "docType": undefined, "field": "node_stats.os.cpu.load_average.1m", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "1m", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "System Load", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_query_latency": LatencyMetric { @@ -4684,335 +6634,573 @@ Object { }, "app": "elasticsearch", "calculation": [Function], + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", + "docType": undefined, "field": "node_stats.indices.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search", + "mbField": undefined, "metricAgg": "sum", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Latency", "type": "node", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_search_total": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of search operations (per shard).", + "docType": undefined, "field": "node_stats.indices.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search Total", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Request Rate", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_segment_count": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Maximum segment count for primary and replica shards on this node.", + "docType": undefined, "field": "node_stats.indices.segments.count", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Segment Count", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_bulk": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of bulk indexing operations waiting to be processed on this node. A single bulk request can create multiple bulk operations.", + "docType": undefined, "field": "node_stats.thread_pool.bulk.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Bulk", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_generic": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of generic (internal) operations waiting to be processed on this node.", + "docType": undefined, "field": "node_stats.thread_pool.generic.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Generic", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_get": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of get operations waiting to be processed on this node.", + "docType": undefined, "field": "node_stats.thread_pool.get.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Get", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_index": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of non-bulk, index operations waiting to be processed on this node.", + "docType": undefined, "field": "node_stats.thread_pool.index.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Index", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_management": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of management (internal) operations waiting to be processed on this node.", + "docType": undefined, "field": "node_stats.thread_pool.management.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Management", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_search": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of search operations waiting to be processed on this node. A single search request can create multiple search operations.", + "docType": undefined, "field": "node_stats.thread_pool.search.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_queued_watcher": ThreadPoolQueueMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Number of Watcher operations waiting to be processed on this node.", + "docType": undefined, "field": "node_stats.thread_pool.watcher.queue", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Watcher", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Queue", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_bulk": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Bulk rejections. These occur when the queue is full.", + "docType": undefined, "field": "node_stats.thread_pool.bulk.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Bulk", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_generic": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Generic (internal) rejections. These occur when the queue is full.", + "docType": undefined, "field": "node_stats.thread_pool.generic.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Generic", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_get": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Get rejections. These occur when the queue is full.", + "docType": undefined, "field": "node_stats.thread_pool.get.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Get", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_index": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Index rejections. These occur when the queue is full. You should look at bulk indexing.", + "docType": undefined, "field": "node_stats.thread_pool.index.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Index", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_management": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Get (internal) rejections. These occur when the queue is full.", + "docType": undefined, "field": "node_stats.thread_pool.management.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Management", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_search": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Search rejections. These occur when the queue is full. This can indicate over-sharding.", + "docType": undefined, "field": "node_stats.thread_pool.search.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Search", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_threads_rejected_watcher": ThreadPoolRejectedMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Watch rejections. These occur when the queue is full. This can indicate stuck-Watches.", + "docType": undefined, "field": "node_stats.thread_pool.watcher.rejected", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Watcher", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Thread Rejections", "type": "node", "units": "", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_throttle_index_time": ElasticsearchMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Amount of time spent with index throttling, which indicates slow disks on a node.", + "docType": undefined, "field": "node_stats.indices.indexing.throttle_time_in_millis", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Index Throttling Time", + "mbField": undefined, "metricAgg": "max", "min": 0, + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Indexing Time", "type": "node", "units": "ms", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_total_cumul_io": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Total I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", + "docType": undefined, "field": "node_stats.fs.io_stats.total.operations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total I/O", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "I/O Operations Rate", "type": "node", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_total_read_io": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Total Read I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", + "docType": undefined, "field": "node_stats.fs.io_stats.total.read_operations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Read I/O", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "I/O Operations Rate", "type": "node", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "node_total_write_io": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Total Write I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", + "docType": undefined, "field": "node_stats.fs.io_stats.total.write_operations", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Write I/O", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "I/O Operations Rate", "type": "node", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "search_request_rate": RequestRateMetric { + "aggs": undefined, "app": "elasticsearch", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": true, "description": "Number of search requests being executed across primary and replica shards. A single search can run against multiple shards!", + "docType": undefined, "field": "index_stats.total.search.query_total", + "fieldSource": undefined, "format": "0,0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Total Shards", + "mbField": undefined, "metricAgg": "max", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "timestamp", "title": "Search Rate", "type": "cluster", "units": "/s", + "usageField": undefined, "uuidField": "source_node.uuid", }, "workplace_search_total_org_sources": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Current number of Workplace Search org-wide content sources within the Enterprise Search deployment.", + "docType": undefined, "field": "enterprisesearch.stats.product_usage.workplace_search.total_org_sources", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Org Sources", + "mbField": undefined, "metricAgg": "avg", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "title": "Workpace Search Content Sources", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, "workplace_search_total_private_sources": EnterpriseSearchMetric { + "aggs": undefined, "app": "enterprise_search", + "calculation": undefined, + "dateHistogramSubAggs": undefined, "derivative": false, "description": "Current number of Workplace Search private content sources within the Enterprise Search deployment.", + "docType": undefined, "field": "enterprisesearch.stats.product_usage.workplace_search.total_private_sources", + "fieldSource": undefined, "format": "0.[00]", + "getDateHistogramSubAggs": undefined, "label": "Private Sources", + "mbField": undefined, "metricAgg": "avg", + "periodsField": undefined, + "quotaField": undefined, "timestampField": "@timestamp", "units": "", + "usageField": undefined, "uuidField": "enterprisesearch.cluster_uuid", }, } diff --git a/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.ts b/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.ts index 4a97e7823999b..89decb1ed0762 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/apm/classes.ts @@ -8,14 +8,17 @@ /* eslint-disable max-classes-per-file */ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { ClusterMetric, Metric } from '../classes'; + +import { ClusterMetric, Metric, MetricOptions } from '../classes'; import { SMALL_FLOAT, LARGE_FLOAT } from '../../../../common/formatting'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; +type ApmClusterMetricOptions = Pick< + MetricOptions, + 'field' | 'title' | 'label' | 'description' | 'derivative' | 'format' | 'metricAgg' | 'units' +>; export class ApmClusterMetric extends ClusterMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: ApmClusterMetricOptions) { super({ ...opts, app: 'apm', @@ -31,9 +34,12 @@ export class ApmClusterMetric extends ClusterMetric { } } +type ApmMetricOptions = Pick< + MetricOptions, + 'title' | 'label' | 'description' | 'field' | 'format' | 'metricAgg' | 'units' | 'derivative' +>; export class ApmMetric extends Metric { - // @ts-ignore - constructor(opts) { + constructor(opts: ApmMetricOptions) { super({ ...opts, app: 'apm', @@ -51,9 +57,12 @@ export class ApmMetric extends Metric { export type ApmMetricFields = ReturnType; +type ApmCpuUtilizationMetricOptions = Pick< + MetricOptions, + 'title' | 'label' | 'description' | 'field' +>; export class ApmCpuUtilizationMetric extends ApmMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: ApmCpuUtilizationMetricOptions) { super({ ...opts, format: SMALL_FLOAT, @@ -62,12 +71,13 @@ export class ApmCpuUtilizationMetric extends ApmMetric { derivative: true, }); - /* - * Convert a counter of milliseconds of utilization time into a percentage of the bucket size - */ - // @ts-ignore - this.calculation = ({ metric_deriv: metricDeriv } = {}, _key, _metric, bucketSizeInSeconds) => { - if (metricDeriv) { + this.calculation = ( + { metric_deriv: metricDeriv } = { metric_deriv: undefined }, + _key, + _metric, + bucketSizeInSeconds + ) => { + if (metricDeriv && bucketSizeInSeconds) { const { value: metricDerivValue } = metricDeriv; const bucketSizeInMillis = bucketSizeInSeconds * 1000; @@ -80,9 +90,13 @@ export class ApmCpuUtilizationMetric extends ApmMetric { } } +type ApmEventsRateClusterMetricOptions = Pick< + ApmClusterMetricOptions, + 'field' | 'title' | 'label' | 'description' +>; + export class ApmEventsRateClusterMetric extends ApmClusterMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: ApmEventsRateClusterMetricOptions) { super({ ...opts, derivative: true, @@ -93,7 +107,6 @@ export class ApmEventsRateClusterMetric extends ApmClusterMetric { }), }); - // @ts-ignore this.aggs = { beats_uuids: { terms: { @@ -103,7 +116,6 @@ export class ApmEventsRateClusterMetric extends ApmClusterMetric { aggs: { event_rate_per_beat: { max: { - // @ts-ignore field: this.field, }, }, diff --git a/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.ts similarity index 100% rename from x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.ts index 7c779f31c684b..8cd3941be1338 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/apm/metrics.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { LARGE_BYTES, LARGE_FLOAT } from '../../../../common/formatting'; import { ApmMetric, ApmCpuUtilizationMetric, ApmEventsRateClusterMetric } from './classes'; -import { i18n } from '@kbn/i18n'; import { QuotaMetric } from '../classes'; const instanceSystemLoadTitle = i18n.translate( diff --git a/x-pack/plugins/monitoring/server/lib/metrics/beats/classes.ts b/x-pack/plugins/monitoring/server/lib/metrics/beats/classes.ts index 78e731cee8881..44241882d5077 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/beats/classes.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/beats/classes.ts @@ -8,8 +8,8 @@ /* eslint-disable max-classes-per-file */ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { ClusterMetric, Metric } from '../classes'; + +import { ClusterMetric, ClusterMetricOptions, Metric, MetricOptions } from '../classes'; import { SMALL_FLOAT, LARGE_FLOAT, LARGE_BYTES } from '../../../../common/formatting'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; @@ -17,9 +17,13 @@ const perSecondUnitLabel = i18n.translate('xpack.monitoring.metrics.beats.perSec defaultMessage: '/s', }); +type BeatsClusterMetricOptions = Pick< + ClusterMetricOptions, + 'derivative' | 'format' | 'metricAgg' | 'units' | 'field' | 'label' | 'description' +>; + export class BeatsClusterMetric extends ClusterMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsClusterMetricOptions) { super({ ...opts, app: 'beats', @@ -35,9 +39,14 @@ export class BeatsClusterMetric extends ClusterMetric { } } +type BeatsEventsRateClusterMetricOptions = Pick< + ClusterMetricOptions, + 'field' | 'title' | 'label' | 'description' +> & + Partial>; + export class BeatsEventsRateClusterMetric extends BeatsClusterMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsEventsRateClusterMetricOptions) { super({ ...opts, derivative: true, @@ -45,7 +54,6 @@ export class BeatsEventsRateClusterMetric extends BeatsClusterMetric { metricAgg: 'max', units: perSecondUnitLabel, }); - // @ts-ignore this.aggs = { beats_uuids: { @@ -56,7 +64,6 @@ export class BeatsEventsRateClusterMetric extends BeatsClusterMetric { aggs: { event_rate_per_beat: { max: { - // @ts-ignore field: this.field, }, }, @@ -79,9 +86,13 @@ export class BeatsEventsRateClusterMetric extends BeatsClusterMetric { } } +type BeatsMetricOptions = Pick< + MetricOptions, + 'field' | 'title' | 'label' | 'description' | 'format' | 'metricAgg' | 'units' | 'derivative' +>; + export class BeatsMetric extends Metric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsMetricOptions) { super({ ...opts, app: 'beats', @@ -99,9 +110,12 @@ export class BeatsMetric extends Metric { export type BeatsMetricFields = ReturnType; +type BeatsByteRateClusterMetricOptions = Pick< + ClusterMetricOptions, + 'field' | 'title' | 'label' | 'description' +>; export class BeatsByteRateClusterMetric extends BeatsEventsRateClusterMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsByteRateClusterMetricOptions) { super({ ...opts, format: LARGE_BYTES, @@ -109,9 +123,12 @@ export class BeatsByteRateClusterMetric extends BeatsEventsRateClusterMetric { } } +type BeatsEventsRateMetricOptions = Pick< + MetricOptions, + 'field' | 'title' | 'label' | 'description' +>; export class BeatsEventsRateMetric extends BeatsMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsEventsRateMetricOptions) { super({ ...opts, format: LARGE_FLOAT, @@ -122,9 +139,10 @@ export class BeatsEventsRateMetric extends BeatsMetric { } } +type BeatsByteRateMetricOptions = Pick; + export class BeatsByteRateMetric extends BeatsMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsByteRateMetricOptions) { super({ ...opts, format: LARGE_BYTES, @@ -135,9 +153,13 @@ export class BeatsByteRateMetric extends BeatsMetric { } } +type BeatsCpuUtilizationMetricOptions = Pick< + MetricOptions, + 'field' | 'title' | 'label' | 'description' +>; + export class BeatsCpuUtilizationMetric extends BeatsMetric { - // @ts-ignore - constructor(opts) { + constructor(opts: BeatsCpuUtilizationMetricOptions) { super({ ...opts, format: SMALL_FLOAT, @@ -149,9 +171,14 @@ export class BeatsCpuUtilizationMetric extends BeatsMetric { /* * Convert a counter of milliseconds of utilization time into a percentage of the bucket size */ - // @ts-ignore - this.calculation = ({ metric_deriv: metricDeriv } = {}, _key, _metric, bucketSizeInSeconds) => { - if (metricDeriv) { + + this.calculation = ( + { metric_deriv: metricDeriv } = { metric_deriv: undefined }, + _key, + _metric, + bucketSizeInSeconds + ) => { + if (metricDeriv && bucketSizeInSeconds) { const { value } = metricDeriv; const bucketSizeInMillis = bucketSizeInSeconds * 1000; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/beats/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/beats/metrics.ts similarity index 100% rename from x-pack/plugins/monitoring/server/lib/metrics/beats/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/beats/metrics.ts index c9d1c015e22d6..6bf7cf67208b1 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/beats/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/beats/metrics.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { BeatsEventsRateClusterMetric, BeatsByteRateClusterMetric, @@ -14,7 +15,6 @@ import { BeatsMetric, } from './classes'; import { LARGE_FLOAT, LARGE_BYTES, SMALL_FLOAT } from '../../../../common/formatting'; -import { i18n } from '@kbn/i18n'; const eventsRateTitle = i18n.translate('xpack.monitoring.metrics.beats.eventsRateTitle', { defaultMessage: 'Events Rate', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/classes/cluster_metric.js b/x-pack/plugins/monitoring/server/lib/metrics/classes/cluster_metric.ts similarity index 68% rename from x-pack/plugins/monitoring/server/lib/metrics/classes/cluster_metric.js rename to x-pack/plugins/monitoring/server/lib/metrics/classes/cluster_metric.ts index 984b896052c47..bf995ffa592a7 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/classes/cluster_metric.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/classes/cluster_metric.ts @@ -5,10 +5,14 @@ * 2.0. */ -import { Metric } from './metric'; +import { Metric, MetricOptions } from './metric'; + +export type ClusterMetricOptions = MetricOptions & { + uuidField: string; +}; export class ClusterMetric extends Metric { - constructor(opts) { + constructor(opts: ClusterMetricOptions) { super({ ...opts, uuidField: 'cluster_uuid', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/classes/index.js b/x-pack/plugins/monitoring/server/lib/metrics/classes/index.ts similarity index 77% rename from x-pack/plugins/monitoring/server/lib/metrics/classes/index.js rename to x-pack/plugins/monitoring/server/lib/metrics/classes/index.ts index e0f19d47c964e..ae8fb55b173ca 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/classes/index.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/classes/index.ts @@ -8,3 +8,6 @@ export { Metric } from './metric'; export { ClusterMetric } from './cluster_metric'; export { QuotaMetric } from './quota_metric'; + +export type { MetricOptions } from './metric'; +export type { ClusterMetricOptions } from './cluster_metric'; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/classes/metric.js b/x-pack/plugins/monitoring/server/lib/metrics/classes/metric.ts similarity index 57% rename from x-pack/plugins/monitoring/server/lib/metrics/classes/metric.js rename to x-pack/plugins/monitoring/server/lib/metrics/classes/metric.ts index ba175ad35988b..8c2c54cd2f4ed 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/classes/metric.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/classes/metric.ts @@ -8,9 +8,57 @@ import _ from 'lodash'; import { MissingRequiredError } from '../../error_missing_required'; +interface RequiredMetricOptions { + field: string; + label: string; + description: string; + format: string; + units: string; + timestampField: string; + [key: string]: string | boolean | number; +} + +interface OptionalMetricOptions { + app?: string; + metricAgg?: string; + mbField?: string; + type?: string; +} + +interface DefaultMetricOptions { + derivative?: boolean; +} + +export type MetricOptions = RequiredMetricOptions & OptionalMetricOptions & DefaultMetricOptions; + export class Metric { - constructor(opts) { - const props = { + public field!: string; + public docType?: string; + public label!: string; + public description!: string; + public format!: string; + public units!: string; + public timestampField!: string; + public app?: string; + public metricAgg?: string; + public mbField?: string; + public derivative: boolean = false; + public aggs?: object; + public dateHistogramSubAggs?: object; + public getDateHistogramSubAggs?: (options: any) => object; + public calculation?: ( + bucket: any, + key?: string, + metric?: Metric, + defaultSizeInSeconds?: number + ) => number | null; + public fieldSource?: string; + public usageField?: string; + public periodsField?: string; + public quotaField?: string; + + constructor(opts: MetricOptions) { + const props: Required = { derivative: false, }; @@ -22,14 +70,15 @@ export class Metric { units: opts.units, timestampField: opts.timestampField, }; + this.checkRequiredParams(requireds); + _.assign(this, _.defaults(opts, props)); } - checkRequiredParams(requireds) { + checkRequiredParams(requireds: RequiredParams) { const undefKey = _.findKey(requireds, _.isUndefined); if (undefKey) { - console.log(`Missing required field: [${undefKey}]`); throw new MissingRequiredError(undefKey); } } @@ -68,7 +117,7 @@ export class Metric { return fields && fields.length ? fields[0].split('.')[0] : null; } - static calculateLatency(timeInMillis, totalEvents) { + static calculateLatency(timeInMillis: number | null, totalEvents: number | null) { if (timeInMillis === null || totalEvents === null) { return null; } else if (timeInMillis < 0 || totalEvents < 0) { diff --git a/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js b/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts similarity index 85% rename from x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js rename to x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts index 4912338bcb335..8e1cb8930f804 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/classes/quota_metric.ts @@ -6,12 +6,26 @@ */ import { get } from 'lodash'; -import { Metric } from './metric'; +import { Metric, MetricOptions } from './metric'; import { LARGE_FLOAT } from '../../../../common/formatting'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; +type QuotaMetricOptions = Pick< + MetricOptions, + | 'fieldSource' + | 'usageField' + | 'periodsField' + | 'quotaField' + | 'field' + | 'label' + | 'description' + | 'app' + | 'timestampField' +> & + Partial>; + export class QuotaMetric extends Metric { - constructor(opts) { + constructor(opts: QuotaMetricOptions) { super({ ...opts, format: LARGE_FLOAT, @@ -51,7 +65,7 @@ export class QuotaMetric extends Metric { }, }; - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { const quota = get(bucket, 'quota.value'); const deltaUsageDerivNormalizedValue = get(bucket, 'usage_deriv.normalized_value'); const periodsDerivNormalizedValue = get(bucket, 'periods_deriv.normalized_value'); diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts similarity index 71% rename from x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js rename to x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts index 2141b30b37290..a70299c3fa42d 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/classes.ts @@ -5,14 +5,22 @@ * 2.0. */ +/* eslint-disable max-classes-per-file */ + import _ from 'lodash'; -import { Metric } from '../classes'; +import { i18n } from '@kbn/i18n'; +import { Metric, MetricOptions } from '../classes'; import { LARGE_FLOAT, SMALL_FLOAT, SMALL_BYTES } from '../../../../common/formatting'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; -import { i18n } from '@kbn/i18n'; + +type ElasticsearchMetricOptions = Pick< + MetricOptions, + 'format' | 'metricAgg' | 'field' | 'label' | 'description' | 'units' | 'derivative' +> & + Partial> & { type: string; min?: number }; export class ElasticsearchMetric extends Metric { - constructor(opts) { + constructor(opts: ElasticsearchMetricOptions) { super({ ...opts, app: 'elasticsearch', @@ -31,8 +39,18 @@ export class ElasticsearchMetric extends Metric { } } +type DifferenceMetricOptions = Pick< + MetricOptions, + 'description' | 'format' | 'metricAgg' | 'units' | 'label' | 'title' +> & { + type: string; + fieldSource: string; + metric: string; + metric2: string; +}; + export class DifferenceMetric extends ElasticsearchMetric { - constructor({ fieldSource, metric, metric2, ...opts }) { + constructor({ fieldSource, metric, metric2, ...opts }: DifferenceMetricOptions) { super({ ...opts, field: '', // NOTE: this is not used for this @@ -56,14 +74,21 @@ export class DifferenceMetric extends ElasticsearchMetric { this.getFields = () => [`${fieldSource}.${metric}`, `${fieldSource}.${metric2}`]; - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { return _.get(bucket, 'metric_max.value') - _.get(bucket, 'metric2_max.value'); }; } } +type LatencyMetricOptions = Pick & + Partial> & { + type: string; + fieldSource: string; + metric: string; + }; + export class LatencyMetric extends ElasticsearchMetric { - constructor({ metric, fieldSource, ...opts }) { + constructor({ metric, fieldSource, ...opts }: LatencyMetricOptions) { super({ ...opts, format: LARGE_FLOAT, @@ -117,7 +142,7 @@ export class LatencyMetric extends ElasticsearchMetric { }, }; - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { const timeInMillisDeriv = _.get(bucket, 'event_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'event_total_deriv.normalized_value', null); @@ -126,8 +151,12 @@ export class LatencyMetric extends ElasticsearchMetric { } } +type RequestRateMetricOptions = Pick & + Partial> & { + type: string; + }; export class RequestRateMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: RequestRateMetricOptions) { super({ ...opts, derivative: true, @@ -140,8 +169,10 @@ export class RequestRateMetric extends ElasticsearchMetric { } } +type ThreadPoolQueueMetricOptions = Pick & + Partial>; export class ThreadPoolQueueMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: ThreadPoolQueueMetricOptions) { super({ ...opts, title: 'Thread Queue', @@ -153,8 +184,11 @@ export class ThreadPoolQueueMetric extends ElasticsearchMetric { } } +type ThreadPoolRejectedMetricOptions = Pick & + Partial>; + export class ThreadPoolRejectedMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: ThreadPoolRejectedMetricOptions) { super({ ...opts, title: 'Thread Rejections', @@ -174,8 +208,11 @@ export class ThreadPoolRejectedMetric extends ElasticsearchMetric { * @see NodeIndexMemoryMetric * @see SingleIndexMemoryMetric */ + +type IndexMemoryMetricOptions = Pick & + Partial> & { type: string }; export class IndexMemoryMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: IndexMemoryMetricOptions) { super({ title: 'Index Memory', ...opts, @@ -186,8 +223,11 @@ export class IndexMemoryMetric extends ElasticsearchMetric { } } +type NodeIndexMemoryMetricOptions = Pick & + Partial>; + export class NodeIndexMemoryMetric extends IndexMemoryMetric { - constructor(opts) { + constructor(opts: NodeIndexMemoryMetricOptions) { super({ ...opts, type: 'node', @@ -198,8 +238,11 @@ export class NodeIndexMemoryMetric extends IndexMemoryMetric { } } +type IndicesMemoryMetricOptions = Pick & + Partial>; + export class IndicesMemoryMetric extends IndexMemoryMetric { - constructor(opts) { + constructor(opts: IndicesMemoryMetricOptions) { super({ ...opts, type: 'cluster', @@ -210,8 +253,11 @@ export class IndicesMemoryMetric extends IndexMemoryMetric { } } +type SingleIndexMemoryMetricOptions = Pick & + Partial>; + export class SingleIndexMemoryMetric extends IndexMemoryMetric { - constructor(opts) { + constructor(opts: SingleIndexMemoryMetricOptions) { super({ ...opts, type: 'index', @@ -222,8 +268,11 @@ export class SingleIndexMemoryMetric extends IndexMemoryMetric { } } +type WriteThreadPoolQueueMetricOptions = Pick & + Partial>; + export class WriteThreadPoolQueueMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: WriteThreadPoolQueueMetricOptions) { super({ ...opts, field: 'node_stats.thread_pool.write.queue', // in 7.0, we can only check for this threadpool @@ -244,8 +293,7 @@ export class WriteThreadPoolQueueMetric extends ElasticsearchMetric { max: { field: 'node_stats.thread_pool.write.queue' }, }, }; - - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { const index = _.get(bucket, 'index.value', null); const bulk = _.get(bucket, 'bulk.value', null); const write = _.get(bucket, 'write.value', null); @@ -260,8 +308,13 @@ export class WriteThreadPoolQueueMetric extends ElasticsearchMetric { } } +type WriteThreadPoolRejectedMetricOptions = Pick< + MetricOptions, + 'label' | 'description' | 'field' | 'title' +>; + export class WriteThreadPoolRejectedMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: WriteThreadPoolRejectedMetricOptions) { super({ ...opts, field: 'node_stats.thread_pool.write.rejected', // in 7.0, we can only check for this threadpool @@ -303,14 +356,13 @@ export class WriteThreadPoolRejectedMetric extends ElasticsearchMetric { }, }, }; - - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { const index = _.get(bucket, 'index_deriv.normalized_value', null); const bulk = _.get(bucket, 'bulk_deriv.normalized_value', null); const write = _.get(bucket, 'write_deriv.normalized_value', null); if (index !== null || bulk !== null || write !== null) { - const valueOrZero = (value) => (value < 0 ? 0 : value || 0); + const valueOrZero = (value: number) => (value < 0 ? 0 : value || 0); return valueOrZero(index) + valueOrZero(bulk) + valueOrZero(write); } @@ -321,16 +373,20 @@ export class WriteThreadPoolRejectedMetric extends ElasticsearchMetric { } } +type MillisecondsToSecondsMetricOptions = Pick< + MetricOptions, + 'label' | 'description' | 'field' | 'title' | 'format' | 'metricAgg' | 'units' +> & { type: string }; + export class MillisecondsToSecondsMetric extends ElasticsearchMetric { - constructor(opts) { + constructor(opts: MillisecondsToSecondsMetricOptions) { super({ ...opts, units: i18n.translate('xpack.monitoring.metrics.es.secondsUnitLabel', { defaultMessage: 's', }), }); - - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { return _.get(bucket, 'metric.value') / 1000; }; } diff --git a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.ts similarity index 100% rename from x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.ts index 83841b0ecb39e..c62a27000c878 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/elasticsearch/metrics.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { QuotaMetric } from '../classes'; import { RequestRateMetric, @@ -27,7 +28,6 @@ import { LARGE_BYTES, LARGE_ABBREVIATED, } from '../../../../common/formatting'; -import { i18n } from '@kbn/i18n'; const indexingRateTitle = i18n.translate('xpack.monitoring.metrics.es.indexingRateTitle', { defaultMessage: 'Indexing Rate', // title to use for the chart diff --git a/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/classes.ts b/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/classes.ts index b1e2ab691c0e7..a97aa7d91f88a 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/classes.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/classes.ts @@ -5,12 +5,16 @@ * 2.0. */ -// @ts-ignore -import { Metric } from '../classes'; +import { Metric, MetricOptions } from '../classes'; + +type EnterpriseSearchMetricOptions = Pick< + MetricOptions, + 'field' | 'metricAgg' | 'label' | 'description' | 'format' | 'units' +> & + Partial>; export class EnterpriseSearchMetric extends Metric { - // @ts-ignore - constructor(opts) { + constructor(opts: EnterpriseSearchMetricOptions) { super({ ...opts, app: 'enterprise_search', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/metrics.ts similarity index 100% rename from x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/metrics.ts index 39a8475471d12..8955bfe59bf11 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/enterprise_search/metrics.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { EnterpriseSearchMetric } from './classes'; import { LARGE_BYTES, SMALL_FLOAT, LARGE_FLOAT } from '../../../../common/formatting'; -import { i18n } from '@kbn/i18n'; const perSecondUnitLabel = i18n.translate('xpack.monitoring.metrics.entSearch.perSecondUnitLabel', { defaultMessage: '/s', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/index.ts b/x-pack/plugins/monitoring/server/lib/metrics/index.ts index 50f477c27f49a..12c19cdd4b870 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/index.ts +++ b/x-pack/plugins/monitoring/server/lib/metrics/index.ts @@ -5,17 +5,13 @@ * 2.0. */ -// @ts-ignore export { ElasticsearchMetric } from './elasticsearch/classes'; -// @ts-ignore export { KibanaClusterMetric, KibanaMetric } from './kibana/classes'; export type { ApmMetricFields } from './apm/classes'; export { ApmMetric, ApmClusterMetric } from './apm/classes'; -// @ts-ignore export { LogstashClusterMetric, LogstashMetric } from './logstash/classes'; export type { BeatsMetricFields } from './beats/classes'; export { BeatsClusterMetric, BeatsMetric } from './beats/classes'; export { EnterpriseSearchMetric } from './enterprise_search/classes'; export type { EnterpriseSearchMetricFields } from './enterprise_search/classes'; -// @ts-ignore export { metrics } from './metrics'; diff --git a/x-pack/plugins/monitoring/server/lib/metrics/kibana/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/kibana/classes.ts similarity index 68% rename from x-pack/plugins/monitoring/server/lib/metrics/kibana/classes.js rename to x-pack/plugins/monitoring/server/lib/metrics/kibana/classes.ts index fccd50c8e763c..84c2d60b43fe2 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/kibana/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/kibana/classes.ts @@ -5,11 +5,19 @@ * 2.0. */ -import { ClusterMetric, Metric } from '../classes'; +/* eslint-disable max-classes-per-file */ + +import { ClusterMetric, Metric, MetricOptions } from '../classes'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; +type KibanaClusterMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'units' | 'metricAgg' +> & + Partial>; + export class KibanaClusterMetric extends ClusterMetric { - constructor(opts) { + constructor(opts: KibanaClusterMetricOptions) { super({ ...opts, app: 'kibana', @@ -25,8 +33,14 @@ export class KibanaClusterMetric extends ClusterMetric { } } +type KibanaEventsRateClusterMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'units' +> & + Partial>; + export class KibanaEventsRateClusterMetric extends KibanaClusterMetric { - constructor(opts) { + constructor(opts: KibanaEventsRateClusterMetricOptions) { super({ ...opts, metricAgg: 'max', @@ -63,8 +77,14 @@ export class KibanaEventsRateClusterMetric extends KibanaClusterMetric { } } +type KibanaMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'metricAgg' | 'units' +> & + Partial>; + export class KibanaMetric extends Metric { - constructor(opts) { + constructor(opts: KibanaMetricOptions) { super({ ...opts, app: 'kibana', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/kibana/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/kibana/metrics.ts similarity index 100% rename from x-pack/plugins/monitoring/server/lib/metrics/kibana/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/kibana/metrics.ts index 1cf156f97597c..e6db13e222251 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/kibana/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/kibana/metrics.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { KibanaEventsRateClusterMetric, KibanaMetric } from './classes'; import { LARGE_FLOAT, SMALL_FLOAT, LARGE_BYTES } from '../../../../common/formatting'; -import { i18n } from '@kbn/i18n'; const clientResponseTimeTitle = i18n.translate( 'xpack.monitoring.metrics.kibana.clientResponseTimeTitle', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js b/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts similarity index 77% rename from x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js rename to x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts index 234a1ac89f66d..6fcd757e3c6c3 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/logstash/classes.ts @@ -5,11 +5,13 @@ * 2.0. */ +/* eslint-disable max-classes-per-file */ + import _ from 'lodash'; -import { ClusterMetric, Metric } from '../classes'; +import { i18n } from '@kbn/i18n'; +import { ClusterMetric, Metric, MetricOptions } from '../classes'; import { LARGE_FLOAT } from '../../../../common/formatting'; import { NORMALIZED_DERIVATIVE_UNIT } from '../../../../common/constants'; -import { i18n } from '@kbn/i18n'; const msTimeUnitLabel = i18n.translate('xpack.monitoring.metrics.logstash.msTimeUnitLabel', { defaultMessage: 'ms', @@ -18,8 +20,14 @@ const perSecondUnitLabel = i18n.translate('xpack.monitoring.metrics.logstash.per defaultMessage: '/s', }); +type LogstashClusterMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'metricAgg' | 'units' +> & + Partial>; + export class LogstashClusterMetric extends ClusterMetric { - constructor(opts) { + constructor(opts: LogstashClusterMetricOptions) { super({ ...opts, app: 'logstash', @@ -35,8 +43,13 @@ export class LogstashClusterMetric extends ClusterMetric { } } +type LogstashEventsLatencyClusterMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' +>; + export class LogstashEventsLatencyClusterMetric extends LogstashClusterMetric { - constructor(opts) { + constructor(opts: LogstashEventsLatencyClusterMetricOptions) { super({ ...opts, format: LARGE_FLOAT, @@ -90,18 +103,23 @@ export class LogstashEventsLatencyClusterMetric extends LogstashClusterMetric { }, }, }; + } - this.calculation = (bucket) => { - const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); - const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); + public calculation = (bucket: object) => { + const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); + const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); - return Metric.calculateLatency(timeInMillisDeriv, totalEventsDeriv); - }; - } + return Metric.calculateLatency(timeInMillisDeriv, totalEventsDeriv); + }; } +type LogstashEventsRateClusterMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' +>; + export class LogstashEventsRateClusterMetric extends LogstashClusterMetric { - constructor(opts) { + constructor(opts: LogstashEventsRateClusterMetricOptions) { super({ ...opts, derivative: true, @@ -141,8 +159,14 @@ export class LogstashEventsRateClusterMetric extends LogstashClusterMetric { } } +type LogstashMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'units' | 'derivative' | 'metricAgg' | 'format' +> & + Partial>; + export class LogstashMetric extends Metric { - constructor(opts) { + constructor(opts: LogstashMetricOptions) { super({ ...opts, app: 'logstash', @@ -158,8 +182,10 @@ export class LogstashMetric extends Metric { } } +type LogstashEventsLatencyMetricOptions = Pick; + export class LogstashEventsLatencyMetric extends LogstashMetric { - constructor(opts) { + constructor(opts: LogstashEventsLatencyMetricOptions) { super({ ...opts, format: LARGE_FLOAT, @@ -189,8 +215,7 @@ export class LogstashEventsLatencyMetric extends LogstashMetric { }, }, }; - - this.calculation = (bucket) => { + this.calculation = (bucket: object) => { const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); @@ -199,8 +224,10 @@ export class LogstashEventsLatencyMetric extends LogstashMetric { } } +type LogstashEventsRateMetricOptions = Pick; + export class LogstashEventsRateMetric extends LogstashMetric { - constructor(opts) { + constructor(opts: LogstashEventsRateMetricOptions) { super({ ...opts, derivative: true, @@ -211,8 +238,14 @@ export class LogstashEventsRateMetric extends LogstashMetric { } } +type LogstashPipelineQueueSizeMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'units' +> & + Partial>; + export class LogstashPipelineQueueSizeMetric extends LogstashMetric { - constructor(opts) { + constructor(opts: LogstashPipelineQueueSizeMetricOptions) { super({ ...opts }); this.dateHistogramSubAggs = { @@ -242,19 +275,32 @@ export class LogstashPipelineQueueSizeMetric extends LogstashMetric { }, }, }; - - this.calculation = (bucket) => _.get(bucket, 'pipelines.total_queue_size_for_node.value'); + this.calculation = (bucket: object) => { + return _.get(bucket, 'pipelines.total_queue_size_for_node.value'); + }; } } +type LogstashPipelineThroughputMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'units' +> & + Partial> & { + mbField?: string; + }; + export class LogstashPipelineThroughputMetric extends LogstashMetric { - constructor(opts) { + constructor(opts: LogstashPipelineThroughputMetricOptions) { super({ ...opts, derivative: true, }); - this.getDateHistogramSubAggs = ({ pipeline }) => { + this.getDateHistogramSubAggs = ({ + pipeline, + }: { + pipeline: { uuids: string[]; id: string }; + }) => { return { metric_deriv: { derivative: { @@ -336,15 +382,30 @@ export class LogstashPipelineThroughputMetric extends LogstashMetric { } } +type LogstashPipelineNodeCountMetricOptions = Pick< + MetricOptions, + 'field' | 'label' | 'description' | 'format' | 'units' +> & + Partial>; + export class LogstashPipelineNodeCountMetric extends LogstashMetric { - constructor(opts) { + constructor(opts: LogstashPipelineNodeCountMetricOptions) { super({ ...opts, derivative: false, }); - this.getDateHistogramSubAggs = ({ pageOfPipelines }) => { - const termAggExtras = {}; + this.getDateHistogramSubAggs = ({ + pageOfPipelines, + }: { + pageOfPipelines: Array<{ id: string }>; + }) => { + const termAggExtras: { + include: string[]; + } = { + include: [], + }; + if (pageOfPipelines) { termAggExtras.include = pageOfPipelines.map((pipeline) => pipeline.id); } @@ -404,14 +465,14 @@ export class LogstashPipelineNodeCountMetric extends LogstashMetric { }; }; - this.calculation = (bucket) => { - const pipelineNodesCounts = {}; + this.calculation = (bucket: object) => { + const pipelineNodesCounts: any = {}; const legacyPipelineBuckets = _.get(bucket, 'pipelines_nested.by_pipeline_id.buckets', []); const mbPiplineBuckets = _.get(bucket, 'pipelines_mb_nested.by_pipeline_id.buckets', []); const pipelineBuckets = legacyPipelineBuckets.length ? legacyPipelineBuckets : mbPiplineBuckets; - pipelineBuckets.forEach((pipelineBucket) => { + pipelineBuckets.forEach((pipelineBucket: any) => { pipelineNodesCounts[pipelineBucket.key] = _.get(pipelineBucket, 'to_root.node_count.value'); }); diff --git a/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.ts similarity index 100% rename from x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.ts index a140cd7bcc370..87024a6de86ab 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/logstash/metrics.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import { QuotaMetric } from '../classes'; import { LogstashEventsRateClusterMetric, @@ -22,7 +23,6 @@ import { SMALL_BYTES, LARGE_ABBREVIATED, } from '../../../../common/formatting'; -import { i18n } from '@kbn/i18n'; const instanceSystemLoadTitle = i18n.translate( 'xpack.monitoring.metrics.logstash.systemLoadTitle', diff --git a/x-pack/plugins/monitoring/server/lib/metrics/metrics.js b/x-pack/plugins/monitoring/server/lib/metrics/metrics.ts similarity index 84% rename from x-pack/plugins/monitoring/server/lib/metrics/metrics.js rename to x-pack/plugins/monitoring/server/lib/metrics/metrics.ts index 58cc61f6bbdc5..cb787d10c7cb8 100644 --- a/x-pack/plugins/monitoring/server/lib/metrics/metrics.js +++ b/x-pack/plugins/monitoring/server/lib/metrics/metrics.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { Metric } from './classes/metric'; import { metrics as elasticsearchMetrics } from './elasticsearch/metrics'; import { metrics as kibanaMetrics } from './kibana/metrics'; import { metrics as logstashMetrics } from './logstash/metrics'; @@ -12,7 +13,9 @@ import { metrics as beatsMetrics } from './beats/metrics'; import { metrics as apmMetrics } from './apm/metrics'; import { metrics as entSearchMetrics } from './enterprise_search/metrics'; -export const metrics = { +export type { Metric } from './classes/metric'; + +export const metrics: { [key: string]: Metric } = { ...elasticsearchMetrics, ...kibanaMetrics, ...logstashMetrics, diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 505c8cfe79c03..1887764f6f72d 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -27,7 +27,8 @@ "inspector", "ruleRegistry", "timelines", - "triggersActionsUi" + "triggersActionsUi", + "inspector" ], "ui": true, "server": true, diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_datepicker/index.test.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx similarity index 51% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/rum_datepicker/index.test.tsx rename to x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx index afb0e9ef37d51..53324e7df3af2 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_datepicker/index.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/date_picker.test.tsx @@ -9,86 +9,89 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import { waitFor } from '@testing-library/react'; import { mount } from 'enzyme'; import { createMemoryHistory, MemoryHistory } from 'history'; -import React, { ReactNode } from 'react'; +import React from 'react'; +import { Router, useLocation } from 'react-router-dom'; import qs from 'query-string'; -import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; -import { UrlParamsContext } from '../../../../context/url_params_context/url_params_context'; -import { RumDatePicker } from './'; -import { useLocation } from 'react-router-dom'; +import { DatePicker } from './'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { of } from 'rxjs'; let history: MemoryHistory; -let mockHistoryPush: jest.SpyInstance; -let mockHistoryReplace: jest.SpyInstance; const mockRefreshTimeRange = jest.fn(); +let mockHistoryPush: jest.SpyInstance; +let mockHistoryReplace: jest.SpyInstance; -function MockUrlParamsProvider({ children }: { children: ReactNode }) { +function DatePickerWrapper() { const location = useLocation(); - const urlParams = qs.parse(location.search, { - parseBooleans: true, + const { rangeFrom, rangeTo, refreshInterval, refreshPaused } = qs.parse(location.search, { parseNumbers: true, - }); + parseBooleans: true, + }) as { + rangeFrom?: string; + rangeTo?: string; + refreshInterval?: number; + refreshPaused?: boolean; + }; return ( - ); } -function mountDatePicker( - params: { - rangeFrom?: string; - rangeTo?: string; - refreshPaused?: boolean; - refreshInterval?: number; - } = {} -) { +function mountDatePicker(initialParams: { + rangeFrom?: string; + rangeTo?: string; + refreshInterval?: number; + refreshPaused?: boolean; +}) { const setTimeSpy = jest.fn(); const getTimeSpy = jest.fn().mockReturnValue({}); history = createMemoryHistory({ - initialEntries: [`/?${qs.stringify(params)}`], + initialEntries: [`/?${qs.stringify(initialParams)}`], }); - jest.spyOn(console, 'error').mockImplementation(() => null); mockHistoryPush = jest.spyOn(history, 'push'); mockHistoryReplace = jest.spyOn(history, 'replace'); const wrapper = mount( - + - - - - + uiSettings: { + get: (key: string) => [], + get$: (key: string) => of(true), + }, + }} + > + + + ); return { wrapper, setTimeSpy, getTimeSpy }; } -describe('RumDatePicker', () => { +describe('DatePicker', () => { + beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation(() => null); + }); + afterAll(() => { jest.restoreAllMocks(); }); @@ -97,40 +100,13 @@ describe('RumDatePicker', () => { jest.resetAllMocks(); }); - it('sets default query params in the URL', () => { - mountDatePicker(); - expect(mockHistoryReplace).toHaveBeenCalledTimes(1); - expect(mockHistoryReplace).toHaveBeenCalledWith( - expect.objectContaining({ - search: 'rangeFrom=now-15m&rangeTo=now', - }) - ); - }); - - it('adds missing `rangeFrom` to url', () => { - mountDatePicker({ rangeTo: 'now', refreshInterval: 5000 }); - expect(mockHistoryReplace).toHaveBeenCalledTimes(1); - expect(mockHistoryReplace).toHaveBeenCalledWith( - expect.objectContaining({ - search: 'rangeFrom=now-15m&rangeTo=now&refreshInterval=5000', - }) - ); - }); - - it('does not set default query params in the URL when values already defined', () => { - mountDatePicker({ - rangeFrom: 'now-1d', + it('updates the URL when the date range changes', () => { + const { wrapper } = mountDatePicker({ + rangeFrom: 'now-15m', rangeTo: 'now', - refreshPaused: false, - refreshInterval: 5000, }); - expect(mockHistoryReplace).toHaveBeenCalledTimes(0); - }); - - it('updates the URL when the date range changes', () => { - const { wrapper } = mountDatePicker(); - expect(mockHistoryReplace).toHaveBeenCalledTimes(1); + expect(mockHistoryReplace).toHaveBeenCalledTimes(0); wrapper.find(EuiSuperDatePicker).props().onTimeChange({ start: 'now-90m', @@ -149,11 +125,13 @@ describe('RumDatePicker', () => { it('enables auto-refresh when refreshPaused is false', async () => { jest.useFakeTimers(); const { wrapper } = mountDatePicker({ + rangeFrom: 'now-15m', + rangeTo: 'now', refreshPaused: false, refreshInterval: 1000, }); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); - jest.advanceTimersByTime(2500); + jest.advanceTimersByTime(1000); await waitFor(() => {}); expect(mockRefreshTimeRange).toHaveBeenCalled(); wrapper.unmount(); @@ -161,7 +139,12 @@ describe('RumDatePicker', () => { it('disables auto-refresh when refreshPaused is true', async () => { jest.useFakeTimers(); - mountDatePicker({ refreshPaused: true, refreshInterval: 1000 }); + mountDatePicker({ + rangeFrom: 'now-15m', + rangeTo: 'now', + refreshPaused: true, + refreshInterval: 1000, + }); expect(mockRefreshTimeRange).not.toHaveBeenCalled(); jest.advanceTimersByTime(1000); await waitFor(() => {}); @@ -184,23 +167,4 @@ describe('RumDatePicker', () => { expect(mockHistoryReplace).toHaveBeenCalledTimes(0); }); }); - - describe('if `rangeFrom` is missing from the urlParams', () => { - beforeEach(() => { - mountDatePicker({ rangeTo: 'now-5m' }); - }); - - it('updates the url with the default `rangeFrom` ', async () => { - expect(mockHistoryReplace).toHaveBeenCalledTimes(1); - expect(mockHistoryReplace.mock.calls[0][0].search).toContain( - 'rangeFrom=now-15m' - ); - }); - - it('preserves `rangeTo`', () => { - expect(mockHistoryReplace.mock.calls[0][0].search).toContain( - 'rangeTo=now-5m' - ); - }); - }); }); diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx index c64d4353e613b..ac32ad31d77b4 100644 --- a/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/date_picker/index.tsx @@ -8,44 +8,40 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import React, { useEffect } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import { useHasData } from '../../../hooks/use_has_data'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { UI_SETTINGS, useKibanaUISettings } from '../../../hooks/use_kibana_ui_settings'; -import { usePluginContext } from '../../../hooks/use_plugin_context'; import { fromQuery, toQuery } from '../../../utils/url'; +import { TimePickerQuickRange } from './typings'; +import { ObservabilityPublicPluginsStart } from '../../../plugin'; -export interface TimePickerTime { - from: string; - to: string; +export interface DatePickerProps { + rangeFrom?: string; + rangeTo?: string; + refreshPaused?: boolean; + refreshInterval?: number; + onTimeRangeRefresh?: (range: { start: string; end: string }) => void; } -export interface TimePickerQuickRange extends TimePickerTime { - display: string; -} - -export interface TimePickerRefreshInterval { - pause: boolean; - value: number; -} - -interface Props { - rangeFrom: string; - rangeTo: string; - refreshPaused: boolean; - refreshInterval: number; -} - -export function DatePicker({ rangeFrom, rangeTo, refreshPaused, refreshInterval }: Props) { +export function DatePicker({ + rangeFrom, + rangeTo, + refreshPaused, + refreshInterval, + onTimeRangeRefresh, +}: DatePickerProps) { const location = useLocation(); const history = useHistory(); - const { plugins } = usePluginContext(); - const { onRefreshTimeRange } = useHasData(); + const { data } = useKibana().services; useEffect(() => { - plugins.data.query.timefilter.timefilter.setTime({ - from: rangeFrom, - to: rangeTo, - }); - }, [plugins, rangeFrom, rangeTo]); + // set time if both to and from are given in the url + if (rangeFrom && rangeTo) { + data.query.timefilter.timefilter.setTime({ + from: rangeFrom, + to: rangeTo, + }); + } + }, [data, rangeFrom, rangeTo]); const timePickerQuickRanges = useKibanaUISettings( UI_SETTINGS.TIMEPICKER_QUICK_RANGES @@ -95,7 +91,10 @@ export function DatePicker({ rangeFrom, rangeTo, refreshPaused, refreshInterval refreshInterval={refreshInterval} onRefreshChange={onRefreshChange} commonlyUsedRanges={commonlyUsedRanges} - onRefresh={onRefreshTimeRange} + onRefresh={onTimeRangeRefresh} /> ); } + +// eslint-disable-next-line import/no-default-export +export default DatePicker; diff --git a/x-pack/plugins/observability/public/components/shared/date_picker/typings.ts b/x-pack/plugins/observability/public/components/shared/date_picker/typings.ts new file mode 100644 index 0000000000000..ed41716bfb78a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/date_picker/typings.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface TimePickerQuickRange { + from: string; + to: string; + display: string; +} + +export interface TimePickerRefreshInterval { + pause: boolean; + value: number; +} + +export interface TimePickerTimeDefaults { + from: string; + to: string; +} diff --git a/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx b/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx index a99187271806a..eb755200949c3 100644 --- a/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx +++ b/x-pack/plugins/observability/public/components/shared/experimental_badge.tsx @@ -13,11 +13,11 @@ export function ExperimentalBadge() { return ( ); diff --git a/x-pack/plugins/observability/public/components/shared/index.tsx b/x-pack/plugins/observability/public/components/shared/index.tsx index 03eac23062273..cdbc6ec8a9513 100644 --- a/x-pack/plugins/observability/public/components/shared/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/index.tsx @@ -9,6 +9,7 @@ import React, { lazy, Suspense } from 'react'; import { EuiLoadingSpinner } from '@elastic/eui'; import type { CoreVitalProps, HeaderMenuPortalProps } from './types'; import type { FieldValueSuggestionsProps } from './field_value_suggestions/types'; +import type { DatePickerProps } from './date_picker/index'; import type { FilterValueLabelProps } from './filter_value_label/filter_value_label'; import type { SelectableUrlListProps } from './exploratory_view/components/url_search/selectable_url_list'; import type { ExploratoryViewPageProps } from './exploratory_view/index'; @@ -76,3 +77,13 @@ export function ExploratoryView(props: ExploratoryViewPageProps) { ); } + +const DatePickerLazy = lazy(() => import('./date_picker/index')); + +export function DatePicker(props: DatePickerProps) { + return ( + }> + + + ); +} diff --git a/x-pack/plugins/observability/public/hooks/use_query_params.ts b/x-pack/plugins/observability/public/hooks/use_query_params.ts index e9d1572cfaae9..9fcbe70b5a9ef 100644 --- a/x-pack/plugins/observability/public/hooks/use_query_params.ts +++ b/x-pack/plugins/observability/public/hooks/use_query_params.ts @@ -9,15 +9,17 @@ import { useLocation } from 'react-router-dom'; import { useMemo } from 'react'; import { parse } from 'query-string'; import { UI_SETTINGS, useKibanaUISettings } from './use_kibana_ui_settings'; -import { TimePickerTime } from '../components/shared/date_picker'; import { getAbsoluteTime } from '../utils/date'; +import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings'; const getParsedParams = (search: string) => { return search ? parse(search[0] === '?' ? search.slice(1) : search, { sort: false }) : {}; }; export function useQueryParams() { - const { from, to } = useKibanaUISettings(UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS); + const { from, to } = useKibanaUISettings( + UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS + ); const { rangeFrom, rangeTo } = getParsedParams(useLocation().search); diff --git a/x-pack/plugins/observability/public/hooks/use_time_range.ts b/x-pack/plugins/observability/public/hooks/use_time_range.ts index bfd5a00c3faa1..aa120d6968bfb 100644 --- a/x-pack/plugins/observability/public/hooks/use_time_range.ts +++ b/x-pack/plugins/observability/public/hooks/use_time_range.ts @@ -7,7 +7,7 @@ import { parse } from 'query-string'; import { useLocation } from 'react-router-dom'; -import { TimePickerTime } from '../components/shared/date_picker'; +import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings'; import { getAbsoluteTime } from '../utils/date'; import { UI_SETTINGS, useKibanaUISettings } from './use_kibana_ui_settings'; import { usePluginContext } from './use_plugin_context'; @@ -19,7 +19,7 @@ const getParsedParams = (search: string) => { export function useTimeRange() { const { plugins } = usePluginContext(); - const timePickerTimeDefaults = useKibanaUISettings( + const timePickerTimeDefaults = useKibanaUISettings( UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS ); diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts index 550969191fa87..08bdace817c15 100644 --- a/x-pack/plugins/observability/public/index.ts +++ b/x-pack/plugins/observability/public/index.ts @@ -57,6 +57,7 @@ export { FilterValueLabel, SelectableUrlList, ExploratoryView, + DatePicker, } from './components/shared/'; export type { LazyObservabilityPageTemplateProps } from './components/shared'; @@ -106,3 +107,4 @@ export { RECORDS_FIELD, } from './components/shared/exploratory_view/configurations/constants'; export { ExploratoryViewContextProvider } from './components/shared/exploratory_view/contexts/exploratory_view_config'; +export { fromQuery, toQuery } from './utils/url'; diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alerts_disclaimer.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_disclaimer.tsx index 4b465a1091965..9a07c1b3cd605 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alerts_disclaimer.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_disclaimer.tsx @@ -35,14 +35,13 @@ export function AlertsDisclaimer() { diff --git a/x-pack/plugins/osquery/cypress/tasks/integrations.ts b/x-pack/plugins/osquery/cypress/tasks/integrations.ts index 673f2091760a6..ebf8668483d1c 100644 --- a/x-pack/plugins/osquery/cypress/tasks/integrations.ts +++ b/x-pack/plugins/osquery/cypress/tasks/integrations.ts @@ -13,10 +13,10 @@ import { DATA_COLLECTION_SETUP_STEP, } from '../screens/integrations'; -export const addIntegration = (agent = 'Default fleet') => { +export const addIntegration = (agentPolicy = 'Default Fleet Server policy') => { cy.getBySel(ADD_POLICY_BTN).click(); cy.getBySel(DATA_COLLECTION_SETUP_STEP).find('.euiLoadingSpinner').should('not.exist'); - cy.getBySel('comboBoxInput').click().type(`${agent} {downArrow} {enter}`); + cy.getBySel('agentPolicySelect').select(agentPolicy); cy.getBySel(CREATE_PACKAGE_POLICY_SAVE_BTN).click(); // sometimes agent is assigned to default policy, sometimes not closeModalIfVisible(); diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json index a499b2b75ee68..63f75b233f73b 100644 --- a/x-pack/plugins/osquery/kibana.json +++ b/x-pack/plugins/osquery/kibana.json @@ -8,7 +8,7 @@ }, "kibanaVersion": "kibana", "optionalPlugins": ["fleet", "home", "usageCollection", "lens"], - "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact"], + "requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lens"], "requiredPlugins": [ "actions", "data", diff --git a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx index 2bfe75e2833aa..8b8d361611a2d 100644 --- a/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx +++ b/x-pack/plugins/osquery/public/packs/pack_queries_status_table.tsx @@ -30,6 +30,7 @@ import { PieVisualizationState, TermsIndexPatternColumn, } from '../../../lens/public'; +import { DOCUMENT_FIELD_NAME as RECORDS_FIELD } from '../../../lens/common/constants'; import { FilterStateStore, DataView } from '../../../../../src/plugins/data/common'; import { useKibana } from '../common/lib/kibana'; import { OsqueryManagerPackagePolicyInputStream } from '../../common/types'; @@ -91,7 +92,7 @@ function getLensAttributes( }, } as TermsIndexPatternColumn, 'ed999e9d-204c-465b-897f-fe1a125b39ed': { - sourceField: 'Records', + sourceField: RECORDS_FIELD, isBucketed: false, dataType: 'number', scale: 'ratio', diff --git a/x-pack/plugins/painless_lab/kibana.json b/x-pack/plugins/painless_lab/kibana.json index 1f59bf30bb761..6032d5ccc0d8d 100644 --- a/x-pack/plugins/painless_lab/kibana.json +++ b/x-pack/plugins/painless_lab/kibana.json @@ -1,6 +1,6 @@ { "id": "painlessLab", - "version": "8.1.0", + "version": "8.2.0", "kibanaVersion": "kibana", "owner": { "name": "Stack Management", diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.tsx b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.tsx index a47e6c023a161..a4debdc6ae964 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.tsx +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { RemoteClusterAdd } from '../../../public/application/sections'; import { createRemoteClustersStore } from '../../../public/application/store'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.ts b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.ts index 0727bc0c9ba2d..28332f71ca6ac 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.ts +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.ts @@ -6,7 +6,7 @@ */ import { SinonFakeServer } from 'sinon'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { setupEnvironment, RemoteClustersActions } from '../helpers'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.tsx b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.tsx index 2259396bf33f2..86f75c12424e7 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.tsx +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBedConfig } from '@kbn/test-jest-helpers'; import React from 'react'; import { RemoteClusterEdit } from '../../../public/application/sections'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx index 2913de94bc2dd..47aac3f924b96 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { RemoteClusterForm } from '../../../public/application/sections/components/remote_cluster_form'; import { RemoteClustersActions, setupEnvironment } from '../helpers'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts index 4cfe1fb41a835..b2a7e2d90dc64 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { nextTick, getRandomString, findTestSubject } from '@kbn/test/jest'; +export { nextTick, getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { setupEnvironment } from './setup_environment'; export type { RemoteClustersActions } from './remote_clusters_actions'; export { createRemoteClustersActions } from './remote_clusters_actions'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_actions.ts b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_actions.ts index ba0c424793838..3a2d4be3e060d 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_actions.ts +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/remote_clusters_actions.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed } from '@kbn/test/jest'; +import { TestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; export interface RemoteClustersActions { docsButtonExists: () => boolean; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js index d05ddbd1800e4..9aeef5d684f3f 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed, findTestSubject } from '@kbn/test/jest'; +import { registerTestBed, findTestSubject } from '@kbn/test-jest-helpers'; import { RemoteClusterList } from '../../../public/application/sections/remote_cluster_list'; import { createRemoteClustersStore } from '../../../public/application/store'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js index 209c224618f78..a6987fa19d1ee 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js @@ -77,6 +77,51 @@ describe('', () => { }); }); + describe('can search', () => { + let table; + let component; + let form; + + const remoteClusters = [ + { + name: 'simple_remote_cluster', + seeds: ['127.0.0.1:2000', '127.0.0.2:3000'], + }, + { + name: 'remote_cluster_with_proxy', + proxyAddress: '192.168.0.1:80', + mode: PROXY_MODE, + }, + ]; + + beforeEach(async () => { + httpRequestsMockHelpers.setLoadRemoteClustersResponse(remoteClusters); + + await act(async () => { + ({ table, component, form } = setup()); + }); + + component.update(); + }); + + test('without any search params it should show all clusters', () => { + const { tableCellsValues } = table.getMetaData('remoteClusterListTable'); + expect(tableCellsValues.length).toBe(2); + }); + + test('search by seed works', () => { + form.setInputValue('remoteClusterSearch', 'simple'); + const { tableCellsValues } = table.getMetaData('remoteClusterListTable'); + expect(tableCellsValues.length).toBe(1); + }); + + test('search by proxyAddress works', () => { + form.setInputValue('remoteClusterSearch', 'proxy'); + const { tableCellsValues } = table.getMetaData('remoteClusterListTable'); + expect(tableCellsValues.length).toBe(1); + }); + }); + describe('when there are multiple pages of remote clusters', () => { let table; let actions; @@ -91,10 +136,18 @@ describe('', () => { ]; for (let i = 0; i < 29; i++) { - remoteClusters.push({ - name: `name${i}`, - seeds: [], - }); + if (i % 2 === 0) { + remoteClusters.push({ + name: `cluster-${i}`, + seeds: [], + }); + } else { + remoteClusters.push({ + name: `cluster_with_proxy-${i}`, + proxyAddress: `127.0.0.1:10${i}`, + mode: PROXY_MODE, + }); + } } beforeEach(async () => { diff --git a/x-pack/plugins/remote_clusters/common/constants.ts b/x-pack/plugins/remote_clusters/common/constants.ts index 072d6d437b8b9..86910640191e3 100644 --- a/x-pack/plugins/remote_clusters/common/constants.ts +++ b/x-pack/plugins/remote_clusters/common/constants.ts @@ -20,7 +20,7 @@ export const PLUGIN = { }, }; -export const MAJOR_VERSION = '8.1.0'; +export const MAJOR_VERSION = '8.2.0'; export const API_BASE_PATH = '/api/remote_clusters'; diff --git a/x-pack/plugins/remote_clusters/fixtures/remote_cluster.js b/x-pack/plugins/remote_clusters/fixtures/remote_cluster.js index d6bbe99f7b5d7..d750636b76234 100644 --- a/x-pack/plugins/remote_clusters/fixtures/remote_cluster.js +++ b/x-pack/plugins/remote_clusters/fixtures/remote_cluster.js @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString } from '@kbn/test/jest'; +import { getRandomString } from '@kbn/test-jest-helpers'; import { SNIFF_MODE } from '../common/constants'; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js index 0b3a272f5bdc4..b53c735ef9fbb 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/remote_cluster_list/remote_cluster_table/remote_cluster_table.js @@ -31,13 +31,22 @@ const getFilteredClusters = (clusters, queryText) => { const normalizedSearchText = queryText.toLowerCase(); return clusters.filter((cluster) => { - const { name, seeds } = cluster; + const { name, seeds, proxyAddress } = cluster; const normalizedName = name.toLowerCase(); + if (normalizedName.toLowerCase().includes(normalizedSearchText)) { return true; } - return seeds.some((seed) => seed.includes(normalizedSearchText)); + if (proxyAddress && proxyAddress.toLowerCase().includes(normalizedSearchText)) { + return true; + } + + if (seeds) { + return seeds.some((seed) => seed.includes(normalizedSearchText)); + } + + return false; }); } else { return clusters; @@ -81,6 +90,11 @@ export class RemoteClusterTable extends Component { } onSearch = ({ query }) => { + // There's no need to update the state if there arent any search params + if (!query) { + return; + } + const { clusters } = this.props; const { text } = query; diff --git a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx index 3030994c5ed32..3200a3305ebcd 100644 --- a/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx +++ b/x-pack/plugins/reporting/public/management/__test__/report_listing.test.helpers.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { Observable } from 'rxjs'; import { SerializableRecord } from '@kbn/utility-types'; diff --git a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx index ef3e9940238c1..82dfe8e167dfa 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/reporting_panel_content.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { httpServiceMock, notificationServiceMock, diff --git a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts index dd3f93ad2c0c6..dbd11943f70e4 100644 --- a/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts +++ b/x-pack/plugins/reporting/server/lib/tasks/execute_report.ts @@ -358,13 +358,13 @@ export class ExecuteReportTask implements ReportingTask { stream.end(); - eventLog.logExecutionComplete({ byteSize: stream.bytesWritten }); - await promisify(finished)(stream, { readable: false }); report._seq_no = stream.getSeqNo()!; report._primary_term = stream.getPrimaryTerm()!; + eventLog.logExecutionComplete({ byteSize: stream.bytesWritten }); + if (output) { this.logger.debug(`Job output size: ${stream.bytesWritten} bytes.`); report = await this._completeJob(report, { diff --git a/x-pack/plugins/rollup/fixtures/job.js b/x-pack/plugins/rollup/fixtures/job.js index 53c63e14c96cc..d0086d134c60b 100644 --- a/x-pack/plugins/rollup/fixtures/job.js +++ b/x-pack/plugins/rollup/fixtures/job.js @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString } from '@kbn/test/jest'; +import { getRandomString } from '@kbn/test-jest-helpers'; const initialValues = { dateHistogramField: 'timestamp', diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js index e1f9ec2b3a315..fa63639ef4d06 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.test.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { getJob } from '../../../../../fixtures'; import { rollupJobsStore } from '../../../store'; import { DetailPanel } from './detail_panel'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js index b2c738a033b3c..ba82e3b9b9f5e 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_list.test.js @@ -6,7 +6,7 @@ */ import React from 'react'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { rollupJobsStore } from '../../store'; import { JobList } from './job_list'; diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js index d52f3fa35a544..d9fbf796c64d6 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_list/job_table/job_table.test.js @@ -7,7 +7,7 @@ import { Pager } from '@elastic/eui'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { getJobs, jobCount } from '../../../../../fixtures'; import { rollupJobsStore } from '../../../store'; import { JobTable } from './job_table'; diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/index.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/index.js index ef5a191b994c6..c897eed6c477e 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/index.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/index.js @@ -11,7 +11,7 @@ import { setup as jobCreateSetup } from './job_create.helpers'; import { setup as jobListSetup } from './job_list.helpers'; import { setup as jobCloneSetup } from './job_clone.helpers'; -export { getRandomString, findTestSubject } from '@kbn/test/jest'; +export { getRandomString, findTestSubject } from '@kbn/test-jest-helpers'; export { wrapComponent } from './setup_context'; diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js index d81bdd3093300..613980868edbe 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_clone.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { createRollupJobsStore } from '../../../crud_app/store'; import { JobCreate } from '../../../crud_app/sections'; import { JOB_TO_CLONE } from './constants'; diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js index 36550d904fa39..92eb2bf62cc2f 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_create.helpers.js @@ -7,7 +7,7 @@ import { act } from 'react-dom/test-utils'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { rollupJobsStore } from '../../../crud_app/store'; import { JobCreate } from '../../../crud_app/sections'; diff --git a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js index f4c59334b8875..5f64e0cfe34bf 100644 --- a/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js +++ b/x-pack/plugins/rollup/public/test/client_integration/helpers/job_list.helpers.js @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { registerRouter } from '../../../crud_app/services'; import { createRollupJobsStore } from '../../../crud_app/store'; import { JobList } from '../../../crud_app/sections/job_list'; diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts new file mode 100644 index 0000000000000..d3ed9ff937a0c --- /dev/null +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/experimental_rule_field_map.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { experimentalRuleFieldMap } from './experimental_rule_field_map'; + +// This test purely exists to see what the resultant mappings are and +// make it obvious when some dependency results in the mappings changing +it('matches snapshot', () => { + expect(experimentalRuleFieldMap).toMatchInlineSnapshot(` + Object { + "kibana.alert.evaluation.threshold": Object { + "scaling_factor": 100, + "type": "scaled_float", + }, + "kibana.alert.evaluation.value": Object { + "scaling_factor": 100, + "type": "scaled_float", + }, + "kibana.alert.instance.id": Object { + "required": true, + "type": "keyword", + }, + } + `); +}); diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts new file mode 100644 index 0000000000000..916174f225415 --- /dev/null +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { technicalRuleFieldMap } from './technical_rule_field_map'; + +// This test purely exists to see what the resultant mappings are and +// make it obvious when some dependency results in the mappings changing +it('matches snapshot', () => { + expect(technicalRuleFieldMap).toMatchInlineSnapshot(` + Object { + "@timestamp": Object { + "array": false, + "required": true, + "type": "date", + }, + "ecs.version": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "event.action": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "event.kind": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.action_group": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.duration.us": Object { + "type": "long", + }, + "kibana.alert.end": Object { + "type": "date", + }, + "kibana.alert.reason": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.risk_score": Object { + "array": false, + "required": false, + "type": "float", + }, + "kibana.alert.rule.author": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.category": Object { + "array": false, + "required": true, + "type": "keyword", + }, + "kibana.alert.rule.consumer": Object { + "required": true, + "type": "keyword", + }, + "kibana.alert.rule.created_at": Object { + "array": false, + "required": false, + "type": "date", + }, + "kibana.alert.rule.created_by": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.description": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.enabled": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.execution.uuid": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.from": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.interval": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.license": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.name": Object { + "array": false, + "required": true, + "type": "keyword", + }, + "kibana.alert.rule.note": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.parameters": Object { + "ignore_above": 4096, + "type": "flattened", + }, + "kibana.alert.rule.producer": Object { + "required": true, + "type": "keyword", + }, + "kibana.alert.rule.references": Object { + "array": true, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.rule_id": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.rule_name_override": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.rule_type_id": Object { + "required": true, + "type": "keyword", + }, + "kibana.alert.rule.tags": Object { + "array": true, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.to": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.type": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.updated_at": Object { + "array": false, + "required": false, + "type": "date", + }, + "kibana.alert.rule.updated_by": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.rule.uuid": Object { + "array": false, + "required": true, + "type": "keyword", + }, + "kibana.alert.rule.version": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.severity": Object { + "type": "keyword", + }, + "kibana.alert.start": Object { + "type": "date", + }, + "kibana.alert.status": Object { + "required": true, + "type": "keyword", + }, + "kibana.alert.system_status": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.uuid": Object { + "required": true, + "type": "keyword", + }, + "kibana.alert.workflow_reason": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.workflow_status": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.alert.workflow_user": Object { + "array": false, + "required": false, + "type": "keyword", + }, + "kibana.space_ids": Object { + "array": true, + "required": true, + "type": "keyword", + }, + "kibana.version": Object { + "array": false, + "required": false, + "type": "version", + }, + "tags": Object { + "array": true, + "required": false, + "type": "keyword", + }, + } + `); +}); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts index af531e8ae8e12..c8660fc69c224 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts @@ -11,7 +11,7 @@ import { AlertsClientFactory, AlertsClientFactoryProps } from './alerts_client_f import { ElasticsearchClient, KibanaRequest } from 'src/core/server'; import { loggingSystemMock } from 'src/core/server/mocks'; import { securityMock } from '../../../security/server/mocks'; -import { AuditLogger } from '../../../security/server'; +import { auditLoggerMock } from '../../../security/server/audit/mocks'; import { alertingAuthorizationMock } from '../../../alerting/server/authorization/alerting_authorization.mock'; import { ruleDataServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock'; @@ -44,10 +44,7 @@ const fakeRequest = { }, } as unknown as Request; -const auditLogger = { - log: jest.fn(), - enabled: true, -} as jest.Mocked; +const auditLogger = auditLoggerMock.create(); describe('AlertsClientFactory', () => { beforeEach(() => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts index 09861278cd5d5..3f89506c0a1f4 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts @@ -17,16 +17,13 @@ import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; -import { AuditLogger } from '../../../../security/server'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); -const auditLogger = { - log: jest.fn(), - enabled: true, -} as jest.Mocked; +const auditLogger = auditLoggerMock.create(); const alertsClientParams: jest.Mocked = { logger: loggingSystemMock.create().get(), diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts index bfff95b5d601b..285d318749786 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts @@ -16,16 +16,13 @@ import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; -import { AuditLogger } from '../../../../security/server'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); -const auditLogger = { - log: jest.fn(), - enabled: true, -} as jest.Mocked; +const auditLogger = auditLoggerMock.create(); const alertsClientParams: jest.Mocked = { logger: loggingSystemMock.create().get(), diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index 0c74cc1463410..acc6e37099fdd 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -17,16 +17,13 @@ import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; -import { AuditLogger } from '../../../../security/server'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); -const auditLogger = { - log: jest.fn(), - enabled: true, -} as jest.Mocked; +const auditLogger = auditLoggerMock.create(); const alertsClientParams: jest.Mocked = { logger: loggingSystemMock.create().get(), diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index 0dcfc602bc281..2778d4da0f089 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -16,16 +16,13 @@ import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { alertingAuthorizationMock } from '../../../../alerting/server/authorization/alerting_authorization.mock'; -import { AuditLogger } from '../../../../security/server'; +import { auditLoggerMock } from '../../../../security/server/audit/mocks'; import { AlertingAuthorizationEntity } from '../../../../alerting/server'; import { ruleDataServiceMock } from '../../rule_data_plugin_service/rule_data_plugin_service.mock'; const alertingAuthMock = alertingAuthorizationMock.create(); const esClientMock = elasticsearchClientMock.createElasticsearchClient(); -const auditLogger = { - log: jest.fn(), - enabled: true, -} as jest.Mocked; +const auditLogger = auditLoggerMock.create(); const alertsClientParams: jest.Mocked = { logger: loggingSystemMock.create().get(), diff --git a/x-pack/plugins/runtime_fields/public/test_utils.ts b/x-pack/plugins/runtime_fields/public/test_utils.ts index 1c052cd666e56..637ef68b1726d 100644 --- a/x-pack/plugins/runtime_fields/public/test_utils.ts +++ b/x-pack/plugins/runtime_fields/public/test_utils.ts @@ -5,5 +5,5 @@ * 2.0. */ -export type { TestBed } from '@kbn/test/jest'; -export { registerTestBed } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; +export { registerTestBed } from '@kbn/test-jest-helpers'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/empty_tree_placeholder/empty_tree_placeholder.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/empty_tree_placeholder/empty_tree_placeholder.test.tsx index 91fda67046f93..007786722ca52 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/empty_tree_placeholder/empty_tree_placeholder.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/empty_tree_placeholder/empty_tree_placeholder.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { EmptyTreePlaceHolder } from '.'; describe('EmptyTreePlaceholder', () => { diff --git a/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx index ef93896e7fe3e..ac8df87086e57 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { HighlightDetailsFlyout, Props } from '.'; describe('Highlight Details Flyout', () => { diff --git a/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice/license_warning_notice.test.ts b/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice/license_warning_notice.test.ts index 27109c0527032..a4a1f8864e883 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice/license_warning_notice.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/license_warning_notice/license_warning_notice.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { LicenseWarningNotice } from './license_warning_notice'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_loading_placeholder/profile_loading_placeholder.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_loading_placeholder/profile_loading_placeholder.test.tsx index caa858c41d702..d6c7a0afbf38d 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_loading_placeholder/profile_loading_placeholder.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_loading_placeholder/profile_loading_placeholder.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { ProfileLoadingPlaceholder } from '.'; describe('Profile Loading Placeholder', () => { diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx index 8e495e7d2c49e..f92e2b2a5167c 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_query_editor/editor/editor.test.tsx @@ -8,7 +8,7 @@ import 'brace'; import 'brace/mode/json'; -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { Editor, Props } from './editor'; describe('Editor Component', () => { diff --git a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/profile_tree.test.tsx b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/profile_tree.test.tsx index fef3c2e46958f..64cd05f3fa0a5 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/profile_tree.test.tsx +++ b/x-pack/plugins/searchprofiler/public/application/components/profile_tree/__jest__/profile_tree.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { searchResponse } from './fixtures/search_response'; import { ProfileTree, Props } from '../profile_tree'; diff --git a/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs/searchprofiler_tabs.test.ts b/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs/searchprofiler_tabs.test.ts index 4cc7b0e5db966..daba54949efe8 100644 --- a/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs/searchprofiler_tabs.test.ts +++ b/x-pack/plugins/searchprofiler/public/application/components/searchprofiler_tabs/searchprofiler_tabs.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed } from '@kbn/test/jest'; +import { registerTestBed } from '@kbn/test-jest-helpers'; import { SearchProfilerTabs, Props } from './searchprofiler_tabs'; diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx index 4e9f1a6692eb9..fb73322655399 100644 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { AuthenticatedUser } from '../../common/model'; diff --git a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.test.tsx b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.test.tsx index f6fcf36a12ee6..a7a43077fbc46 100644 --- a/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.test.tsx +++ b/x-pack/plugins/security/public/authentication/access_agreement/access_agreement_page.test.tsx @@ -10,7 +10,7 @@ import { act } from '@testing-library/react'; import React from 'react'; import ReactMarkdown from 'react-markdown'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { AccessAgreementPage } from './access_agreement_page'; diff --git a/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.test.tsx b/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.test.tsx index eba918e072110..f775d158a3ec0 100644 --- a/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.test.tsx +++ b/x-pack/plugins/security/public/authentication/components/authentication_state_page/authentication_state_page.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { AuthenticationStatePage } from './authentication_state_page'; diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx index 8647edd2ba06a..d21cf1bb5677c 100644 --- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx +++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx @@ -8,7 +8,7 @@ import { EuiButton } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { LoggedOutPage } from './logged_out_page'; diff --git a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx index a9bbcaab74ab0..9de62907261c1 100644 --- a/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx +++ b/x-pack/plugins/security/public/authentication/login/components/login_form/login_form.test.tsx @@ -11,7 +11,7 @@ import type { ReactWrapper } from 'enzyme'; import React from 'react'; import ReactMarkdown from 'react-markdown'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { LoginForm, MessageType, PageMode } from './login_form'; diff --git a/x-pack/plugins/security/public/authentication/login/login_page.test.tsx b/x-pack/plugins/security/public/authentication/login/login_page.test.tsx index c01777a106490..6507355221259 100644 --- a/x-pack/plugins/security/public/authentication/login/login_page.test.tsx +++ b/x-pack/plugins/security/public/authentication/login/login_page.test.tsx @@ -10,7 +10,7 @@ import { act } from '@testing-library/react'; import { shallow } from 'enzyme'; import React from 'react'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER } from '../../../common/constants'; diff --git a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.test.tsx b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.test.tsx index 42915e841b3a7..66b2408338391 100644 --- a/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.test.tsx +++ b/x-pack/plugins/security/public/authentication/overwritten_session/overwritten_session_page.test.tsx @@ -9,7 +9,7 @@ import { EuiButton } from '@elastic/eui'; import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; diff --git a/x-pack/plugins/security/public/management/role_combo_box/role_combo_box.test.tsx b/x-pack/plugins/security/public/management/role_combo_box/role_combo_box.test.tsx index 3abcc07042e82..bde0980613b22 100644 --- a/x-pack/plugins/security/public/management/role_combo_box/role_combo_box.test.tsx +++ b/x-pack/plugins/security/public/management/role_combo_box/role_combo_box.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { RoleComboBox } from './role_combo_box'; diff --git a/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx index 17a6fb0bb84b3..55b6cbee4783a 100644 --- a/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/delete_provider/delete_provider.test.tsx @@ -9,7 +9,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import { act } from '@testing-library/react'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { RoleMapping } from '../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/role_mappings/components/section_loading/section_loading.test.tsx b/x-pack/plugins/security/public/management/role_mappings/components/section_loading/section_loading.test.tsx index e3e5929428164..92ce48cda0cd2 100644 --- a/x-pack/plugins/security/public/management/role_mappings/components/section_loading/section_loading.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/components/section_loading/section_loading.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { SectionLoading } from './section_loading'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx index af7a2fb8d5240..083b742a21fe7 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/edit_role_mapping_page.test.tsx @@ -8,11 +8,11 @@ // brace/ace uses the Worker class, which is not currently provided by JSDOM. // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. -import '@kbn/test/target_node/jest/utils/stub_web_worker'; +import '@kbn/test-jest-helpers/target_node/stub_web_worker'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx index 3e18437a52ece..dd3bd25a745ca 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { coreMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/add_role_template_button.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/add_role_template_button.test.tsx index a82b089f6630e..14cc6db062987 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/add_role_template_button.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/add_role_template_button.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { AddRoleTemplateButton } from './add_role_template_button'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.test.tsx index 367fdaadca96e..6d34dbffded8b 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_selector.test.tsx @@ -8,7 +8,7 @@ import { EuiComboBox } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { Role, RoleMapping } from '../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx index 844d7b48321c5..c8df67e4f7b98 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/role_selector/role_template_editor.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { RoleTemplateEditor } from './role_template_editor'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx index 399d2fc2d2c91..2ada7e9730cb0 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/add_rule_button.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { AllRule, FieldRule } from '../../model'; import { AddRuleButton } from './add_rule_button'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx index c81f3cc9da595..e2e41e90be824 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/field_rule_editor.test.tsx @@ -8,7 +8,7 @@ import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { FieldRule } from '../../model'; import { FieldRuleEditor } from './field_rule_editor'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx index 5a1122a88ac80..06437675a8270 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.test.tsx @@ -10,13 +10,13 @@ import 'brace/mode/json'; // brace/ace uses the Worker class, which is not currently provided by JSDOM. // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. -import '@kbn/test/target_node/jest/utils/stub_web_worker'; +import '@kbn/test-jest-helpers/target_node//stub_web_worker'; import React from 'react'; import { act } from 'react-dom/test-utils'; import type { monaco } from '@kbn/monaco'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { CodeEditorField } from 'src/plugins/kibana_react/public'; import { AllRule, AnyRule, ExceptAllRule, ExceptAnyRule, FieldRule } from '../../model'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx index c0da0ae42a726..36b1390072408 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_editor_panel.test.tsx @@ -8,12 +8,12 @@ // brace/ace uses the Worker class, which is not currently provided by JSDOM. // This is not required for the tests to pass, but it rather suppresses lengthy // warnings in the console which adds unnecessary noise to the test output. -import '@kbn/test/target_node/jest/utils/stub_web_worker'; +import '@kbn/test-jest-helpers/target_node/stub_web_worker'; import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx index 8915bef293bc2..07a458b6a48b9 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/rule_group_editor.test.tsx @@ -8,7 +8,7 @@ import { EuiContextMenuItem } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import { AllRule, AnyRule, ExceptAnyRule, FieldRule } from '../../model'; import { AddRuleButton } from './add_rule_button'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx index fc48e2e1c8c9e..9428e860acc09 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/visual_rule_editor.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { AllRule, AnyRule, ExceptAllRule, ExceptAnyRule, FieldRule } from '../../model'; import { FieldRuleEditor } from './field_rule_editor'; diff --git a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx index d9009d49b592b..3ac54cb7863fd 100644 --- a/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/role_mappings_grid/role_mappings_grid_page.test.tsx @@ -9,7 +9,7 @@ import { EuiLink } from '@elastic/eui'; import { act } from '@testing-library/react'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import type { CoreStart } from 'src/core/public'; import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.test.tsx index e37b440d2c021..65c30d0a723ea 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.test.tsx @@ -8,7 +8,7 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { CollapsiblePanel } from './collapsible_panel'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx index 1faf514e71925..32d1e4fa6bafb 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/delete_role_button.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonEmpty, EuiConfirmModal } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { DeleteRoleButton } from './delete_role_button'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index a99e08fd5a8a6..e7c3490d2ce13 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -9,7 +9,7 @@ import { act } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import type { Capabilities } from 'src/core/public'; import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.test.tsx index baeba5829562e..816e3f43c9ddc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/cluster_privileges.test.tsx @@ -8,7 +8,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { Role } from '../../../../../../common/model'; import { ClusterPrivileges } from './cluster_privileges'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx index 0ed9362956960..9585db6efda25 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { licenseMock } from '../../../../../../common/licensing/index.mock'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx index af701df39d8b1..d61fd1e17e33b 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonIcon, EuiComboBox, EuiTextArea } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.test.tsx index 03c949d90e30a..440d5132baa33 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privileges.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts index 3fab9d86e4dd3..c236bf15e3bb7 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/__fixtures__/index.ts @@ -9,7 +9,7 @@ import type { EuiButtonGroupProps, EuiCheckboxProps } from '@elastic/eui'; import { EuiAccordion, EuiButtonGroup, EuiCheckbox } from '@elastic/eui'; import type { ReactWrapper } from 'enzyme'; -import { findTestSubject } from '@kbn/test/jest'; +import { findTestSubject } from '@kbn/test-jest-helpers'; import { SubFeatureForm } from '../sub_feature_form'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx index 0cc4c4281b38f..4014a2a265abd 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table.test.tsx @@ -8,7 +8,7 @@ import { EuiAccordion } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import type { KibanaFeature, SubFeatureConfig } from '../../../../../../../../features/public'; import type { Role } from '../../../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx index cd1db2f9ce486..0503ec1bf75f9 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/feature_table_expanded_row.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import type { Role } from '../../../../../../../common/model'; import { kibanaFeatures } from '../../../../__fixtures__/kibana_features'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx index d39d7acc6c3a1..c73dbb2616d2d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table/sub_feature_form.test.tsx @@ -9,7 +9,7 @@ import { EuiButtonGroup, EuiCheckbox } from '@elastic/eui'; import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { KibanaFeature } from '../../../../../../../../features/public'; import type { Role } from '../../../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx index dcd2170325006..7052f724cd1cc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/feature_table_cell/feature_table_cell.test.tsx @@ -8,7 +8,7 @@ import { EuiIconTip } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { createFeature } from '../../../../__fixtures__/kibana_features'; import { SecuredFeature } from '../../../../model'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts index 6f5c729784bb1..3a70ff5713bd9 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/__fixtures__/index.ts @@ -8,7 +8,7 @@ import { EuiTableRow } from '@elastic/eui'; import type { ReactWrapper } from 'enzyme'; -import { findTestSubject } from '@kbn/test/jest'; +import { findTestSubject } from '@kbn/test-jest-helpers'; import type { Role, RoleKibanaPrivilege } from '../../../../../../../../common/model'; import { FeatureTableCell } from '../../feature_table_cell'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary.test.tsx index b23566019b31a..928a9f5d28fea 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { spacesManagerMock } from '../../../../../../../../spaces/public/spaces_manager/mocks'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx index 93ed2d000bb0e..0a3620aaa4114 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/privilege_summary_table.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { spacesManagerMock } from '../../../../../../../../spaces/public/spaces_manager/mocks'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.test.tsx index 77c8cdd69ec44..e3746e7fb1051 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/privilege_summary/space_column_header.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { SpaceAvatarInternal } from '../../../../../../../../spaces/public/space_avatar/space_avatar_internal'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx index 8aa2b77f8fc12..c4400abba2da2 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_display.test.tsx @@ -8,7 +8,7 @@ import { EuiText } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { PrivilegeDisplay } from './privilege_display'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index 7976d76ff8d86..8eaae663e7e80 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonGroup } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import type { Space } from '../../../../../../../../spaces/public'; import type { Role } from '../../../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx index 6f00df3a4ee7b..9b86ad54c4d35 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/privilege_space_table.test.tsx @@ -9,7 +9,7 @@ import { EuiBadge, EuiInMemoryTable } from '@elastic/eui'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { KibanaFeature } from '../../../../../../../../features/public'; import type { Role, RoleKibanaPrivilege } from '../../../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx index ebc82f28f5426..beaaf783f2f4d 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { kibanaFeatures } from '../../../../__fixtures__/kibana_features'; import { createKibanaPrivileges } from '../../../../__fixtures__/kibana_privileges'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx index 031df26eb38f7..572822db3ca2a 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.test.tsx @@ -15,7 +15,7 @@ import { import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { Space } from '../../../../../../spaces/public'; diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx index aa507cf823eff..7041a61a850d0 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/roles_grid/roles_grid_page.test.tsx @@ -9,7 +9,7 @@ import { EuiBasicTable, EuiIcon } from '@elastic/eui'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import type { PublicMethodsOf } from '@kbn/utility-types'; import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx index 3d8c95d52f505..cf1959f8a389f 100644 --- a/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx +++ b/x-pack/plugins/security/public/management/users/components/change_password_form/change_password_form.test.tsx @@ -9,7 +9,7 @@ import { EuiFieldPassword } from '@elastic/eui'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { User } from '../../../../../common/model'; diff --git a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx index 5ce22b42e624f..2668050645f33 100644 --- a/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx +++ b/x-pack/plugins/security/public/management/users/components/confirm_delete_users/confirm_delete_users.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { userAPIClientMock } from '../../index.mock'; diff --git a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx index b340a915bc054..898fec5709c56 100644 --- a/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/users_grid/users_grid_page.test.tsx @@ -10,7 +10,7 @@ import type { ReactWrapper } from 'enzyme'; import type { LocationDescriptorObject } from 'history'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import type { CoreStart, ScopedHistory } from 'src/core/public'; import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx index adaca23be5dae..be74cca3d2671 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.test.tsx @@ -9,7 +9,7 @@ import { EuiContextMenuItem, EuiHeaderSectionItemButton, EuiPopover } from '@ela import React from 'react'; import { BehaviorSubject } from 'rxjs'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import type { AuthenticatedUser } from '../../common/model'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index f19b88dd91c34..21352f16d2354 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -7,7 +7,7 @@ import { BehaviorSubject } from 'rxjs'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { ILicense } from '../../../licensing/public'; diff --git a/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts b/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts index 1c4a15a8cfb93..b71d42fb08adc 100644 --- a/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts +++ b/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import type { DocLinksStart } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts index 0a7337e453274..7ebc07a37b5c9 100644 --- a/x-pack/plugins/security/server/audit/audit_events.test.ts +++ b/x-pack/plugins/security/server/audit/audit_events.test.ts @@ -239,6 +239,7 @@ describe('#userLoginEvent', () => { authenticationResult: AuthenticationResult.succeeded(mockAuthenticatedUser()), authenticationProvider: 'basic1', authenticationType: 'basic', + sessionId: '123', }) ).toMatchInlineSnapshot(` Object { @@ -255,6 +256,7 @@ describe('#userLoginEvent', () => { "authentication_realm": "native1", "authentication_type": "basic", "lookup_realm": "native1", + "session_id": "123", "space_id": undefined, }, "message": "User [user] has logged in using basic provider [name=basic1]", @@ -293,6 +295,7 @@ describe('#userLoginEvent', () => { "authentication_realm": undefined, "authentication_type": "basic", "lookup_realm": undefined, + "session_id": undefined, "space_id": undefined, }, "message": "Failed attempt to login using basic provider [name=basic1]", diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 37b2cecfa55c1..c896b6cea9947 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -97,12 +97,14 @@ export interface UserLoginParams { authenticationResult: AuthenticationResult; authenticationProvider?: string; authenticationType?: string; + sessionId?: string; } export function userLoginEvent({ authenticationResult, authenticationProvider, authenticationType, + sessionId, }: UserLoginParams): AuditEvent { return { message: authenticationResult.user @@ -119,6 +121,7 @@ export function userLoginEvent({ }, kibana: { space_id: undefined, // Ensure this does not get populated by audit service + session_id: sessionId, authentication_provider: authenticationProvider, authentication_type: authenticationType, authentication_realm: authenticationResult.user?.authentication_realm.name, diff --git a/x-pack/plugins/security/server/audit/index.mock.ts b/x-pack/plugins/security/server/audit/mocks.ts similarity index 55% rename from x-pack/plugins/security/server/audit/index.mock.ts rename to x-pack/plugins/security/server/audit/mocks.ts index 6ac9108b51a83..6485818e7fc58 100644 --- a/x-pack/plugins/security/server/audit/index.mock.ts +++ b/x-pack/plugins/security/server/audit/mocks.ts @@ -5,20 +5,23 @@ * 2.0. */ -import type { AuditService } from './audit_service'; +import type { AuditLogger, AuditService } from './audit_service'; + +export const auditLoggerMock = { + create() { + return { + log: jest.fn(), + enabled: true, + } as jest.Mocked; + }, +}; export const auditServiceMock = { create() { return { getLogger: jest.fn(), - asScoped: jest.fn().mockReturnValue({ - log: jest.fn(), - enabled: true, - }), - withoutRequest: { - log: jest.fn(), - enabled: true, - }, + asScoped: jest.fn().mockReturnValue(auditLoggerMock.create()), + withoutRequest: auditLoggerMock.create(), } as jest.Mocked>; }, }; diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index 7d9a36f489b7c..b740a2a2db085 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -39,7 +39,7 @@ import type { AuthenticatedUser, SecurityLicense } from '../../common'; import { licenseMock } from '../../common/licensing/index.mock'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; import type { AuditServiceSetup } from '../audit'; -import { auditServiceMock } from '../audit/index.mock'; +import { auditServiceMock } from '../audit/mocks'; import type { ConfigType } from '../config'; import { ConfigSchema, createConfig } from '../config'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index f0ff164cbbc5a..1559a6fa4a5d7 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -27,7 +27,8 @@ import { import type { SecurityLicenseFeatures } from '../../common/licensing'; import { licenseMock } from '../../common/licensing/index.mock'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; -import { auditServiceMock } from '../audit/index.mock'; +import type { AuditLogger } from '../audit'; +import { auditLoggerMock, auditServiceMock } from '../audit/mocks'; import { ConfigSchema, createConfig } from '../config'; import { securityFeatureUsageServiceMock } from '../feature_usage/index.mock'; import { securityMock } from '../mocks'; @@ -39,6 +40,7 @@ import { Authenticator } from './authenticator'; import { DeauthenticationResult } from './deauthentication_result'; import type { BasicAuthenticationProvider, SAMLAuthenticationProvider } from './providers'; +let auditLogger: AuditLogger; function getMockOptions({ providers, http = {}, @@ -48,8 +50,11 @@ function getMockOptions({ http?: Partial; selector?: AuthenticatorOptions['config']['authc']['selector']; } = {}) { + const auditService = auditServiceMock.create(); + auditLogger = auditLoggerMock.create(); + auditService.asScoped.mockReturnValue(auditLogger); return { - audit: auditServiceMock.create(), + audit: auditService, getCurrentUser: jest.fn(), clusterClient: elasticsearchServiceMock.createClusterClient(), basePath: httpServiceMock.createSetupContract().basePath, @@ -66,6 +71,26 @@ function getMockOptions({ }; } +interface ExpectedAuditEvent { + action: string; + outcome?: string; + kibana?: Record; +} + +function expectAuditEvents(...events: ExpectedAuditEvent[]) { + expect(auditLogger.log).toHaveBeenCalledTimes(events.length); + for (let i = 0; i < events.length; i++) { + const { action, outcome, kibana } = events[i]; + expect(auditLogger.log).toHaveBeenNthCalledWith( + i + 1, + expect.objectContaining({ + event: { action, category: ['authentication'], ...(outcome && { outcome }) }, + ...(kibana && { kibana }), + }) + ); + } +} + describe('Authenticator', () => { let mockBasicAuthenticationProvider: jest.Mocked>; beforeEach(() => { @@ -262,16 +287,10 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessVal: SessionValue; - const auditLogger = { - log: jest.fn(), - enabled: true, - }; beforeEach(() => { - auditLogger.log.mockClear(); mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); mockOptions.session.get.mockResolvedValue(null); - mockOptions.audit.asScoped.mockReturnValue(auditLogger); mockSessVal = sessionMock.createValue({ state: { authorization: 'Basic xxx' } }); authenticator = new Authenticator(mockOptions); @@ -281,6 +300,7 @@ describe('Authenticator', () => { await expect(authenticator.login(undefined as any, undefined as any)).rejects.toThrowError( 'Request should be a valid "KibanaRequest" instance, was [undefined].' ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('fails if login attempt is not provided or invalid.', async () => { @@ -304,6 +324,7 @@ describe('Authenticator', () => { ).rejects.toThrowError( 'Login attempt should be an object with non-empty "provider.type" or "provider.name" property.' ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('fails if an authentication provider fails.', async () => { @@ -317,6 +338,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.failed(failureReason)); + expectAuditEvents({ action: 'user_login', outcome: 'failure' }); }); it('returns user that authentication provider returns.', async () => { @@ -332,38 +354,49 @@ describe('Authenticator', () => { ).resolves.toEqual( AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } }) ); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); - it('adds audit event when successful.', async () => { - const request = httpServerMock.createKibanaRequest(); - const user = mockAuthenticatedUser(); - mockBasicAuthenticationProvider.login.mockResolvedValue( - AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } }) - ); - await authenticator.login(request, { provider: { type: 'basic' }, value: {} }); + describe('user_login audit events', () => { + // Every other test case includes audit event assertions, but the user_login event is a bit special. + // We have these separate, detailed test cases to ensure that the session ID is included for user_login success events. + // This allows us to keep audit event assertions in the other test cases simpler. - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: { action: 'user_login', category: ['authentication'], outcome: 'success' }, - }) - ); - }); + it('adds audit event with session ID when successful.', async () => { + const request = httpServerMock.createKibanaRequest(); + const user = mockAuthenticatedUser(); + mockBasicAuthenticationProvider.login.mockResolvedValue( + AuthenticationResult.succeeded(user, { + authHeaders: { authorization: 'Basic .....' }, + state: 'foo', // to ensure a new session is created + }) + ); + mockOptions.session.create.mockResolvedValue({ ...mockSessVal, sid: '123' }); + await authenticator.login(request, { provider: { type: 'basic' }, value: {} }); - it('adds audit event when not successful.', async () => { - const request = httpServerMock.createKibanaRequest(); - const failureReason = new Error('Not Authorized'); - mockBasicAuthenticationProvider.login.mockResolvedValue( - AuthenticationResult.failed(failureReason) - ); - await authenticator.login(request, { provider: { type: 'basic' }, value: {} }); + expect(mockOptions.session.create).toHaveBeenCalledTimes(1); + expectAuditEvents({ + action: 'user_login', + outcome: 'success', + kibana: expect.objectContaining({ authentication_type: 'basic', session_id: '123' }), + }); + }); - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: { action: 'user_login', category: ['authentication'], outcome: 'failure' }, - }) - ); + it('adds audit event without session ID when not successful.', async () => { + const request = httpServerMock.createKibanaRequest(); + const failureReason = new Error('Not Authorized'); + mockBasicAuthenticationProvider.login.mockResolvedValue( + AuthenticationResult.failed(failureReason) + ); + await authenticator.login(request, { provider: { type: 'basic' }, value: {} }); + + expect(mockOptions.session.create).not.toHaveBeenCalled(); + expectAuditEvents({ + action: 'user_login', + outcome: 'failure', + kibana: expect.objectContaining({ authentication_type: 'basic', session_id: undefined }), + }); + }); }); it('does not add audit event when not handled.', async () => { @@ -396,6 +429,7 @@ describe('Authenticator', () => { provider: mockSessVal.provider, state: { authorization }, }); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('returns `notHandled` if login attempt is targeted to not configured provider.', async () => { @@ -476,6 +510,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.login).not.toHaveBeenCalled(); expect(mockSAMLAuthenticationProvider1.login).not.toHaveBeenCalled(); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('tries to login only with the provider that has specified type', async () => { @@ -493,6 +528,7 @@ describe('Authenticator', () => { expect(mockSAMLAuthenticationProvider1.login.mock.invocationCallOrder[0]).toBeLessThan( mockSAMLAuthenticationProvider2.login.mock.invocationCallOrder[0] ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('returns as soon as provider handles request', async () => { @@ -528,6 +564,10 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.login).not.toHaveBeenCalled(); expect(mockSAMLAuthenticationProvider2.login).not.toHaveBeenCalled(); expect(mockSAMLAuthenticationProvider1.login).toHaveBeenCalledTimes(3); + expectAuditEvents( + { action: 'user_login', outcome: 'failure' }, + { action: 'user_login', outcome: 'success' } + ); }); it('provides session only if provider name matches', async () => { @@ -563,6 +603,7 @@ describe('Authenticator', () => { expect(mockSAMLAuthenticationProvider2.login.mock.invocationCallOrder[0]).toBeLessThan( mockSAMLAuthenticationProvider1.login.mock.invocationCallOrder[0] ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); }); @@ -595,6 +636,10 @@ describe('Authenticator', () => { expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('clears session if provider asked to do so in `succeeded` result.', async () => { @@ -615,6 +660,10 @@ describe('Authenticator', () => { expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('clears session if provider asked to do so in `redirected` result.', async () => { @@ -634,6 +683,7 @@ describe('Authenticator', () => { expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); describe('with Access Agreement', () => { @@ -670,6 +720,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.succeeded(mockUser)); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('does not redirect to Access Agreement if request cannot be handled', async () => { @@ -681,6 +732,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.notHandled()); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if authentication fails', async () => { @@ -695,6 +747,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.failed(failureReason)); + expectAuditEvents({ action: 'user_login', outcome: 'failure' }); }); it('does not redirect to Access Agreement if redirect is required to complete login', async () => { @@ -708,6 +761,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.redirectTo('/some-url', { state: 'some-state' })); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if user has already acknowledged it', async () => { @@ -724,6 +778,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.succeeded(mockUser, { state: 'some-state' })); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('does not redirect to Access Agreement its own requests', async () => { @@ -737,6 +792,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.succeeded(mockUser, { state: 'some-state' })); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('does not redirect to Access Agreement if it is not configured', async () => { @@ -752,6 +808,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.succeeded(mockUser, { state: 'some-state' })); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('does not redirect to Access Agreement if license doesnt allow it.', async () => { @@ -768,6 +825,7 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.succeeded(mockUser, { state: 'some-state' })); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('redirects to Access Agreement when needed.', async () => { @@ -793,6 +851,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('redirects to Access Agreement preserving redirect URL specified in login attempt.', async () => { @@ -822,6 +881,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('redirects to Access Agreement preserving redirect URL specified in the authentication result.', async () => { @@ -848,6 +908,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('redirects AJAX requests to Access Agreement when needed.', async () => { @@ -873,6 +934,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); }); @@ -909,6 +971,10 @@ describe('Authenticator', () => { await expect( authenticator.login(request, { provider: { type: 'basic' }, value: {} }) ).resolves.toEqual(AuthenticationResult.succeeded(mockUser, { state: 'some-state' })); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('does not redirect to Overwritten Session if username and provider did not change', async () => { @@ -930,6 +996,7 @@ describe('Authenticator', () => { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); + expectAuditEvents({ action: 'user_login', outcome: 'success' }); }); it('does not redirect to Overwritten Session if session was unauthenticated before login', async () => { @@ -952,6 +1019,10 @@ describe('Authenticator', () => { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); + expectAuditEvents( + // We do not record a user_logout event for "intermediate" sessions that are deleted, only user_login for the new session + { action: 'user_login', outcome: 'success' } + ); }); it('redirects to Overwritten Session when username changes', async () => { @@ -977,6 +1048,10 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('redirects to Overwritten Session when provider changes', async () => { @@ -1005,6 +1080,10 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('redirects to Overwritten Session preserving redirect URL specified in login attempt.', async () => { @@ -1034,6 +1113,10 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('redirects to Overwritten Session preserving redirect URL specified in the authentication result.', async () => { @@ -1060,6 +1143,10 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); it('redirects AJAX requests to Overwritten Session when needed.', async () => { @@ -1085,6 +1172,10 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents( + { action: 'user_logout', outcome: 'unknown' }, + { action: 'user_login', outcome: 'success' } + ); }); }); }); @@ -1093,16 +1184,10 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessVal: SessionValue; - const auditLogger = { - log: jest.fn(), - enabled: true, - }; beforeEach(() => { - auditLogger.log.mockClear(); mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); mockOptions.session.get.mockResolvedValue(null); - mockOptions.audit.asScoped.mockReturnValue(auditLogger); mockSessVal = sessionMock.createValue({ state: { authorization: 'Basic xxx' } }); authenticator = new Authenticator(mockOptions); @@ -1112,6 +1197,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(undefined as any)).rejects.toThrowError( 'Request should be a valid "KibanaRequest" instance, was [undefined].' ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('fails if an authentication provider fails.', async () => { @@ -1125,6 +1211,7 @@ describe('Authenticator', () => { const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.failed()).toBe(true); expect(authenticationResult.error).toBe(failureReason); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('returns user that authentication provider returns.', async () => { @@ -1140,6 +1227,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(user, { authHeaders: { authorization: 'Basic .....' } }) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('creates session whenever authentication provider returns state for system API requests', async () => { @@ -1163,6 +1251,7 @@ describe('Authenticator', () => { provider: mockSessVal.provider, state: { authorization }, }); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('creates session whenever authentication provider returns state for non-system API requests', async () => { @@ -1186,6 +1275,7 @@ describe('Authenticator', () => { provider: mockSessVal.provider, state: { authorization }, }); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not extend session for system API calls.', async () => { @@ -1207,6 +1297,7 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('extends session for non-system API calls.', async () => { @@ -1229,6 +1320,7 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not touch session for system API calls if authentication fails with non-401 reason.', async () => { @@ -1250,6 +1342,7 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not touch session for non-system API calls if authentication fails with non-401 reason.', async () => { @@ -1271,6 +1364,7 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider for system API requests', async () => { @@ -1297,6 +1391,7 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider for non-system API requests', async () => { @@ -1323,6 +1418,7 @@ describe('Authenticator', () => { expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('clears session if provider failed to authenticate system API request with 401 with active session.', async () => { @@ -1342,11 +1438,12 @@ describe('Authenticator', () => { AuthenticationResult.failed(failureReason) ); - expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); - expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('clears session if provider failed to authenticate non-system API request with 401 with active session.', async () => { @@ -1366,11 +1463,12 @@ describe('Authenticator', () => { AuthenticationResult.failed(failureReason) ); - expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); - expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('clears session if provider requested it via setting state to `null`.', async () => { @@ -1385,31 +1483,12 @@ describe('Authenticator', () => { AuthenticationResult.redirectTo('some-url', { state: null }) ); - expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); - expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - }); - - it('adds audit event when invalidating session.', async () => { - const request = httpServerMock.createKibanaRequest(); - - mockBasicAuthenticationProvider.authenticate.mockResolvedValue( - AuthenticationResult.redirectTo('some-url', { state: null }) - ); - mockOptions.session.get.mockResolvedValue(mockSessVal); - - await expect(authenticator.authenticate(request)).resolves.toEqual( - AuthenticationResult.redirectTo('some-url', { state: null }) - ); - - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: { action: 'user_logout', category: ['authentication'], outcome: 'unknown' }, - }) - ); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('does not clear session if provider can not handle system API request authentication with active session.', async () => { @@ -1423,10 +1502,11 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); - expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not clear session if provider can not handle non-system API request authentication with active session.', async () => { @@ -1440,10 +1520,11 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); - expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); + expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); describe('with Login Selector', () => { @@ -1464,6 +1545,7 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); expect(mockBasicAuthenticationProvider.authenticate).toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect AJAX requests to Login Selector', async () => { @@ -1473,6 +1555,7 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); expect(mockBasicAuthenticationProvider.authenticate).toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Login Selector if request has `Authorization` header', async () => { @@ -1484,6 +1567,7 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); expect(mockBasicAuthenticationProvider.authenticate).toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Login Selector if it is not enabled', async () => { @@ -1495,6 +1579,7 @@ describe('Authenticator', () => { AuthenticationResult.notHandled() ); expect(mockBasicAuthenticationProvider.authenticate).toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('redirects to the Login Selector when needed.', async () => { @@ -1513,6 +1598,7 @@ describe('Authenticator', () => { ) ); expect(mockBasicAuthenticationProvider.authenticate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('redirects to the Login Selector with auth provider hint when needed.', async () => { @@ -1536,6 +1622,7 @@ describe('Authenticator', () => { ); expect(mockBasicAuthenticationProvider.authenticate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); }); @@ -1565,6 +1652,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect AJAX requests to Access Agreement', async () => { @@ -1574,6 +1662,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if request cannot be handled', async () => { @@ -1587,6 +1676,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.notHandled() ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if authentication fails', async () => { @@ -1601,6 +1691,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.failed(failureReason) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if redirect is required to complete authentication', async () => { @@ -1614,6 +1705,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.redirectTo('/some-url') ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if user has already acknowledged it', async () => { @@ -1626,6 +1718,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement its own requests', async () => { @@ -1635,6 +1728,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if it is not configured', async () => { @@ -1646,6 +1740,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Access Agreement if license doesnt allow it.', async () => { @@ -1658,6 +1753,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('redirects to Access Agreement when needed.', async () => { @@ -1677,6 +1773,7 @@ describe('Authenticator', () => { { user: mockUser, authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' } } ) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); }); @@ -1712,6 +1809,7 @@ describe('Authenticator', () => { await expect(authenticator.authenticate(request)).resolves.toEqual( AuthenticationResult.succeeded(mockUser) ); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('does not redirect AJAX requests to Overwritten Session', async () => { @@ -1731,6 +1829,7 @@ describe('Authenticator', () => { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('does not redirect to Overwritten Session if username and provider did not change', async () => { @@ -1750,6 +1849,7 @@ describe('Authenticator', () => { authResponseHeaders: { 'WWW-Authenticate': 'Negotiate' }, }) ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('redirects to Overwritten Session when username changes', async () => { @@ -1773,6 +1873,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('redirects to Overwritten Session when provider changes', async () => { @@ -1799,6 +1900,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('redirects to Overwritten Session preserving redirect URL specified in the authentication result.', async () => { @@ -1823,6 +1925,7 @@ describe('Authenticator', () => { } ) ); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); }); }); @@ -1831,16 +1934,10 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessVal: SessionValue; - const auditLogger = { - log: jest.fn(), - enabled: true, - }; beforeEach(() => { - auditLogger.log.mockClear(); mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); mockOptions.session.get.mockResolvedValue(null); - mockOptions.audit.asScoped.mockReturnValue(auditLogger); mockSessVal = sessionMock.createValue({ state: { authorization: 'Basic xxx' } }); authenticator = new Authenticator(mockOptions); @@ -1863,8 +1960,8 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); - expect(mockBasicAuthenticationProvider.authenticate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not redirect to Login Selector even if it is enabled if session is not available.', async () => { @@ -1885,8 +1982,8 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); - expect(mockBasicAuthenticationProvider.authenticate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not clear session if provider cannot handle authentication', async () => { @@ -1905,12 +2002,12 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); - expect(mockBasicAuthenticationProvider.authenticate).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.authenticate).toBeCalledWith( request, mockSessVal.state ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('does not clear session if authentication fails with non-401 reason.', async () => { @@ -1930,6 +2027,7 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('extends session if no update is needed.', async () => { @@ -1945,11 +2043,12 @@ describe('Authenticator', () => { AuthenticationResult.succeeded(user) ); - expect(mockOptions.session.extend).toHaveBeenCalledTimes(1); - expect(mockOptions.session.extend).toHaveBeenCalledWith(request, mockSessVal); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); + expect(mockOptions.session.extend).toHaveBeenCalledTimes(1); + expect(mockOptions.session.extend).toHaveBeenCalledWith(request, mockSessVal); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider', async () => { @@ -1966,14 +2065,15 @@ describe('Authenticator', () => { AuthenticationResult.succeeded(user, { state: newState }) ); + expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).toHaveBeenCalledTimes(1); expect(mockOptions.session.update).toHaveBeenCalledWith(request, { ...mockSessVal, state: newState, }); - expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('clears session if provider failed to authenticate request with 401.', async () => { @@ -1991,18 +2091,12 @@ describe('Authenticator', () => { AuthenticationResult.failed(failureReason) ); - expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); - expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); expect(mockOptions.session.create).not.toHaveBeenCalled(); expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.session.extend).not.toHaveBeenCalled(); - - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: { action: 'user_logout', category: ['authentication'], outcome: 'unknown' }, - }) - ); + expect(mockOptions.session.invalidate).toHaveBeenCalledTimes(1); + expect(mockOptions.session.invalidate).toHaveBeenCalledWith(request, { match: 'current' }); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); }); @@ -2010,15 +2104,9 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessVal: SessionValue; - const auditLogger = { - log: jest.fn(), - enabled: true, - }; beforeEach(() => { - auditLogger.log.mockClear(); mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); - mockOptions.audit.asScoped.mockReturnValue(auditLogger); mockSessVal = sessionMock.createValue({ state: { authorization: 'Basic xxx' } }); authenticator = new Authenticator(mockOptions); @@ -2028,6 +2116,7 @@ describe('Authenticator', () => { await expect(authenticator.logout(undefined as any)).rejects.toThrowError( 'Request should be a valid "KibanaRequest" instance, was [undefined].' ); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('redirects to login form if session does not exist.', async () => { @@ -2040,6 +2129,7 @@ describe('Authenticator', () => { ); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('clears session and returns whatever authentication provider returns.', async () => { @@ -2055,25 +2145,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockOptions.session.invalidate).toHaveBeenCalled(); - }); - - it('adds audit event.', async () => { - const request = httpServerMock.createKibanaRequest(); - mockBasicAuthenticationProvider.logout.mockResolvedValue( - DeauthenticationResult.redirectTo('some-url') - ); - mockOptions.session.get.mockResolvedValue(mockSessVal); - - await expect(authenticator.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo('some-url') - ); - - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: { action: 'user_logout', category: ['authentication'], outcome: 'unknown' }, - }) - ); + expectAuditEvents({ action: 'user_logout', outcome: 'unknown' }); }); it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => { @@ -2093,6 +2165,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request, null); expect(mockOptions.session.invalidate).toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('if session does not exist and provider name is not available, returns whatever authentication provider returns.', async () => { @@ -2110,6 +2183,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('if session does not exist and providers is empty, redirects to default logout path.', async () => { @@ -2128,6 +2202,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('redirects to login form if session does not exist and provider name is invalid', async () => { @@ -2140,6 +2215,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); expect(mockOptions.session.invalidate).toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); }); @@ -2147,16 +2223,11 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessionValue: SessionValue; - const auditLogger = { - log: jest.fn(), - enabled: true, - }; beforeEach(() => { mockOptions = getMockOptions({ providers: { basic: { basic1: { order: 0 } } } }); mockSessionValue = sessionMock.createValue({ state: { authorization: 'Basic xxx' } }); mockOptions.session.get.mockResolvedValue(mockSessionValue); - mockOptions.audit.asScoped.mockReturnValue(auditLogger); mockOptions.getCurrentUser.mockReturnValue(mockAuthenticatedUser()); mockOptions.license.getFeatures.mockReturnValue({ allowAccessAgreement: true, @@ -2176,6 +2247,7 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('fails if cannot retrieve user session', async () => { @@ -2189,6 +2261,7 @@ describe('Authenticator', () => { expect(mockOptions.session.update).not.toHaveBeenCalled(); expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); }); it('fails if license does not allow access agreement acknowledgement', async () => { @@ -2203,6 +2276,7 @@ describe('Authenticator', () => { ); expect(mockOptions.session.update).not.toHaveBeenCalled(); + expect(auditLogger.log).not.toHaveBeenCalled(); expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).not.toHaveBeenCalled(); }); @@ -2215,17 +2289,10 @@ describe('Authenticator', () => { ...mockSessionValue, accessAgreementAcknowledged: true, }); - - expect(auditLogger.log).toHaveBeenCalledTimes(1); - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: { action: 'access_agreement_acknowledged', category: ['authentication'] }, - }) - ); - expect(mockOptions.featureUsageService.recordPreAccessAgreementUsage).toHaveBeenCalledTimes( 1 ); + expectAuditEvents({ action: 'access_agreement_acknowledged' }); }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index fd1de8af4a149..3cddcd48dda7e 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -90,6 +90,16 @@ export interface AuthenticatorOptions { getServerBaseURL: () => string; } +/** @internal */ +interface InvalidateSessionValueParams { + /** Request instance. */ + request: KibanaRequest; + /** Value of the existing session, if any. */ + sessionValue: SessionValue | null; + /** If enabled, skips writing a `user_logout` audit event for this session. */ + skipAuditEvent?: boolean; +} + // Mapping between provider key defined in the config and authentication // provider class that can handle specific authentication mechanism. const providerMap = new Map< @@ -325,6 +335,9 @@ export class Authenticator { const auditLogger = this.options.audit.asScoped(request); auditLogger.log( userLoginEvent({ + // We must explicitly specify the sessionId for login events because we just created the session, so + // it won't automatically get included in the audit event from the request context. + sessionId: sessionUpdateResult?.value?.sid, authenticationResult, authenticationProvider: providerName, authenticationType: provider.type, @@ -446,7 +459,7 @@ export class Authenticator { sessionValue?.provider.name ?? request.url.searchParams.get(LOGOUT_PROVIDER_QUERY_STRING_PARAMETER); if (suggestedProviderName) { - await this.invalidateSessionValue(request, sessionValue); + await this.invalidateSessionValue({ request, sessionValue }); // Provider name may be passed in a query param and sourced from the browser's local storage; // hence, we can't assume that this provider exists, so we have to check it. @@ -595,7 +608,7 @@ export class Authenticator { this.logger.warn( `Attempted to retrieve session for the "${existingSessionValue.provider.type}/${existingSessionValue.provider.name}" provider, but it is not configured.` ); - await this.invalidateSessionValue(request, existingSessionValue); + await this.invalidateSessionValue({ request, sessionValue: existingSessionValue }); return null; } @@ -629,7 +642,7 @@ export class Authenticator { // attempt didn't fail. if (authenticationResult.shouldClearState()) { this.logger.debug('Authentication provider requested to invalidate existing session.'); - await this.invalidateSessionValue(request, existingSessionValue); + await this.invalidateSessionValue({ request, sessionValue: existingSessionValue }); return null; } @@ -643,7 +656,7 @@ export class Authenticator { if (authenticationResult.failed()) { if (ownsSession && getErrorStatusCode(authenticationResult.error) === 401) { this.logger.debug('Authentication attempt failed, existing session will be invalidated.'); - await this.invalidateSessionValue(request, existingSessionValue); + await this.invalidateSessionValue({ request, sessionValue: existingSessionValue }); } return null; } @@ -681,17 +694,21 @@ export class Authenticator { this.logger.debug( 'Authentication provider has changed, existing session will be invalidated.' ); - await this.invalidateSessionValue(request, existingSessionValue); + await this.invalidateSessionValue({ request, sessionValue: existingSessionValue }); existingSessionValue = null; } else if (sessionHasBeenAuthenticated) { this.logger.debug( 'Session is authenticated, existing unauthenticated session will be invalidated.' ); - await this.invalidateSessionValue(request, existingSessionValue); + await this.invalidateSessionValue({ + request, + sessionValue: existingSessionValue, + skipAuditEvent: true, // Skip writing an audit event when we are replacing an intermediate session with a fullly authenticated session + }); existingSessionValue = null; } else if (usernameHasChanged) { this.logger.debug('Username has changed, existing session will be invalidated.'); - await this.invalidateSessionValue(request, existingSessionValue); + await this.invalidateSessionValue({ request, sessionValue: existingSessionValue }); existingSessionValue = null; } @@ -726,11 +743,13 @@ export class Authenticator { /** * Invalidates session value associated with the specified request. - * @param request Request instance. - * @param sessionValue Value of the existing session if any. */ - private async invalidateSessionValue(request: KibanaRequest, sessionValue: SessionValue | null) { - if (sessionValue) { + private async invalidateSessionValue({ + request, + sessionValue, + skipAuditEvent, + }: InvalidateSessionValueParams) { + if (sessionValue && !skipAuditEvent) { const auditLogger = this.options.audit.asScoped(request); auditLogger.log( userLogoutEvent({ diff --git a/x-pack/plugins/security/server/authorization/authorization_service.test.ts b/x-pack/plugins/security/server/authorization/authorization_service.test.ts index ff6f510f64b28..61ca8423fc626 100644 --- a/x-pack/plugins/security/server/authorization/authorization_service.test.ts +++ b/x-pack/plugins/security/server/authorization/authorization_service.test.ts @@ -7,7 +7,7 @@ import { Subject } from 'rxjs'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; // Note: this import must be before other relative imports for the mocks to work as intended. diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts index 65fe1de0c962c..0cbf04aac4f58 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_service.test.ts @@ -7,7 +7,7 @@ import { BehaviorSubject } from 'rxjs'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import type { CoreStatus } from 'src/core/server'; import { ServiceStatusLevels } from 'src/core/server'; import { coreMock, loggingSystemMock } from 'src/core/server/mocks'; diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index 491d6cdafa44d..de484647ffb6d 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -10,7 +10,7 @@ import type { TransportResult } from '@elastic/elasticsearch'; import { licenseMock } from '../common/licensing/index.mock'; import type { MockAuthenticatedUserProps } from '../common/model/authenticated_user.mock'; import { mockAuthenticatedUser } from '../common/model/authenticated_user.mock'; -import { auditServiceMock } from './audit/index.mock'; +import { auditServiceMock } from './audit/mocks'; import { authenticationServiceMock } from './authentication/authentication_service.mock'; import { authorizationMock } from './authorization/index.mock'; diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index d890861849cfe..7e45946c7452d 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -15,10 +15,10 @@ import type { SavedObjectsResolveResponse, SavedObjectsUpdateObjectsSpacesResponseObject, } from 'src/core/server'; -import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; import type { AuditEvent } from '../audit'; -import { auditServiceMock } from '../audit/index.mock'; +import { auditLoggerMock } from '../audit/mocks'; import { Actions } from '../authorization'; import type { SavedObjectActions } from '../authorization/actions/saved_object'; import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; @@ -65,7 +65,7 @@ const createSecureSavedObjectsClientWrapperOptions = () => { checkSavedObjectsPrivilegesAsCurrentUser: jest.fn(), errors, getSpacesService, - auditLogger: auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()), + auditLogger: auditLoggerMock.create(), forbiddenError, generalError, }; diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index 45ce865de5635..1667ae8b400be 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -18,7 +18,7 @@ import type { ElasticsearchClient } from 'src/core/server'; import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; import type { AuditLogger } from '../audit'; -import { auditServiceMock } from '../audit/index.mock'; +import { auditLoggerMock } from '../audit/mocks'; import { ConfigSchema, createConfig } from '../config'; import { securityMock } from '../mocks'; import { getSessionIndexTemplate, SessionIndex } from './session_index'; @@ -32,7 +32,7 @@ describe('Session index', () => { const indexTemplateName = '.kibana_some_tenant_security_session_index_template_1'; beforeEach(() => { mockElasticsearchClient = elasticsearchServiceMock.createElasticsearchClient(); - auditLogger = auditServiceMock.create().withoutRequest; + auditLogger = auditLoggerMock.create(); sessionIndex = new SessionIndex({ logger: loggingSystemMock.createLogger(), kibanaIndexName: '.kibana_some_tenant', @@ -364,7 +364,7 @@ describe('Session index', () => { { isTLSEnabled: false } ), elasticsearchClient: mockElasticsearchClient, - auditLogger: auditServiceMock.create().withoutRequest, + auditLogger, }); await sessionIndex.cleanUp(); @@ -456,7 +456,7 @@ describe('Session index', () => { { isTLSEnabled: false } ), elasticsearchClient: mockElasticsearchClient, - auditLogger: auditServiceMock.create().withoutRequest, + auditLogger, }); await sessionIndex.cleanUp(); @@ -542,7 +542,7 @@ describe('Session index', () => { { isTLSEnabled: false } ), elasticsearchClient: mockElasticsearchClient, - auditLogger: auditServiceMock.create().withoutRequest, + auditLogger, }); await sessionIndex.cleanUp(); @@ -653,7 +653,7 @@ describe('Session index', () => { { isTLSEnabled: false } ), elasticsearchClient: mockElasticsearchClient, - auditLogger: auditServiceMock.create().withoutRequest, + auditLogger, }); await sessionIndex.cleanUp(); diff --git a/x-pack/plugins/security/server/session_management/session_management_service.test.ts b/x-pack/plugins/security/server/session_management/session_management_service.test.ts index 100d0b30082c6..6c7df0540d1f9 100644 --- a/x-pack/plugins/security/server/session_management/session_management_service.test.ts +++ b/x-pack/plugins/security/server/session_management/session_management_service.test.ts @@ -7,7 +7,7 @@ import { Subject } from 'rxjs'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock, elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; import type { @@ -16,7 +16,7 @@ import type { } from '../../../task_manager/server'; import { taskManagerMock } from '../../../task_manager/server/mocks'; import type { AuditLogger } from '../audit'; -import { auditServiceMock } from '../audit/index.mock'; +import { auditLoggerMock } from '../audit/mocks'; import { ConfigSchema, createConfig } from '../config'; import type { OnlineStatusRetryScheduler } from '../elasticsearch'; import { Session } from './session'; @@ -37,7 +37,7 @@ describe('SessionManagementService', () => { let auditLogger: AuditLogger; beforeEach(() => { service = new SessionManagementService(loggingSystemMock.createLogger()); - auditLogger = auditServiceMock.create().withoutRequest; + auditLogger = auditLoggerMock.create(); }); afterEach(() => { diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index 2e39810d4cbde..607ea032078ee 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -20,7 +20,7 @@ import type { GetAllSpacesPurpose, LegacyUrlAliasTarget, Space } from '../../../ import { spacesClientMock } from '../../../spaces/server/mocks'; import type { AuditEvent, AuditLogger } from '../audit'; import { SavedObjectAction, SpaceAuditAction } from '../audit'; -import { auditServiceMock } from '../audit/index.mock'; +import { auditLoggerMock } from '../audit/mocks'; import type { AuthorizationServiceSetup, AuthorizationServiceSetupInternal, @@ -98,7 +98,7 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { }); authorization.mode.useRbacForRequest.mockReturnValue(securityEnabled); - const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); + const auditLogger = auditLoggerMock.create(); const request = httpServerMock.createKibanaRequest(); diff --git a/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts b/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts index 89f0f81dcc5f9..e151b943e2b04 100644 --- a/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts +++ b/x-pack/plugins/security/server/spaces/setup_spaces_client.test.ts @@ -8,7 +8,7 @@ import { coreMock, httpServerMock } from 'src/core/server/mocks'; import { spacesMock } from '../../../spaces/server/mocks'; -import { auditServiceMock } from '../audit/index.mock'; +import { auditServiceMock } from '../audit/mocks'; import { authorizationMock } from '../authorization/index.mock'; import { setupSpacesClient } from './setup_spaces_client'; diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index fcec53eb0cf30..44e73cd8d1a8f 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -84,6 +84,7 @@ export enum SecurityPageName { * Warning: Computed values are not permitted in an enum with string valued members * The 3 following Cases page names must match `CasesDeepLinkId` in x-pack/plugins/cases/public/common/navigation.ts */ + blocklist = 'blocklist', case = 'cases', caseConfigure = 'cases_configure', caseCreate = 'cases_create', @@ -132,6 +133,7 @@ export const TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/trusted_apps` as const; export const EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/event_filters` as const; export const HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/host_isolation_exceptions` as const; +export const BLOCKLIST_PATH = `${MANAGEMENT_PATH}/blocklist` as const; export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}` as const; export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}` as const; @@ -151,6 +153,7 @@ export const APP_TRUSTED_APPS_PATH = `${APP_PATH}${TRUSTED_APPS_PATH}` as const; export const APP_EVENT_FILTERS_PATH = `${APP_PATH}${EVENT_FILTERS_PATH}` as const; export const APP_HOST_ISOLATION_EXCEPTIONS_PATH = `${APP_PATH}${HOST_ISOLATION_EXCEPTIONS_PATH}` as const; +export const APP_BLOCKLIST_PATH = `${APP_PATH}${BLOCKLIST_PATH}` as const; /** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */ export const DEFAULT_INDEX_PATTERN = [ diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/event_filter_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/event_filter_generator.ts index 6c827d763bb34..daf96a3149649 100644 --- a/x-pack/plugins/security_solution/common/endpoint/data_generators/event_filter_generator.ts +++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/event_filter_generator.ts @@ -8,24 +8,30 @@ import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { BaseDataGenerator } from './base_data_generator'; -import { getCreateExceptionListItemSchemaMock } from '../../../../lists/common/schemas/request/create_exception_list_item_schema.mock'; +import { ExceptionsListItemGenerator } from './exceptions_list_item_generator'; +import { BY_POLICY_ARTIFACT_TAG_PREFIX, GLOBAL_ARTIFACT_TAG } from '../service/artifacts'; -const EFFECT_SCOPE_TYPES = ['policy:', 'policy:all']; +const EFFECT_SCOPE_TYPES = [BY_POLICY_ARTIFACT_TAG_PREFIX, GLOBAL_ARTIFACT_TAG]; export class EventFilterGenerator extends BaseDataGenerator { generate(): CreateExceptionListItemSchema { - const overrides: Partial = { - name: `generator event ${this.randomString(5)}`, - list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, - item_id: `generator_endpoint_event_filter_${this.randomUUID()}`, - os_types: [this.randomOSFamily()] as CreateExceptionListItemSchema['os_types'], - tags: [this.randomChoice(EFFECT_SCOPE_TYPES)], - namespace_type: 'agnostic', - meta: undefined, - }; - - return Object.assign>( - getCreateExceptionListItemSchemaMock(), - overrides + const eventFilterGenerator = new ExceptionsListItemGenerator(); + const eventFilterData: CreateExceptionListItemSchema = eventFilterGenerator.generateEventFilter( + { + name: `Generated event ${this.randomString(5)}`, + item_id: `generator_endpoint_event_filter_${this.randomUUID()}`, + list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, + os_types: [this.randomOSFamily()] as CreateExceptionListItemSchema['os_types'], + tags: [this.randomChoice(EFFECT_SCOPE_TYPES)], + _version: undefined, + created_at: undefined, + created_by: undefined, + id: undefined, + tie_breaker_id: undefined, + updated_at: undefined, + updated_by: undefined, + } ); + + return eventFilterData; } } diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 51a17e76f969e..d88aeb26273fb 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -27,6 +27,7 @@ import { UEBA, HOST_ISOLATION_EXCEPTIONS, EVENT_FILTERS, + BLOCKLIST, TRUSTED_APPLICATIONS, POLICIES, ENDPOINTS, @@ -45,6 +46,7 @@ import { TRUSTED_APPS_PATH, EVENT_FILTERS_PATH, UEBA_PATH, + BLOCKLIST_PATH, CASES_FEATURE_ID, HOST_ISOLATION_EXCEPTIONS_PATH, SERVER_APP_ID, @@ -350,6 +352,11 @@ export const securitySolutionsDeepLinks: SecuritySolutionDeepLink[] = [ title: HOST_ISOLATION_EXCEPTIONS, path: HOST_ISOLATION_EXCEPTIONS_PATH, }, + { + id: SecurityPageName.blocklist, + title: BLOCKLIST, + path: BLOCKLIST_PATH, + }, ], }, ]; diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts index 15ec9a98b37fd..41240ede35ae9 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.ts +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.ts @@ -25,6 +25,7 @@ import { APP_POLICIES_PATH, APP_TRUSTED_APPS_PATH, APP_EVENT_FILTERS_PATH, + APP_BLOCKLIST_PATH, APP_UEBA_PATH, SecurityPageName, APP_HOST_ISOLATION_EXCEPTIONS_PATH, @@ -136,6 +137,13 @@ export const navTabs: SecurityNav = { disabled: false, urlKey: 'administration', }, + [SecurityPageName.blocklist]: { + id: SecurityPageName.blocklist, + name: i18n.BLOCKLIST, + href: APP_BLOCKLIST_PATH, + disabled: false, + urlKey: 'administration', + }, }; export const securityNavGroup: SecurityNavGroup = { diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 80726cb26fce5..28a3186584781 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -87,6 +87,10 @@ export const MANAGE = i18n.translate('xpack.securitySolution.navigation.manage', defaultMessage: 'Manage', }); +export const BLOCKLIST = i18n.translate('xpack.securitySolution.navigation.blocklist', { + defaultMessage: 'Blocklist', +}); + export const GO_TO_DOCUMENTATION = i18n.translate( 'xpack.securitySolution.goToDocumentationButton', { diff --git a/x-pack/plugins/security_solution/public/common/components/empty_value/empty_value.test.tsx b/x-pack/plugins/security_solution/public/common/components/empty_value/empty_value.test.tsx index 8bf2bba491051..c455ed1953c28 100644 --- a/x-pack/plugins/security_solution/public/common/components/empty_value/empty_value.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/empty_value/empty_value.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { defaultToEmptyTag, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx index 9dd8bf59893c0..2ecae44487908 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx @@ -33,6 +33,11 @@ jest.mock('../../../timelines/containers', () => ({ jest.mock('../../components/url_state/normalize_time_range.ts'); +const mockUseCreateFieldButton = jest.fn().mockReturnValue(<>); +jest.mock('../../../timelines/components/create_field_button', () => ({ + useCreateFieldButton: (...params: unknown[]) => mockUseCreateFieldButton(...params), +})); + const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock; jest.mock('use-resize-observer/polyfilled'); mockUseResizeObserver.mockImplementation(() => ({})); @@ -87,4 +92,22 @@ describe('StatefulEventsViewer', () => { expect(wrapper.find(`InspectButtonContainer`).exists()).toBe(true); }); }); + + test('it closes field editor when unmounted', async () => { + const mockCloseEditor = jest.fn(); + mockUseCreateFieldButton.mockImplementation((_, __, fieldEditorActionsRef) => { + fieldEditorActionsRef.current = { closeEditor: mockCloseEditor }; + return <>; + }); + + const wrapper = mount( + + + + ); + expect(mockCloseEditor).not.toHaveBeenCalled(); + + wrapper.unmount(); + expect(mockCloseEditor).toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 97b0424168f0a..9fa91ed25c995 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useEffect } from 'react'; +import React, { useRef, useCallback, useMemo, useEffect } from 'react'; import { connect, ConnectedProps, useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import styled from 'styled-components'; @@ -29,7 +29,10 @@ import { CellValueElementProps } from '../../../timelines/components/timeline/ce import { FIELDS_WITHOUT_CELL_ACTIONS } from '../../lib/cell_actions/constants'; import { useKibana } from '../../lib/kibana'; import { GraphOverlay } from '../../../timelines/components/graph_overlay'; -import { useCreateFieldButton } from '../../../timelines/components/create_field_button'; +import { + CreateFieldEditorActions, + useCreateFieldButton, +} from '../../../timelines/components/create_field_button'; const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = []; @@ -121,6 +124,8 @@ const StatefulEventsViewerComponent: React.FC = ({ const tGridEventRenderedViewEnabled = useIsExperimentalFeatureEnabled( 'tGridEventRenderedViewEnabled' ); + const editorActionsRef = useRef(null); + useEffect(() => { if (createTimeline != null) { createTimeline({ @@ -137,6 +142,10 @@ const StatefulEventsViewerComponent: React.FC = ({ } return () => { deleteEventQuery({ id, inputId: 'global' }); + if (editorActionsRef.current) { + // eslint-disable-next-line react-hooks/exhaustive-deps + editorActionsRef.current.closeEditor(); + } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -167,7 +176,7 @@ const StatefulEventsViewerComponent: React.FC = ({ }, [id, timelineQuery, globalQuery]); const bulkActions = useMemo(() => ({ onAlertStatusActionSuccess }), [onAlertStatusActionSuccess]); - const createFieldComponent = useCreateFieldButton(scopeId, id); + const createFieldComponent = useCreateFieldButton(scopeId, id, editorActionsRef); return ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx index 531ad1318e0e1..6c3dba5f2e22b 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/error_callout.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { getListMock } from '../../../../common/detection_engine/schemas/types/lists.mock'; diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_utility.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_utility.test.tsx index 9bbd490f1ed16..562c6a7250a5d 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_utility.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_utility.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ExceptionsViewerUtility } from './exceptions_utility'; import { getMockTheme } from '../../../lib/kibana/kibana_react.mock'; diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx index 0c077aaea81a8..97f93b9732c02 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow, ReactWrapper, ShallowWrapper } from 'enzyme'; import React from 'react'; import { removeExternalLinkText } from '@kbn/securitysolution-io-ts-utils'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { encodeIpv6 } from '../../lib/helpers'; import { useUiSetting$ } from '../../lib/kibana'; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.test.tsx index a3d6cb50bef57..ebd1c0c4df109 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/ml_popover.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { MlPopover } from './ml_popover'; diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index ea2b692b2b3b7..8e25166d12a98 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -43,6 +43,7 @@ export interface NavTab { export type SecurityNavKey = | SecurityPageName.administration | SecurityPageName.alerts + | SecurityPageName.blocklist | SecurityPageName.case | SecurityPageName.endpoints | SecurityPageName.policies diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index e0b890345071c..e3fe8a76efe0a 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -249,6 +249,16 @@ describe('useSecuritySolutionNavigation', () => { "name": "Host isolation exceptions", "onClick": [Function], }, + Object { + "data-href": "securitySolutionUI/blocklist", + "data-test-subj": "navigation-blocklist", + "disabled": false, + "href": "securitySolutionUI/blocklist", + "id": "blocklist", + "isSelected": false, + "name": "Blocklist", + "onClick": [Function], + }, ], "name": "Manage", }, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index 3c52efcc06567..69a6a187d63e6 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -103,6 +103,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { navTabs.trusted_apps, navTabs.event_filters, ...(canSeeHostIsolationExceptions ? [navTabs.host_isolation_exceptions] : []), + navTabs.blocklist, ], }, ] diff --git a/x-pack/plugins/security_solution/public/common/components/news_feed/post/index.tsx b/x-pack/plugins/security_solution/public/common/components/news_feed/post/index.tsx index 484452e68e3a6..2c4042d57561f 100644 --- a/x-pack/plugins/security_solution/public/common/components/news_feed/post/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/news_feed/post/index.tsx @@ -32,9 +32,9 @@ export const Post = React.memo<{ newsItem: NewsItem }>(({ newsItem }) => { return ( - - {title} - + + {title} + @@ -45,11 +45,9 @@ export const Post = React.memo<{ newsItem: NewsItem }>(({ newsItem }) => { - {imageUrl && ( - - - - )} +
+ {imageUrl && } +
); diff --git a/x-pack/plugins/security_solution/public/common/experimental_features_service.ts b/x-pack/plugins/security_solution/public/common/experimental_features_service.ts index 813341f175408..bb03fb59bf7a5 100644 --- a/x-pack/plugins/security_solution/public/common/experimental_features_service.ts +++ b/x-pack/plugins/security_solution/public/common/experimental_features_service.ts @@ -24,7 +24,7 @@ export class ExperimentalFeaturesService { private static throwUninitializedError(): never { throw new Error( - 'Experimental features services not initialized - are you trying to import this module from outside of the Security Solution app?' + 'Technical preview features services not initialized - are you trying to import this module from outside of the Security Solution app?' ); } } diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index 6d5d2dcbc7b4d..161cc62d6e731 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -62,7 +62,7 @@ export interface AppContextTestRender { render: UiRender; /** - * Set experimental features on/off. Calling this method updates the Store with the new values + * Set technical preview features on/off. Calling this method updates the Store with the new values * for the given feature flags * @param flags */ @@ -70,7 +70,7 @@ export interface AppContextTestRender { } // Defined a private custom reducer that reacts to an action that enables us to updat the -// store with new values for experimental features/flags. Because the `action.type` is a `Symbol`, +// store with new values for technical preview features/flags. Because the `action.type` is a `Symbol`, // and its not exported the action can only be `dispatch`'d from this module const UpdateExperimentalFeaturesTestActionType = Symbol('updateExperimentalFeaturesTestAction'); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx index 01d280d61d720..bca04dcf37a5b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx @@ -22,7 +22,6 @@ interface InvestigateInTimelineActionProps { alertIds?: string[]; buttonType?: 'text' | 'icon'; onInvestigateInTimelineAlertClick?: () => void; - timelineId?: string; } const InvestigateInTimelineActionComponent: React.FC = ({ @@ -31,13 +30,11 @@ const InvestigateInTimelineActionComponent: React.FC { const { investigateInTimelineAlertClick } = useInvestigateInTimeline({ ecsRowData, alertIds, onInvestigateInTimelineAlertClick, - timelineId, }); return ( diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx index 54e7da3066ea1..c1cbe657415a6 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_investigate_in_timeline.tsx @@ -27,14 +27,12 @@ interface UseInvestigateInTimelineActionProps { nonEcsRowData?: TimelineNonEcsData[]; alertIds?: string[] | null | undefined; onInvestigateInTimelineAlertClick?: () => void; - timelineId?: string; } export const useInvestigateInTimeline = ({ ecsRowData, alertIds, onInvestigateInTimelineAlertClick, - timelineId, }: UseInvestigateInTimelineActionProps) => { const { data: { search: searchStrategyClient, query }, @@ -80,7 +78,7 @@ export const useInvestigateInTimeline = ({ const showInvestigateInTimelineAction = alertIds != null; const { isLoading: isFetchingAlertEcs, alertsEcsData } = useFetchEcsAlertsData({ alertIds, - skip: alertIds == null || timelineId !== undefined, + skip: alertIds == null, }); const investigateInTimelineAlertClick = useCallback(async () => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_fetch_ecs_alerts_data.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_fetch_ecs_alerts_data.ts index f7c3c341bfa29..c459fab89a25e 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_fetch_ecs_alerts_data.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_fetch_ecs_alerts_data.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState } from 'react'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isEmpty } from 'lodash'; @@ -25,11 +25,10 @@ export const useFetchEcsAlertsData = ({ }): { isLoading: boolean | null; alertsEcsData: Ecs[] | null } => { const [isLoading, setIsLoading] = useState(null); const [alertsEcsData, setAlertEcsData] = useState(null); - const abortCtrl = useRef(new AbortController()); useEffect(() => { let isSubscribed = true; - const controller = abortCtrl.current; + const abortCtrl = new AbortController(); const fetchAlert = async () => { try { @@ -73,7 +72,7 @@ export const useFetchEcsAlertsData = ({ return (): void => { isSubscribed = false; - controller.abort(); + abortCtrl.abort(); }; }, [alertIds, onError, skip]); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts index 2ea37ccfd343b..32745f39d27a8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts @@ -24,7 +24,7 @@ export const BACK_TO_RULES = i18n.translate( export const EXPERIMENTAL = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.experimentalDescription', { - defaultMessage: 'Experimental', + defaultMessage: 'Technical preview', } ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index 7972eb90310c1..24d842eb930a8 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -55,7 +55,7 @@ export const PAGE_TITLE = i18n.translate('xpack.securitySolution.detectionEngine export const EXPERIMENTAL_ON = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.experimentalOn', { - defaultMessage: 'Experimental: On', + defaultMessage: 'Technical preview: On', } ); @@ -63,14 +63,14 @@ export const EXPERIMENTAL_DESCRIPTION = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.experimentalDescription', { defaultMessage: - 'The experimental rules table view allows for advanced sorting capabilities. If you experience performance issues when working with the table, you can turn this setting off.', + 'The experimental rules table view is in technical preview and allows for advanced sorting capabilities. If you experience performance issues when working with the table, you can turn this setting off.', } ); export const EXPERIMENTAL_OFF = i18n.translate( 'xpack.securitySolution.detectionEngine.rules.experimentalOff', { - defaultMessage: 'Experimental: Off', + defaultMessage: 'Technical preview: Off', } ); diff --git a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts index ffda54d0deda1..40a726bdf42e4 100644 --- a/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts +++ b/x-pack/plugins/security_solution/public/management/common/breadcrumbs.ts @@ -9,7 +9,7 @@ import { ChromeBreadcrumb } from 'kibana/public'; import { AdministrationSubTab } from '../types'; import { ENDPOINTS_TAB, EVENT_FILTERS_TAB, POLICIES_TAB, TRUSTED_APPS_TAB } from './translations'; import { AdministrationRouteSpyState } from '../../common/utils/route/types'; -import { HOST_ISOLATION_EXCEPTIONS } from '../../app/translations'; +import { HOST_ISOLATION_EXCEPTIONS, BLOCKLIST } from '../../app/translations'; const TabNameMappedToI18nKey: Record = { [AdministrationSubTab.endpoints]: ENDPOINTS_TAB, @@ -17,6 +17,7 @@ const TabNameMappedToI18nKey: Record = { [AdministrationSubTab.trustedApps]: TRUSTED_APPS_TAB, [AdministrationSubTab.eventFilters]: EVENT_FILTERS_TAB, [AdministrationSubTab.hostIsolationExceptions]: HOST_ISOLATION_EXCEPTIONS, + [AdministrationSubTab.blocklist]: BLOCKLIST, }; export function getBreadcrumbs(params: AdministrationRouteSpyState): ChromeBreadcrumb[] { diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts index d6603896c9cf8..dd3db8f3352a7 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -20,6 +20,7 @@ export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD = `${MANAGEMENT_PATH}/:t export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`; export const MANAGEMENT_ROUTING_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.eventFilters})`; export const MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.hostIsolationExceptions})`; +export const MANAGEMENT_ROUTING_BLOCKLIST_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.blocklist})`; // --[ STORE ]--------------------------------------------------------------------------- /** The SIEM global store namespace where the management state will be mounted */ diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx new file mode 100644 index 0000000000000..59638f2e529ab --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Switch, Route } from 'react-router-dom'; +import React, { memo } from 'react'; +import { MANAGEMENT_ROUTING_BLOCKLIST_PATH } from '../../common/constants'; +import { NotFoundPage } from '../../../app/404'; +import { Blocklist } from './view/blocklist'; + +/** + * Provides the routing container for the blocklist related views + */ +export const BlocklistContainer = memo(() => { + return ( + + + + + ); +}); + +BlocklistContainer.displayName = 'BlocklistContainer'; diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/blocklist.test.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/blocklist.test.tsx new file mode 100644 index 0000000000000..37f4004ba9174 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/blocklist.test.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { act } from '@testing-library/react'; +import React from 'react'; +import { BLOCKLIST_PATH } from '../../../../../common/constants'; +import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges'; +import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint'; +import { Blocklist } from './blocklist'; + +describe('When on the blocklist page', () => { + let render: () => ReturnType; + let renderResult: ReturnType; + let history: AppContextTestRender['history']; + let mockedContext: AppContextTestRender; + + beforeEach(() => { + mockedContext = createAppRootMockRenderer(); + ({ history } = mockedContext); + render = () => (renderResult = mockedContext.render()); + + act(() => { + history.push(BLOCKLIST_PATH); + }); + }); + + describe('When on the blocklist list page', () => { + describe('And no data exists', () => { + it('should show the Empty message', async () => { + render(); + expect(renderResult.getByTestId('blocklistEmpty')).toBeTruthy(); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx new file mode 100644 index 0000000000000..ab96451f0cd25 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/blocklist.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo, useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiButton } from '@elastic/eui'; +import { AdministrationListPage } from '../../../components/administration_list_page'; +import { ListPageRouteState } from '../../../../../common/endpoint/types'; +import { useMemoizedRouteState } from '../../../common/hooks'; +import { BackToExternalAppButton } from '../../../components/back_to_external_app_button'; +import { BlocklistEmptyState } from './components/empty'; +import { BackToExternalAppSecondaryButton } from '../../../components/back_to_external_app_secondary_button'; + +export const Blocklist = memo(() => { + const { state: routeState } = useLocation(); + const memoizedRouteState = useMemoizedRouteState(routeState); + + const backButtonEmptyComponent = useMemo(() => { + if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) { + return ; + } + }, [memoizedRouteState]); + + const backButtonHeaderComponent = useMemo(() => { + if (memoizedRouteState && memoizedRouteState.onBackButtonNavigateTo) { + return ; + } + }, [memoizedRouteState]); + + const hasDataToShow = false; + + const handleAddButtonClick = () => {}; + + return ( + + } + subtitle={ + + } + actions={ + hasDataToShow ? ( + + + + ) : ( + [] + ) + } + hideHeader={!hasDataToShow} + > + {hasDataToShow ? ( +

{'Data, search bar, etc here'}

+ ) : ( + + )} +
+ ); +}); + +Blocklist.displayName = 'Blocklist'; diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/empty.tsx b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/empty.tsx new file mode 100644 index 0000000000000..bd1d01b73ec8d --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/components/empty.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import styled, { css } from 'styled-components'; +import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { ManagementEmptyStateWrapper } from '../../../../components/management_empty_state_wrapper'; + +const EmptyPrompt = styled(EuiEmptyPrompt)` + ${() => css` + max-width: 100%; + `} +`; + +export const BlocklistEmptyState = memo<{ + onAdd: () => void; + backComponent?: React.ReactNode; +}>(({ onAdd, backComponent }) => { + return ( + + + + + } + body={ + + } + actions={[ + + + , + + ...(backComponent ? [backComponent] : []), + ]} + /> + + ); +}); + +BlocklistEmptyState.displayName = 'BlocklistEmptyState'; diff --git a/x-pack/plugins/security_solution/public/management/pages/blocklist/view/index.ts b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/index.ts new file mode 100644 index 0000000000000..cf0e31a77dd18 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/blocklist/view/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './blocklist'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts index a7261e31ed35e..e58b489cdd2c6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts @@ -19,7 +19,7 @@ import { } from '@kbn/securitysolution-list-constants'; export const EVENT_FILTER_LIST_TYPE: ExceptionListType = ExceptionListTypeEnum.ENDPOINT_EVENTS; -export const EVENT_FILTER_LIST: CreateExceptionListSchema = { +export const EVENT_FILTER_LIST_DEFINITION: CreateExceptionListSchema = { name: ENDPOINT_EVENT_FILTERS_LIST_NAME, namespace_type: 'agnostic', description: ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts index 6bd0e9447d36f..40de6de881431 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/service_actions.ts @@ -16,7 +16,7 @@ import { HttpStart } from 'kibana/public'; import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL, - EVENT_FILTER_LIST, + EVENT_FILTER_LIST_DEFINITION, ENDPOINT_EVENT_FILTERS_LIST_ID, } from '../constants'; import { Immutable } from '../../../../../common/endpoint/types'; @@ -24,7 +24,7 @@ import { Immutable } from '../../../../../common/endpoint/types'; async function createEventFilterList(http: HttpStart): Promise { try { await http.post(EXCEPTION_LIST_URL, { - body: JSON.stringify(EVENT_FILTER_LIST), + body: JSON.stringify(EVENT_FILTER_LIST_DEFINITION), }); } catch (err) { // Ignore 409 errors. List already created diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index b3dcc4a0fbc97..5af8863f3a5cc 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -50,7 +50,21 @@ type HostIsolationExceptionPaginatedContent = PaginatedContentProps< typeof ExceptionItem >; -/* eslint-disable complexity */ +const getPaginationObject = ({ + total = 0, + perPage = MANAGEMENT_DEFAULT_PAGE_SIZE, + page = 1, +}: { + total?: number; + perPage?: number; + page?: number; +}) => ({ + totalItemCount: total, + pageSize: perPage, + pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS], + pageIndex: page - 1, +}); + export const HostIsolationExceptionsList = () => { const history = useHistory(); const privileges = useUserPrivileges().endpointPrivileges; @@ -102,12 +116,11 @@ export const HostIsolationExceptionsList = () => { }, }); - const pagination = { - totalItemCount: data?.total ?? 0, - pageSize: data?.per_page ?? MANAGEMENT_DEFAULT_PAGE_SIZE, - pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS], - pageIndex: (data?.page ?? 1) - 1, - }; + const pagination = getPaginationObject({ + total: data?.total, + perPage: data?.per_page, + page: data?.page, + }); const listItems = data?.data || []; const allListItems = allData?.data || []; diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index 9cf931dee6283..fb23b7466623c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -15,6 +15,7 @@ import { MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICIES_PATH, MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, + MANAGEMENT_ROUTING_BLOCKLIST_PATH, } from '../common/constants'; import { NotFoundPage } from '../../app/404'; import { TrackApplicationView } from '../../../../../../src/plugins/usage_collection/public'; @@ -27,6 +28,7 @@ import { EventFiltersContainer } from './event_filters'; import { getEndpointListPath } from '../common/routing'; import { useUserPrivileges } from '../../common/components/user_privileges'; import { HostIsolationExceptionsContainer } from './host_isolation_exceptions'; +import { BlocklistContainer } from './blocklist'; const NoPermissions = memo(() => { return ( @@ -110,6 +112,7 @@ export const ManagementContainer = memo(() => { path={MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH} component={HostIsolationExceptionsTelemetry} /> + diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index 8e5fc3e6cfe90..23b356207028a 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -36,6 +36,7 @@ export enum AdministrationSubTab { trustedApps = 'trusted_apps', eventFilters = 'event_filters', hostIsolationExceptions = 'host_isolation_exceptions', + blocklist = 'blocklist', } /** diff --git a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.test.tsx index 6f3f3e8b87bc8..0afb2bf641351 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.test.tsx @@ -6,8 +6,8 @@ */ import { render, fireEvent, act, screen } from '@testing-library/react'; -import React from 'react'; -import { CreateFieldButton } from './index'; +import React, { MutableRefObject } from 'react'; +import { CreateFieldButton, CreateFieldEditorActions } from './index'; import { indexPatternFieldEditorPluginMock, Start, @@ -108,4 +108,38 @@ describe('CreateFieldButton', () => { fireEvent.click(screen.getByRole('button')); expect(onClickParam).toHaveBeenCalled(); }); + + it("stores 'closeEditor' in the actions ref when editor is open", async () => { + const mockCloseEditor = jest.fn(); + useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView); + useKibanaMock().services.dataViewFieldEditor.openEditor = () => mockCloseEditor; + + const editorActionsRef: MutableRefObject = React.createRef(); + await act(async () => { + render( + undefined} + timelineId={TimelineId.detectionsPage} + editorActionsRef={editorActionsRef} + />, + { + wrapper: TestProviders, + } + ); + await runAllPromises(); + }); + + expect(editorActionsRef?.current).toBeNull(); + + fireEvent.click(screen.getByRole('button')); + + expect(mockCloseEditor).not.toHaveBeenCalled(); + expect(editorActionsRef?.current?.closeEditor).toBeDefined(); + + editorActionsRef!.current!.closeEditor(); + + expect(mockCloseEditor).toHaveBeenCalled(); + expect(editorActionsRef!.current).toBeNull(); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx index 04f23605efac5..8979a78d7aa46 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { MutableRefObject, useCallback, useEffect, useMemo, useState } from 'react'; import { EuiButton } from '@elastic/eui'; import styled from 'styled-components'; @@ -23,17 +23,21 @@ import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../timeline/body/constants'; import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers'; +export type CreateFieldEditorActions = { closeEditor: () => void } | null; +type CreateFieldEditorActionsRef = MutableRefObject; + interface CreateFieldButtonProps { selectedDataViewId: string; onClick: () => void; timelineId: TimelineId; + editorActionsRef?: CreateFieldEditorActionsRef; } const StyledButton = styled(EuiButton)` margin-left: ${({ theme }) => theme.eui.paddingSizes.m}; `; export const CreateFieldButton = React.memo( - ({ selectedDataViewId, onClick: onClickParam, timelineId }) => { + ({ selectedDataViewId, onClick: onClickParam, timelineId, editorActionsRef }) => { const [dataView, setDataView] = useState(null); const dispatch = useDispatch(); @@ -52,7 +56,7 @@ export const CreateFieldButton = React.memo( const onClick = useCallback(() => { if (dataView) { - dataViewFieldEditor?.openEditor({ + const closeFieldEditor = dataViewFieldEditor?.openEditor({ ctx: { dataView }, onSave: async (field: DataViewField) => { // Fetch the updated list of fields @@ -72,6 +76,14 @@ export const CreateFieldButton = React.memo( ); }, }); + if (editorActionsRef) { + editorActionsRef.current = { + closeEditor: () => { + editorActionsRef.current = null; + closeFieldEditor(); + }, + }; + } } onClickParam(); }, [ @@ -82,6 +94,7 @@ export const CreateFieldButton = React.memo( selectedDataViewId, dispatch, timelineId, + editorActionsRef, ]); if ( @@ -116,7 +129,8 @@ CreateFieldButton.displayName = 'CreateFieldButton'; */ export const useCreateFieldButton = ( sourcererScope: SourcererScopeName, - timelineId: TimelineId + timelineId: TimelineId, + editorActionsRef?: CreateFieldEditorActionsRef ) => { const scopeIdSelector = useMemo(() => sourcererSelectors.scopeIdSelector(), []); const { missingPatterns, selectedDataViewId } = useDeepEqualSelector((state) => @@ -133,9 +147,10 @@ export const useCreateFieldButton = ( selectedDataViewId={selectedDataViewId} onClick={onClick} timelineId={timelineId} + editorActionsRef={editorActionsRef} /> ); return CreateFieldButtonComponent; - }, [missingPatterns.length, selectedDataViewId, timelineId]); + }, [missingPatterns.length, selectedDataViewId, timelineId, editorActionsRef]); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx index c130ea4c96814..1e6a4b15bf124 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { useParams } from 'react-router-dom'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx index 54b405feeb176..6a9d4a6bbdbe8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { useParams } from 'react-router-dom'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx index 607bccdbc039d..e00a23261e747 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.test.tsx @@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash/fp'; import moment from 'moment'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import '../../../../common/mock/formatted_relative'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx index a215f02863123..24683cfdab548 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline.test.tsx @@ -6,7 +6,7 @@ */ import { cloneDeep } from 'lodash/fp'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; import { waitFor } from '@testing-library/react'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index 794cd275528e4..e2184aaaec773 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -6,7 +6,7 @@ */ import { cloneDeep } from 'lodash/fp'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx index 1c650b0ae0a11..55d230d118e25 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/search_row/index.test.tsx @@ -6,7 +6,7 @@ */ import { EuiFilterButtonProps } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx index 2a77f872117f6..925ea396b7fa0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -7,7 +7,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import { cloneDeep, omit } from 'lodash/fp'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx index bdb55aaf20969..5b17994a64530 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/common_columns.test.tsx @@ -9,7 +9,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import { cloneDeep, omit } from 'lodash/fp'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import '../../../../common/mock/match_media'; import '../../../../common/mock/formatted_relative'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx index e8960d2e0ad93..89331ca8e33bf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/extended_columns.test.tsx @@ -6,7 +6,7 @@ */ import { cloneDeep, omit } from 'lodash/fp'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx index a92d3a4eac4b0..01e9c588f0ef5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/icon_header_columns.test.tsx @@ -6,7 +6,7 @@ */ import { cloneDeep, omit } from 'lodash/fp'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx index c1aecd4a33825..55de25ff283e3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/timelines_table/index.test.tsx @@ -6,7 +6,7 @@ */ import { cloneDeep } from 'lodash/fp'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/title_row/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/title_row/index.test.tsx index a94777d0e573b..d46cad3c43a98 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/title_row/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/title_row/index.test.tsx @@ -6,7 +6,7 @@ */ import { EuiButtonProps } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index 53764bd8d2539..75ca399bf52d4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -169,7 +169,6 @@ const ActionsComponent: React.FC = ({ key="investigate-in-timeline" alertIds={alertIds} ecsRowData={ecsData} - timelineId={timelineId} /> )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 644a3c95baf08..2d625f678721b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -658,6 +658,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` ] } onSelectAll={[Function]} + show={true} showEventsSelect={false} showSelectAllCheckbox={false} sort={ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx index 59bdcf808ca42..aec28732f38af 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx @@ -16,7 +16,7 @@ import { Sort } from '../sort'; import { TestProviders } from '../../../../../common/mock/test_providers'; import { useMountAppended } from '../../../../../common/utils/use_mount_appended'; -import { ColumnHeadersComponent } from '.'; +import { ColumnHeadersComponent, ColumnHeadersComponentProps } from '.'; import { cloneDeep } from 'lodash/fp'; import { timelineActions } from '../../../../store/timeline'; import { TimelineTabs } from '../../../../../../common/types/timeline'; @@ -27,6 +27,11 @@ import { HeaderActions } from '../actions/header_actions'; jest.mock('../../../../../common/lib/kibana'); +const mockUseCreateFieldButton = jest.fn().mockReturnValue(<>); +jest.mock('../../../create_field_button', () => ({ + useCreateFieldButton: (...params: unknown[]) => mockUseCreateFieldButton(...params), +})); + const mockDispatch = jest.fn(); jest.mock('react-redux', () => { const original = jest.requireActual('react-redux'); @@ -46,33 +51,34 @@ describe('ColumnHeaders', () => { ...x, headerCellRender: HeaderActions, })); + const sort: Sort[] = [ + { + columnId: '@timestamp', + columnType: 'number', + sortDirection: Direction.desc, + }, + ]; + const defaultProps: ColumnHeadersComponentProps = { + actionsColumnWidth, + browserFields: mockBrowserFields, + columnHeaders: defaultHeaders, + isSelectAllChecked: false, + onSelectAll: jest.fn, + show: true, + showEventsSelect: false, + showSelectAllCheckbox: false, + sort, + tabType: TimelineTabs.query, + timelineId, + leadingControlColumns, + trailingControlColumns: [], + }; describe('rendering', () => { - const sort: Sort[] = [ - { - columnId: '@timestamp', - columnType: 'number', - sortDirection: Direction.desc, - }, - ]; - test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(wrapper.find('ColumnHeadersComponent')).toMatchSnapshot(); @@ -81,20 +87,7 @@ describe('ColumnHeaders', () => { test('it renders the field browser', () => { const wrapper = mount( - + ); @@ -104,20 +97,7 @@ describe('ColumnHeaders', () => { test('it renders every column header', () => { const wrapper = mount( - + ); @@ -166,18 +146,7 @@ describe('ColumnHeaders', () => { const wrapper = mount( ); @@ -210,18 +179,7 @@ describe('ColumnHeaders', () => { const wrapper = mount( ); @@ -249,18 +207,11 @@ describe('ColumnHeaders', () => { const wrapper = mount( ); @@ -287,18 +238,13 @@ describe('ColumnHeaders', () => { const wrapper = mount( ); @@ -307,4 +253,43 @@ describe('ColumnHeaders', () => { expect(wrapper.exists('[data-test-subj="test-header-action-cell"]')).toBeTruthy(); }); }); + + describe('Field Editor', () => { + test('Closes field editor when the timeline is unmounted', () => { + const mockCloseEditor = jest.fn(); + mockUseCreateFieldButton.mockImplementation((_, __, fieldEditorActionsRef) => { + fieldEditorActionsRef.current = { closeEditor: mockCloseEditor }; + return <>; + }); + + const wrapper = mount( + + + + ); + expect(mockCloseEditor).not.toHaveBeenCalled(); + + wrapper.unmount(); + expect(mockCloseEditor).toHaveBeenCalled(); + }); + + test('Closes field editor when the timeline is closed', () => { + const mockCloseEditor = jest.fn(); + mockUseCreateFieldButton.mockImplementation((_, __, fieldEditorActionsRef) => { + fieldEditorActionsRef.current = { closeEditor: mockCloseEditor }; + return <>; + }); + + const Proxy = (props: ColumnHeadersComponentProps) => ( + + + + ); + const wrapper = mount(); + expect(mockCloseEditor).not.toHaveBeenCalled(); + + wrapper.setProps({ ...defaultProps, show: false }); + expect(mockCloseEditor).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx index 80a9022105d2c..ca1cdef903de8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ import deepEqual from 'fast-deep-equal'; -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { Droppable, DraggableChildrenFn } from 'react-beautiful-dnd'; import { DragEffects } from '../../../../../common/components/drag_and_drop/draggable_wrapper'; @@ -34,15 +34,16 @@ import { Sort } from '../sort'; import { ColumnHeader } from './column_header'; import { SourcererScopeName } from '../../../../../common/store/sourcerer/model'; -import { useCreateFieldButton } from '../../../create_field_button'; +import { CreateFieldEditorActions, useCreateFieldButton } from '../../../create_field_button'; -interface Props { +export interface ColumnHeadersComponentProps { actionsColumnWidth: number; browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; isEventViewer?: boolean; isSelectAllChecked: boolean; onSelectAll: OnSelectAll; + show: boolean; showEventsSelect: boolean; showSelectAllCheckbox: boolean; sort: Sort[]; @@ -92,6 +93,7 @@ export const ColumnHeadersComponent = ({ isEventViewer = false, isSelectAllChecked, onSelectAll, + show, showEventsSelect, showSelectAllCheckbox, sort, @@ -99,8 +101,24 @@ export const ColumnHeadersComponent = ({ timelineId, leadingControlColumns, trailingControlColumns, -}: Props) => { +}: ColumnHeadersComponentProps) => { const [draggingIndex, setDraggingIndex] = useState(null); + const fieldEditorActionsRef = useRef(null); + + useEffect(() => { + return () => { + if (fieldEditorActionsRef.current) { + // eslint-disable-next-line react-hooks/exhaustive-deps + fieldEditorActionsRef.current.closeEditor(); + } + }; + }, []); + + useEffect(() => { + if (!show && fieldEditorActionsRef.current) { + fieldEditorActionsRef.current.closeEditor(); + } + }, [show]); const renderClone: DraggableChildrenFn = useCallback( (dragProvided, _dragSnapshot, rubric) => { @@ -174,7 +192,8 @@ export const ColumnHeadersComponent = ({ const createFieldComponent = useCreateFieldButton( SourcererScopeName.timeline, - timelineId as TimelineId + timelineId as TimelineId, + fieldEditorActionsRef ); const LeadingHeaderActions = useMemo(() => { @@ -300,6 +319,7 @@ export const ColumnHeaders = React.memo( prevProps.isEventViewer === nextProps.isEventViewer && prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && prevProps.onSelectAll === nextProps.onSelectAll && + prevProps.show === nextProps.show && prevProps.showEventsSelect === nextProps.showEventsSelect && prevProps.showSelectAllCheckbox === nextProps.showSelectAllCheckbox && deepEqual(prevProps.sort, nextProps.sort) && diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 5467dbab9845c..db927e67ccc67 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -146,6 +146,7 @@ describe('Body', () => { selectedEventIds: {}, setSelected: jest.fn() as unknown as StatefulBodyProps['setSelected'], sort: mockSort, + show: true, showCheckboxes: false, tabType: TimelineTabs.query, totalPages: 1, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 7e7192610a222..7257d4246f6fe 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -89,6 +89,7 @@ export const BodyComponent = React.memo( setSelected, clearSelected, onRuleChange, + show, showCheckboxes, refetch, renderCellValue, @@ -244,6 +245,7 @@ export const BodyComponent = React.memo( isEventViewer={isEventViewer} isSelectAllChecked={isSelectAllChecked} onSelectAll={onSelectAll} + show={show} showEventsSelect={false} showSelectAllCheckbox={showCheckboxes} sort={sort} @@ -298,7 +300,8 @@ export const BodyComponent = React.memo( prevProps.renderCellValue === nextProps.renderCellValue && prevProps.rowRenderers === nextProps.rowRenderers && prevProps.showCheckboxes === nextProps.showCheckboxes && - prevProps.tabType === nextProps.tabType + prevProps.tabType === nextProps.tabType && + prevProps.show === nextProps.show ); BodyComponent.displayName = 'BodyComponent'; @@ -321,6 +324,7 @@ const makeMapStateToProps = () => { pinnedEventIds, selectedEventIds, showCheckboxes, + show, } = timeline; return { @@ -333,6 +337,7 @@ const makeMapStateToProps = () => { pinnedEventIds, selectedEventIds, showCheckboxes, + show, }; }; return mapStateToProps; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts index 787c26871d869..9168e25edfb37 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/get_input_output_index.test.ts @@ -99,7 +99,7 @@ describe('get_input_output_index', () => { expect(inputIndex).toEqual(DEFAULT_INDEX_PATTERN); }); - test('Returns a saved object inputIndex default along with experimental features when uebaEnabled=true', async () => { + test('Returns a saved object inputIndex default along with technical preview features when uebaEnabled=true', async () => { servicesMock.savedObjectsClient.get.mockImplementation(async (type: string, id: string) => ({ id, type, diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts index 1e16fa2a40129..45b31a19693b7 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts @@ -13,7 +13,7 @@ import { TestBed, AsyncTestBedConfig, delay, -} from '@kbn/test/jest'; +} from '@kbn/test-jest-helpers'; import { SnapshotRestoreHome } from '../../../public/application/sections/home/home'; import { BASE_PATH } from '../../../public/application/constants'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts index fdf8ea8d83868..51c41bc49bc3d 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/index.ts @@ -14,8 +14,8 @@ import { setup as policyEditSetup } from './policy_edit.helpers'; import { setup as restoreSnapshotSetup } from './restore_snapshot.helpers'; import { setup as snapshotListSetup } from './snapshot_list.helpers'; -export type { TestBed } from '@kbn/test/jest'; -export { nextTick, getRandomString, findTestSubject, delay } from '@kbn/test/jest'; +export type { TestBed } from '@kbn/test-jest-helpers'; +export { nextTick, getRandomString, findTestSubject, delay } from '@kbn/test-jest-helpers'; export { setupEnvironment } from './setup_environment'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts index a6e7c4a4c1056..d921b96781d0c 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_add.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { PolicyAdd } from '../../../public/application/sections/policy_add'; import { formSetup, PolicyFormTestSubjects } from './policy_form.helpers'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts index 2014d22ffbfbc..461351e744125 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { PolicyEdit } from '../../../public/application/sections/policy_edit'; import { WithAppDependencies } from './setup_environment'; import { POLICY_NAME } from './constant'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_form.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_form.helpers.ts index f097f6185fc7c..c776a22ef9868 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_form.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/policy_form.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TestBed, SetupFunc } from '@kbn/test/jest'; +import { TestBed, SetupFunc } from '@kbn/test-jest-helpers'; export interface PolicyFormTestBed extends TestBed { actions: { diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts index b369b20c122eb..c6460078482bf 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_add.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, TestBed } from '@kbn/test/jest'; +import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { RepositoryType } from '../../../common/types'; import { RepositoryAdd } from '../../../public/application/sections/repository_add'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts index f0563f2831a98..275c216d70664 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/repository_edit.helpers.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { registerTestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { RepositoryEdit } from '../../../public/application/sections/repository_edit'; import { WithAppDependencies } from './setup_environment'; import { REPOSITORY_NAME } from './constant'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts index 123ae0cbb1c2e..86c93a6811bd4 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/restore_snapshot.helpers.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { RestoreSnapshot } from '../../../public/application/sections/restore_snapshot'; import { WithAppDependencies } from './setup_environment'; diff --git a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts index 176e5714f1308..261976623144b 100644 --- a/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts +++ b/x-pack/plugins/snapshot_restore/__jest__/client_integration/helpers/snapshot_list.helpers.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { TestBedConfig, registerTestBed, TestBed } from '@kbn/test/jest'; +import { TestBedConfig, registerTestBed, TestBed } from '@kbn/test-jest-helpers'; import { BASE_PATH } from '../../../public/application/constants'; import { SnapshotList } from '../../../public/application/sections/home/snapshot_list'; diff --git a/x-pack/plugins/snapshot_restore/test/fixtures/policy.ts b/x-pack/plugins/snapshot_restore/test/fixtures/policy.ts index 873d36fcc656e..66d5b9557fae2 100644 --- a/x-pack/plugins/snapshot_restore/test/fixtures/policy.ts +++ b/x-pack/plugins/snapshot_restore/test/fixtures/policy.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString, getRandomNumber } from '@kbn/test/jest'; +import { getRandomString, getRandomNumber } from '@kbn/test-jest-helpers'; import { SlmPolicy } from '../../common/types'; import { DEFAULT_POLICY_SCHEDULE } from '../../public/application/constants'; diff --git a/x-pack/plugins/snapshot_restore/test/fixtures/repository.ts b/x-pack/plugins/snapshot_restore/test/fixtures/repository.ts index b906a657f1ad7..16f2bea61bc28 100644 --- a/x-pack/plugins/snapshot_restore/test/fixtures/repository.ts +++ b/x-pack/plugins/snapshot_restore/test/fixtures/repository.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString } from '@kbn/test/jest'; +import { getRandomString } from '@kbn/test-jest-helpers'; import { RepositoryType } from '../../common/types'; const defaultSettings: any = { chunkSize: '10mb', location: '/tmp/es-backups' }; diff --git a/x-pack/plugins/snapshot_restore/test/fixtures/snapshot.ts b/x-pack/plugins/snapshot_restore/test/fixtures/snapshot.ts index 8b385b5e83006..e7fad7f4a291f 100644 --- a/x-pack/plugins/snapshot_restore/test/fixtures/snapshot.ts +++ b/x-pack/plugins/snapshot_restore/test/fixtures/snapshot.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getRandomString, getRandomNumber } from '@kbn/test/jest'; +import { getRandomString, getRandomNumber } from '@kbn/test-jest-helpers'; export const getSnapshot = ({ repository = 'my-repo', diff --git a/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx index 6e215b20e4f2a..7352e769f7e55 100644 --- a/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx +++ b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx @@ -9,7 +9,7 @@ import { EuiCallOut } from '@elastic/eui'; import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { AdvancedSettingsSubtitle } from './advanced_settings_subtitle'; diff --git a/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx index 95dd62a6680a6..e8b6766411d0c 100644 --- a/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx +++ b/x-pack/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { SpaceAvatarInternal } from '../../../space_avatar/space_avatar_internal'; import { AdvancedSettingsTitle } from './advanced_settings_title'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_mode_control.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_mode_control.test.tsx index 9c69b172751cf..a9da59c37739d 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_mode_control.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_mode_control.test.tsx @@ -8,7 +8,7 @@ import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { CopyModeControlProps } from './copy_mode_control'; import { CopyModeControl } from './copy_mode_control'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx index a9768bea9d1ed..858fddaf4698b 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_internal.test.tsx @@ -10,7 +10,7 @@ import Boom from '@hapi/boom'; import { act } from '@testing-library/react'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { Space } from '../../../common'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx index 96fe78e237a80..ad183196039c6 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/resolve_all_conflicts.test.tsx @@ -9,7 +9,7 @@ import { act } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import type { SummarizedCopyToSpaceResult } from '../lib'; import type { ImportRetry } from '../types'; diff --git a/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.test.tsx b/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.test.tsx index b999ae961d0f1..5f4f1f845717a 100644 --- a/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.test.tsx +++ b/x-pack/plugins/spaces/public/legacy_urls/components/legacy_url_conflict_internal.test.tsx @@ -10,7 +10,7 @@ import { act } from '@testing-library/react'; import React from 'react'; import { BehaviorSubject } from 'rxjs'; -import { findTestSubject, mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { LegacyUrlConflictInternal } from './legacy_url_conflict_internal'; diff --git a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx index 59c7dde71aedb..4b1ad8a6fe01f 100644 --- a/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx +++ b/x-pack/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { spacesManagerMock } from '../../../spaces_manager/mocks'; import { ConfirmDeleteModal } from './confirm_delete_modal'; diff --git a/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx b/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx index 0f26c69cac2be..6a491e5303149 100644 --- a/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx +++ b/x-pack/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { UnauthorizedPrompt } from './unauthorized_prompt'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx index f4ba960b2f3a4..5c9ed1b6a3283 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx index 42195317e6731..cea22e31a8289 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { SpaceValidator } from '../../lib'; import { CustomizeSpace } from './customize_space'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx index 578da9b96611c..ddc767163f0b5 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx @@ -8,7 +8,7 @@ import { EuiColorPicker, EuiFieldText, EuiLink } from '@elastic/eui'; import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { SpaceValidator } from '../../lib'; import { CustomizeSpaceAvatar } from './customize_space_avatar'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx index 900c032c8fbfa..d3f95cadc051f 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { notificationServiceMock } from 'src/core/public/mocks'; import type { SpacesManager } from '../../spaces_manager'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx index af6c546fcf56a..5283cfe8ad1a3 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx @@ -8,7 +8,7 @@ import type { EuiCheckboxProps } from '@elastic/eui'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers'; import { DEFAULT_APP_CATEGORIES } from 'src/core/public'; import type { KibanaFeatureConfig } from '../../../../../features/public'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index 465cb76c9e5bd..55672e9896ea3 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -11,7 +11,7 @@ import { waitFor } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { DEFAULT_APP_CATEGORIES } from 'src/core/public'; import { notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx index d8fb026058dd0..de3977ac792d0 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx @@ -8,7 +8,7 @@ import { EuiBadge } from '@elastic/eui'; import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ReservedSpaceBadge } from './reserved_space_badge'; diff --git a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx index 733044e638f5f..125697c6f4e19 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { SectionPanel } from './section_panel'; diff --git a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx index bb1ea2914fc43..8cff17b4dfe57 100644 --- a/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx +++ b/x-pack/plugins/spaces/public/management/spaces_grid/spaces_grid_page.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import React from 'react'; -import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { httpServiceMock, notificationServiceMock, scopedHistoryMock } from 'src/core/public/mocks'; import { KibanaFeature } from '../../../../features/public'; diff --git a/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx index 3e1d77d0a45f1..563b3693bcd7b 100644 --- a/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ManageSpacesButton } from './manage_spaces_button'; diff --git a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index d1282331027c2..8e5aa0c6769fe 100644 --- a/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx +++ b/x-pack/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -11,7 +11,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import * as Rx from 'rxjs'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { SpaceAvatarInternal } from '../space_avatar/space_avatar_internal'; import type { SpacesManager } from '../spaces_manager'; diff --git a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx index 6daea2413599a..3c84b5da2796b 100644 --- a/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx +++ b/x-pack/plugins/spaces/public/share_saved_objects_to_space/components/share_to_space_flyout_internal.test.tsx @@ -11,7 +11,7 @@ import { act } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test/jest'; +import { findTestSubject, mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import type { SavedObjectReferenceWithContext } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; diff --git a/x-pack/plugins/spaces/public/space_list/space_list_internal.test.tsx b/x-pack/plugins/spaces/public/space_list/space_list_internal.test.tsx index 39ae339fb7e18..cc365e943a510 100644 --- a/x-pack/plugins/spaces/public/space_list/space_list_internal.test.tsx +++ b/x-pack/plugins/spaces/public/space_list/space_list_internal.test.tsx @@ -9,7 +9,7 @@ import { act } from '@testing-library/react'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import type { Space } from '../../common'; diff --git a/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx b/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx index e17c3e0078d42..7764bcc2c3b9c 100644 --- a/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx +++ b/x-pack/plugins/spaces/public/space_selector/space_selector.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import type { Space } from '../../common'; import { spacesManagerMock } from '../spaces_manager/mocks'; diff --git a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts index d09177a915d99..f1e4b767b934c 100644 --- a/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts +++ b/x-pack/plugins/spaces/public/spaces_manager/spaces_manager.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from 'src/core/public/mocks'; import { SpacesManager } from './spaces_manager'; diff --git a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts index c082a75cc894b..d8a1689ae3bd6 100644 --- a/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts +++ b/x-pack/plugins/spaces/server/default_space/default_space_service.test.ts @@ -8,7 +8,7 @@ import * as Rx from 'rxjs'; import { first } from 'rxjs/operators'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import type { Writable } from '@kbn/utility-types'; import type { CoreStatus, SavedObjectsRepository, ServiceStatusLevel } from 'src/core/server'; import { SavedObjectsErrorHelpers, ServiceStatusLevels } from 'src/core/server'; diff --git a/x-pack/plugins/stack_alerts/kibana.json b/x-pack/plugins/stack_alerts/kibana.json index 693bcf2f8dbca..acd9dcb374d10 100644 --- a/x-pack/plugins/stack_alerts/kibana.json +++ b/x-pack/plugins/stack_alerts/kibana.json @@ -5,7 +5,7 @@ "githubTeam": "kibana-alerting-services" }, "server": true, - "version": "8.1.0", + "version": "8.2.0", "kibanaVersion": "kibana", "requiredPlugins": [ "alerting", diff --git a/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.test.tsx index 3b7baac9b80e6..e5c8343fddf6d 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/components/index_select_popover.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { IndexSelectPopover } from './index_select_popover'; import { EuiComboBox } from '@elastic/eui'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx index 994eeadd50d3b..7ecdcd6dbce38 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/es_query/expression.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import 'brace'; import { of } from 'rxjs'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import EsQueryAlertTypeExpression from './expression'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx index bf46df5eabfa7..d0aaa9b8fda52 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import IndexThresholdAlertTypeExpression, { DEFAULT_VALUES } from './expression'; import { dataPluginMock } from 'src/plugins/data/public/mocks'; diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx index a27646c1643fa..ed667b4940f29 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ThresholdVisualization } from './visualization'; import { DataPublicPluginStart } from 'src/plugins/data/public/types'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; diff --git a/x-pack/plugins/task_manager/kibana.json b/x-pack/plugins/task_manager/kibana.json index 36e68ca00af81..3c28df441a0ab 100644 --- a/x-pack/plugins/task_manager/kibana.json +++ b/x-pack/plugins/task_manager/kibana.json @@ -1,7 +1,7 @@ { "id": "taskManager", "server": true, - "version": "8.1.0", + "version": "8.2.0", "owner": { "name": "Kibana Alerting", "githubTeam": "kibana-alerting-services" diff --git a/x-pack/plugins/timelines/public/components/empty_value/empty_value.test.tsx b/x-pack/plugins/timelines/public/components/empty_value/empty_value.test.tsx index be9a086d8dc5b..0e5ee638d29e8 100644 --- a/x-pack/plugins/timelines/public/components/empty_value/empty_value.test.tsx +++ b/x-pack/plugins/timelines/public/components/empty_value/empty_value.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; import { ThemeProvider } from 'styled-components'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { defaultToEmptyTag, diff --git a/x-pack/plugins/transform/public/app/common/navigation.tsx b/x-pack/plugins/transform/public/app/common/navigation.tsx index b847ac66de58e..b146d4401c633 100644 --- a/x-pack/plugins/transform/public/app/common/navigation.tsx +++ b/x-pack/plugins/transform/public/app/common/navigation.tsx @@ -13,5 +13,5 @@ import { SECTION_SLUG } from '../constants'; export const RedirectToTransformManagement: FC = () => ; export const RedirectToCreateTransform: FC<{ savedObjectId: string }> = ({ savedObjectId }) => ( - + ); diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts index 300c9c84993a1..01cb39ac87fa8 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts @@ -110,6 +110,7 @@ export const usePivotData = ( getDataGridSchemaFromESFieldType, formatHumanReadableDateTimeSeconds, multiColumnSortFactory, + getNestedOrEscapedVal, useDataGrid, INDEX_STATUS, }, @@ -235,7 +236,12 @@ export const usePivotData = ( }, [indexPatternTitle, JSON.stringify([requestPayload, query, combinedRuntimeMappings])]); if (sortingColumns.length > 0) { - tableItems.sort(multiColumnSortFactory(sortingColumns)); + const sortingColumnsWithTypes = sortingColumns.map((c) => ({ + ...c, + // Since items might contain undefined/null values, we want to accurate find the data type + type: typeof tableItems.find((item) => getNestedOrEscapedVal(item, c.id) !== undefined), + })); + tableItems.sort(multiColumnSortFactory(sortingColumnsWithTypes)); } const pageData = tableItems.slice( diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index e004a074a2922..75ed5c10f0483 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -486,18 +486,20 @@ export const StepDetailsForm: FC = React.memo( aria-label={i18n.translate( 'xpack.transform.stepDetailsForm.destinationIngestPipelineAriaLabel', { - defaultMessage: 'Select an ingest pipeline', + defaultMessage: 'Select an ingest pipeline (optional)', } )} placeholder={i18n.translate( 'xpack.transform.stepDetailsForm.destinationIngestPipelineComboBoxPlaceholder', { - defaultMessage: 'Select an ingest pipeline', + defaultMessage: 'Select an ingest pipeline (optional)', } )} singleSelection={{ asPlainText: true }} options={ingestPipelineNames.map((label: string) => ({ label }))} - selectedOptions={[{ label: destinationIngestPipeline }]} + selectedOptions={ + destinationIngestPipeline !== '' ? [{ label: destinationIngestPipeline }] : [] + } onChange={(options) => setDestinationIngestPipeline(options[0]?.label ?? '')} /> diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx index 60423e0eb4eb0..ccf65074be8be 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_summary.tsx @@ -26,6 +26,7 @@ export const StepDetailsSummary: FC = React.memo((props transformFrequency, transformSettingsMaxPageSearchSize, destinationIndex, + destinationIngestPipeline, touched, indexPatternTimeField, } = props; @@ -78,6 +79,19 @@ export const StepDetailsSummary: FC = React.memo((props )} + {destinationIngestPipeline !== undefined && destinationIngestPipeline !== '' && ( + + {destinationIngestPipeline} + + )} + {isContinuousModeEnabled && ( ( direction: sortDirection, }, }; - return { onTableChange, pagination, sorting }; } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2d72bd20f7712..8c9548e9b1f8e 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2956,6 +2956,35 @@ "expressionTagcloud.functions.tagcloudHelpText": "Tagcloudのビジュアライゼーションです。", "expressionTagcloud.renderer.tagcloud.displayName": "Tag Cloudのビジュアライゼーションです", "expressionTagcloud.renderer.tagcloud.helpDescription": "Tag Cloudを表示", + "expressionPartitionVis.reusable.function.dimension.buckets": "スライス", + "expressionPartitionVis.reusable.function.args.legendDisplayHelpText": "グラフ凡例を表示", + "expressionPartitionVis.reusable.function.args.addTooltipHelpText": "スライスにカーソルを置いたときにツールチップを表示", + "expressionPartitionVis.reusable.function.args.bucketsHelpText": "バケットディメンション構成", + "expressionPartitionVis.pieVis.function.args.distinctColorsHelpText": "スライスごとに異なる色をマッピングします。同じ値のスライスは同じ色になります", + "expressionPartitionVis.reusable.function.args.isDonutHelpText": "円グラフをドーナツグラフとして表示します", + "expressionPartitionVis.reusable.function.args.labelsHelpText": "円グラフラベル構成", + "expressionPartitionVis.reusable.function.args.legendPositionHelpText": "グラフの上、下、左、右に凡例を配置", + "expressionPartitionVis.reusable.function.args.maxLegendLinesHelpText": "凡例項目ごとの行数を定義します", + "expressionPartitionVis.reusable.function.args.metricHelpText": "メトリックディメンション構成", + "expressionPartitionVis.reusable.function.args.nestedLegendHelpText": "詳細凡例を表示", + "expressionPartitionVis.reusable.function.args.paletteHelpText": "グラフパレット名を定義します", + "expressionPartitionVis.reusable.function.args.splitColumnHelpText": "列ディメンション構成で分割", + "expressionPartitionVis.reusable.function.args.splitRowHelpText": "行ディメンション構成で分割", + "expressionPartitionVis.reusable.function.args.truncateLegendHelpText": "凡例項目が切り捨てられるかどうかを定義します", + "expressionPartitionVis.reusable.function.dimension.metric": "スライスサイズ", + "expressionPartitionVis.reusable.function.dimension.splitcolumn": "列分割", + "expressionPartitionVis.reusable.function.dimension.splitrow": "行分割", + "expressionPartitionVis.partitionLabels.function.help": "円グラフラベルオブジェクトを生成します", + "expressionPartitionVis.partitionLabels.function.args.percentDecimals.help": "割合として値に表示される10進数を定義します", + "expressionPartitionVis.partitionLabels.function.args.position.help": "ラベル位置を定義します", + "expressionPartitionVis.partitionLabels.function.args.values.help": "スライス内の値を定義します", + "expressionPartitionVis.partitionLabels.function.args.valuesFormat.help": "値の形式を定義します", + "expressionPartitionVis.pieVis.function.help": "パイビジュアライゼーション", + "expressionPartitionVis.legend.filterForValueButtonAriaLabel": "値でフィルター", + "expressionPartitionVis.legend.filterOptionsLegend": "{legendDataLabel}、フィルターオプション", + "expressionPartitionVis.legend.filterOutValueButtonAriaLabel": "値を除外", + "expressionPartitionVis.negativeValuesFound": "円/ドーナツグラフは負の値では表示できません。", + "expressionPartitionVis.noResultsFoundTitle": "結果が見つかりませんでした", "fieldFormats.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数字フォーマット", "fieldFormats.advancedSettings.format.bytesFormatText": "「バイト」フォーマットのデフォルト{numeralFormatLink}です", "fieldFormats.advancedSettings.format.bytesFormatTitle": "バイトフォーマット", @@ -5175,7 +5204,6 @@ "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "円グラフのレガシーグラフライブラリ", "visTypePie.controls.truncateLabel": "切り捨て", "visTypePie.controls.truncateTooltip": "グラフ外に配置されたラベルの文字数。", - "visTypePie.editors.pie.addLegendLabel": "凡例を表示", "visTypePie.editors.pie.decimalSliderLabel": "割合の最大小数点桁数", "visTypePie.editors.pie.distinctColorsLabel": "スライスごとに異なる色を使用", "visTypePie.editors.pie.donutLabel": "ドーナッツ", @@ -6390,8 +6418,6 @@ "xpack.apm.alertTypes.transactionErrorRate.description": "サービスのトランザクションエラー率が定義されたしきい値を超過したときにアラートを発行します。", "xpack.apm.analyzeDataButton.label": "データの探索", "xpack.apm.analyzeDataButton.tooltip": "データの探索では、任意のディメンションの結果データを選択してフィルタリングし、パフォーマンスの問題の原因または影響を調査することができます。", - "xpack.apm.analyzeDataButtonLabel": "データの探索", - "xpack.apm.analyzeDataButtonLabel.message": "データの探索では、任意のディメンションの結果データを選択してフィルタリングし、パフォーマンスの問題の原因または影響を調査することができます。", "xpack.apm.anomaly_detection.error.invalid_license": "異常検知を使用するには、Elastic Platinumライセンスのサブスクリプションが必要です。このライセンスがあれば、機械学習を活用して、サービスを監視できます。", "xpack.apm.anomaly_detection.error.missing_read_privileges": "異常検知ジョブを表示するには、機械学習およびAPMの「読み取り」権限が必要です", "xpack.apm.anomaly_detection.error.missing_write_privileges": "異常検知ジョブを作成するには、機械学習およびAPMの「書き込み」権限が必要です", @@ -6433,7 +6459,6 @@ "xpack.apm.chart.error": "データの取得時にエラーが発生しました。再試行してください", "xpack.apm.chart.memorySeries.systemAverageLabel": "平均", "xpack.apm.chart.memorySeries.systemMaxLabel": "最高", - "xpack.apm.clearFilters": "フィルターを消去", "xpack.apm.compositeSpanCallsLabel": "、{count}件の呼び出し、平均{duration}", "xpack.apm.compositeSpanDurationLabel": "平均時間", "xpack.apm.correlations.cancelButtonTitle": "キャンセル", @@ -6501,12 +6526,6 @@ "xpack.apm.correlations.progressAriaLabel": "進捗", "xpack.apm.correlations.progressTitle": "進捗状況: {progress}%", "xpack.apm.correlations.refreshButtonTitle": "更新", - "xpack.apm.csm.breakdownFilter.browser": "ブラウザー", - "xpack.apm.csm.breakdownFilter.device": "デバイス", - "xpack.apm.csm.breakdownFilter.location": "場所", - "xpack.apm.csm.breakDownFilter.noBreakdown": "内訳なし", - "xpack.apm.csm.breakdownFilter.os": "OS", - "xpack.apm.csm.pageViews.analyze": "分析", "xpack.apm.customLink.buttom.create": "カスタムリンクを作成", "xpack.apm.customLink.buttom.create.title": "作成", "xpack.apm.customLink.buttom.manage": "カスタムリンクを管理", @@ -6526,7 +6545,6 @@ "xpack.apm.deprecations.steps.switch": "[Elasticエージェントに切り替える]をクリックします。手順が案内されます。", "xpack.apm.emptyMessage.noDataFoundDescription": "別の時間範囲を試すか検索フィルターをリセットしてください。", "xpack.apm.emptyMessage.noDataFoundLabel": "データが見つかりません。", - "xpack.apm.emptyState.loadingMessage": "読み込み中…", "xpack.apm.environmentsSelectCustomOptionText": "新しい環境として\\{searchValue\\}を追加", "xpack.apm.environmentsSelectPlaceholder": "環境を選択", "xpack.apm.error.prompt.body": "詳細はブラウザの開発者コンソールをご確認ください。", @@ -6734,7 +6752,6 @@ "xpack.apm.localFilters.titles.os": "OS", "xpack.apm.localFilters.titles.serviceName": "サービス名", "xpack.apm.localFilters.titles.transactionUrl": "URL", - "xpack.apm.localFiltersTitle": "フィルター", "xpack.apm.managedTable.errorMessage": "取得できませんでした", "xpack.apm.managedTable.loadingDescription": "読み込み中…", "xpack.apm.metrics.transactionChart.machineLearningLabel": "機械学習:", @@ -6769,56 +6786,6 @@ "xpack.apm.propertiesTable.tabs.logStacktraceLabel": "スタックトレース", "xpack.apm.propertiesTable.tabs.metadataLabel": "メタデータ", "xpack.apm.propertiesTable.tabs.timelineLabel": "Timeline", - "xpack.apm.rum.coreVitals.dataUndefined": "N/A", - "xpack.apm.rum.coreVitals.fcp": "初回コンテンツの描画", - "xpack.apm.rum.coreVitals.fcpTooltip": "初回コンテンツの描画(FCP)は初期のレンダリングに集中し、ページの読み込みが開始してから、ページのコンテンツのいずれかの部分が画面に表示されるときまでの時間を測定します。", - "xpack.apm.rum.coreVitals.tbt": "合計ブロック時間", - "xpack.apm.rum.coreVitals.tbtTooltip": "合計ブロック時間(TBT)は、初回コンテンツの描画からトランザクションが完了したときまでに発生する、各長いタスクのブロック時間(50 ミリ秒超)の合計です。", - "xpack.apm.rum.dashboard.backend": "バックエンド", - "xpack.apm.rum.dashboard.dataMissing": "N/A", - "xpack.apm.rum.dashboard.frontend": "フロントエンド", - "xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages": "高トラフィックページ", - "xpack.apm.rum.dashboard.impactfulMetrics.jsErrors": "JavaScript エラー", - "xpack.apm.rum.dashboard.overall.label": "全体", - "xpack.apm.rum.dashboard.pageLoad.label": "ページの読み込み", - "xpack.apm.rum.dashboard.pageLoadDistribution.label": "ページ読み込み分布", - "xpack.apm.rum.dashboard.pageLoadDuration.label": "ページ読み込み時間", - "xpack.apm.rum.dashboard.pageLoadTime.label": "ページ読み込み時間(秒)", - "xpack.apm.rum.dashboard.pageLoadTimes.label": "ページ読み込み時間", - "xpack.apm.rum.dashboard.pagesLoaded.label": "ページが読み込まれました", - "xpack.apm.rum.dashboard.pageViews": "合計ページビュー", - "xpack.apm.rum.dashboard.resetZoom.label": "ズームをリセット", - "xpack.apm.rum.dashboard.tooltips.backEnd": "バックエンド時間は、最初の 1 バイトを受信するまでの時間(TTFB)です。これは、要求が実行された後、最初の応答パケットが受信された時点です。", - "xpack.apm.rum.dashboard.tooltips.frontEnd": "フロントエンド時間は、合計ページ読み込み時間からバックエンド時間を減算した時間です。", - "xpack.apm.rum.dashboard.tooltips.totalPageLoad": "合計はすべてのページ読み込み時間です。", - "xpack.apm.rum.dashboard.totalPageLoad": "合計", - "xpack.apm.rum.filterGroup.breakdown": "内訳", - "xpack.apm.rum.filterGroup.coreWebVitals": "コアWebバイタル", - "xpack.apm.rum.filterGroup.seconds": "秒", - "xpack.apm.rum.filterGroup.selectBreakdown": "内訳を選択", - "xpack.apm.rum.filters.filterByUrl": "IDでフィルタリング", - "xpack.apm.rum.filters.searchResults": "{total}件の検索結果", - "xpack.apm.rum.filters.select": "選択してください", - "xpack.apm.rum.filters.topPages": "上位のページ", - "xpack.apm.rum.filters.url": "Url", - "xpack.apm.rum.filters.url.loadingResults": "結果を読み込み中", - "xpack.apm.rum.filters.url.noResults": "結果がありません", - "xpack.apm.rum.jsErrors.errorMessage": "エラーメッセージ", - "xpack.apm.rum.jsErrors.errorRate": "エラー率", - "xpack.apm.rum.jsErrors.impactedPageLoads": "影響を受けるページ読み込み数", - "xpack.apm.rum.jsErrors.totalErrors": "合計エラー数", - "xpack.apm.rum.jsErrorsTable.errorMessage": "取得できませんでした", - "xpack.apm.rum.uxMetrics.longestLongTasks": "最長タスク時間", - "xpack.apm.rum.uxMetrics.longestLongTasksTooltip": "最も長いタスクの時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。", - "xpack.apm.rum.uxMetrics.noOfLongTasks": "時間がかかるタスク数", - "xpack.apm.rum.uxMetrics.noOfLongTasksTooltip": "長いタスクの数。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。", - "xpack.apm.rum.uxMetrics.sumLongTasks": "時間がかかるタスクの合計時間", - "xpack.apm.rum.uxMetrics.sumLongTasksTooltip": "長いタスクの合計時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。", - "xpack.apm.rum.visitorBreakdown": "アクセスユーザー内訳", - "xpack.apm.rum.visitorBreakdown.browser": "ブラウザー", - "xpack.apm.rum.visitorBreakdown.operatingSystem": "オペレーティングシステム", - "xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration": "平均ページ読み込み時間", - "xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion": "地域別ページ読み込み時間(平均)", "xpack.apm.searchInput.filter": "フィルター...", "xpack.apm.selectPlaceholder": "オプションを選択:", "xpack.apm.serviceDependencies.breakdownChartTitle": "依存関係にかかった時間", @@ -7162,9 +7129,6 @@ "xpack.apm.transactionDetails.statusCode": "ステータスコード", "xpack.apm.transactionDetails.syncBadgeAsync": "非同期", "xpack.apm.transactionDetails.syncBadgeBlocking": "ブロック", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaDescription": "失敗したトランザクションの相関関係はGAではありません。不具合が発生したら報告してください。", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaLabel": "ベータ", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaTitle": "失敗したトランザクションの相関関係", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsLabel": "失敗したトランザクションの相関関係", "xpack.apm.transactionDetails.tabs.latencyLabel": "遅延の相関関係", "xpack.apm.transactionDetails.tabs.traceSamplesLabel": "トレースのサンプル", @@ -7349,28 +7313,6 @@ "xpack.apm.tutorial.windowsServerInstructions.textPost": "注:システムでスクリプトの実行が無効な場合、スクリプトを実行するために現在のセッションの実行ポリシーの設定が必要となります。例:{command}。", "xpack.apm.tutorial.windowsServerInstructions.textPre": "1.[ダウンロードページ]({downloadPageLink})から APM Server Windows zip ファイルをダウンロードします。\n2.zip ファイルの内容を {zipFileExtractFolder} に抽出します。\n3.「{apmServerDirectory} ディレクトリの名前を「APM-Server」に変更します。\n4.管理者としてPowerShellプロンプトを開きます(PowerShellアイコンを右クリックして「管理者として実行」を選択します)。Windows XPをご使用の場合、PowerShellのダウンロードとインストールが必要な場合があります。\n5.PowerShell プロンプトで次のコマンドを実行し、APM Server を Windows サービスとしてインストールします。", "xpack.apm.unitLabel": "単位を選択", - "xpack.apm.urlFilter.wildcard": "ワイルドカード*{wildcard}*を使用", - "xpack.apm.ux.breadcrumbs.dashboard": "ダッシュボード", - "xpack.apm.ux.breadcrumbs.root": "ユーザーエクスペリエンス", - "xpack.apm.ux.jsErrors.percent": "{pageLoadPercent} %", - "xpack.apm.ux.localFilters.titles.webApplication": "Webアプリケーション", - "xpack.apm.ux.median": "中間", - "xpack.apm.ux.metrics": "メトリック", - "xpack.apm.ux.overview.agent.description": "APMエージェントを使用して、APMデータを収集します。多数の一般的な言語では、エージェントを使用することで処理が簡単になっています。", - "xpack.apm.ux.overview.agent.title": "APM統合を追加", - "xpack.apm.ux.overview.beatsCard.description": "APMエージェントでRUMを有効にして、ユーザーエクスペリエンスデータを収集します。", - "xpack.apm.ux.overview.beatsCard.title": "APM統合を追加", - "xpack.apm.ux.overview.heading": "ダッシュボード", - "xpack.apm.ux.overview.solutionName": "Observability", - "xpack.apm.ux.percentile.50thMedian": "50 番目(中央値)", - "xpack.apm.ux.percentile.75th": "75番目", - "xpack.apm.ux.percentile.90th": "90番目", - "xpack.apm.ux.percentile.95th": "95番目", - "xpack.apm.ux.percentile.99th": "99番目", - "xpack.apm.ux.percentile.label": "パーセンタイル", - "xpack.apm.ux.percentiles.label": "{value} パーセンタイル", - "xpack.apm.ux.title": "ダッシュボード", - "xpack.apm.ux.visitorBreakdown.noData": "データがありません。", "xpack.apm.views.dependencies.title": "依存関係", "xpack.apm.views.dependenciesInventory.title": "依存関係", "xpack.apm.views.errors.title": "エラー", @@ -7393,6 +7335,81 @@ "xpack.apm.views.transactions.title": "トランザクション", "xpack.apm.waterfall.errorCount": "{errorCount, plural, one {関連するエラーを表示} other {View # 件の関連するエラーを表示}}", "xpack.apm.waterfall.exceedsMax": "このトレースの項目数は表示されている範囲を超えています", + "xpack.ux.breakdownFilter.browser": "ブラウザー", + "xpack.ux.breakdownFilter.device": "デバイス", + "xpack.ux.breakdownFilter.location": "場所", + "xpack.ux.breakDownFilter.noBreakdown": "内訳なし", + "xpack.ux.breakdownFilter.os": "OS", + "xpack.ux.pageViews.analyze": "分析", + "xpack.ux.coreVitals.dataUndefined": "N/A", + "xpack.ux.coreVitals.fcp": "初回コンテンツの描画", + "xpack.ux.coreVitals.fcpTooltip": "初回コンテンツの描画(FCP)は初期のレンダリングに集中し、ページの読み込みが開始してから、ページのコンテンツのいずれかの部分が画面に表示されるときまでの時間を測定します。", + "xpack.ux.coreVitals.tbt": "合計ブロック時間", + "xpack.ux.coreVitals.tbtTooltip": "合計ブロック時間(TBT)は、初回コンテンツの描画からトランザクションが完了したときまでに発生する、各長いタスクのブロック時間(50 ミリ秒超)の合計です。", + "xpack.ux.dashboard.backend": "バックエンド", + "xpack.ux.dashboard.dataMissing": "N/A", + "xpack.ux.dashboard.frontend": "フロントエンド", + "xpack.ux.dashboard.impactfulMetrics.highTrafficPages": "高トラフィックページ", + "xpack.ux.dashboard.impactfulMetrics.jsErrors": "JavaScript エラー", + "xpack.ux.dashboard.overall.label": "全体", + "xpack.ux.dashboard.pageLoad.label": "ページの読み込み", + "xpack.ux.dashboard.pageLoadDistribution.label": "ページ読み込み分布", + "xpack.ux.dashboard.pageLoadDuration.label": "ページ読み込み時間", + "xpack.ux.dashboard.pageLoadTime.label": "ページ読み込み時間(秒)", + "xpack.ux.dashboard.pageLoadTimes.label": "ページ読み込み時間", + "xpack.ux.dashboard.pagesLoaded.label": "ページが読み込まれました", + "xpack.ux.dashboard.pageViews": "合計ページビュー", + "xpack.ux.dashboard.resetZoom.label": "ズームをリセット", + "xpack.ux.dashboard.tooltips.backEnd": "バックエンド時間は、最初の 1 バイトを受信するまでの時間(TTFB)です。これは、要求が実行された後、最初の応答パケットが受信された時点です。", + "xpack.ux.dashboard.tooltips.frontEnd": "フロントエンド時間は、合計ページ読み込み時間からバックエンド時間を減算した時間です。", + "xpack.ux.dashboard.tooltips.totalPageLoad": "合計はすべてのページ読み込み時間です。", + "xpack.ux.dashboard.totalPageLoad": "合計", + "xpack.ux.filterGroup.breakdown": "内訳", + "xpack.ux.filterGroup.coreWebVitals": "コアWebバイタル", + "xpack.ux.filterGroup.seconds": "秒", + "xpack.ux.filterGroup.selectBreakdown": "内訳を選択", + "xpack.ux.filters.filterByUrl": "IDでフィルタリング", + "xpack.ux.filters.searchResults": "{total}件の検索結果", + "xpack.ux.filters.select": "選択してください", + "xpack.ux.filters.topPages": "上位のページ", + "xpack.ux.filters.url": "Url", + "xpack.ux.filters.url.loadingResults": "結果を読み込み中", + "xpack.ux.filters.url.noResults": "結果がありません", + "xpack.ux.jsErrors.errorMessage": "エラーメッセージ", + "xpack.ux.jsErrors.errorRate": "エラー率", + "xpack.ux.jsErrors.impactedPageLoads": "影響を受けるページ読み込み数", + "xpack.ux.jsErrors.totalErrors": "合計エラー数", + "xpack.ux.jsErrorsTable.errorMessage": "取得できませんでした", + "xpack.ux.uxMetrics.longestLongTasks": "最長タスク時間", + "xpack.ux.uxMetrics.longestLongTasksTooltip": "最も長いタスクの時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。", + "xpack.ux.uxMetrics.noOfLongTasks": "時間がかかるタスク数", + "xpack.ux.uxMetrics.noOfLongTasksTooltip": "長いタスクの数。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。", + "xpack.ux.uxMetrics.sumLongTasks": "時間がかかるタスクの合計時間", + "xpack.ux.uxMetrics.sumLongTasksTooltip": "長いタスクの合計時間。長いタスクは、UI スレッドを長時間(50 ミリ秒以上)独占し、他の重要なタスク(フレームレートや入力レイテンシ)の実行を妨害するユーザーアクティビティまたはブラウザータスクとして定義されます。", + "xpack.ux.visitorBreakdown": "アクセスユーザー内訳", + "xpack.ux.visitorBreakdown.browser": "ブラウザー", + "xpack.ux.visitorBreakdown.operatingSystem": "オペレーティングシステム", + "xpack.ux.visitorBreakdownMap.avgPageLoadDuration": "平均ページ読み込み時間", + "xpack.ux.visitorBreakdownMap.pageLoadDurationByRegion": "地域別ページ読み込み時間(平均)", + "xpack.ux.breadcrumbs.dashboard": "ダッシュボード", + "xpack.ux.breadcrumbs.root": "ユーザーエクスペリエンス", + "xpack.ux.jsErrors.percent": "{pageLoadPercent} %", + "xpack.ux.localFilters.titles.webApplication": "Webアプリケーション", + "xpack.ux.median": "中間", + "xpack.ux.metrics": "メトリック", + "xpack.ux.overview.beatsCard.description": "APMエージェントでRUMを有効にして、ユーザーエクスペリエンスデータを収集します。", + "xpack.ux.overview.beatsCard.title": "APM統合を追加", + "xpack.ux.overview.heading": "ダッシュボード", + "xpack.ux.overview.solutionName": "オブザーバビリティ", + "xpack.ux.percentile.50thMedian": "50 番目(中央値)", + "xpack.ux.percentile.75th": "75番目", + "xpack.ux.percentile.90th": "90番目", + "xpack.ux.percentile.95th": "95番目", + "xpack.ux.percentile.99th": "99番目", + "xpack.ux.percentile.label": "パーセンタイル", + "xpack.ux.percentiles.label": "{value} パーセンタイル", + "xpack.ux.title": "ダッシュボード", + "xpack.ux.visitorBreakdown.noData": "データがありません。", "xpack.banners.settings.backgroundColor.description": "バナーの背景色。{subscriptionLink}", "xpack.banners.settings.backgroundColor.title": "バナー背景色", "xpack.banners.settings.placement.description": "Elasticヘッダーの上に、このスペースの上部のバナーを表示します。{subscriptionLink}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 97f6c16210de8..20f0748703770 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -923,255 +923,8 @@ "xpack.lens.xyVisualization.stackedPercentageBarLabel": "垂直百分比条形图", "xpack.lens.xyVisualization.xyLabel": "XY", "advancedSettings.advancedSettingsLabel": "高级设置", - "advancedSettings.badge.readOnly.text": "只读", - "advancedSettings.badge.readOnly.tooltip": "无法保存高级设置", - "advancedSettings.callOutCautionDescription": "此处请谨慎操作,这些设置仅供高级用户使用。您在这里所做的更改可能使 Kibana 的大部分功能出现问题。这些设置有一部分可能未在文档中说明、不受支持或是实验性设置。如果字段有默认值,将字段留空会将其设置为默认值,其他配置指令可能不接受其默认值。删除定制设置会将其从 Kibana 的配置中永久删除。", - "advancedSettings.callOutCautionTitle": "注意:在这里您可能会使问题出现", - "advancedSettings.categoryNames.dashboardLabel": "仪表板", - "advancedSettings.categoryNames.discoverLabel": "Discover", - "advancedSettings.categoryNames.generalLabel": "常规", - "advancedSettings.categoryNames.machineLearningLabel": "Machine Learning", - "advancedSettings.categoryNames.notificationsLabel": "通知", - "advancedSettings.categoryNames.observabilityLabel": "Observability", - "advancedSettings.categoryNames.reportingLabel": "报告", - "advancedSettings.categoryNames.searchLabel": "搜索", - "advancedSettings.categoryNames.securitySolutionLabel": "安全解决方案", - "advancedSettings.categoryNames.timelionLabel": "Timelion", - "advancedSettings.categoryNames.visualizationsLabel": "可视化", - "advancedSettings.categorySearchLabel": "类别", - "advancedSettings.featureCatalogueTitle": "定制您的 Kibana 体验 — 更改日期格式、打开深色模式,等等。", - "advancedSettings.field.changeImageLinkAriaLabel": "更改 {ariaName}", - "advancedSettings.field.changeImageLinkText": "更改图片", - "advancedSettings.field.codeEditorSyntaxErrorMessage": "JSON 语法无效", - "advancedSettings.field.customSettingAriaLabel": "定制设置", - "advancedSettings.field.customSettingTooltip": "定制设置", - "advancedSettings.field.defaultValueText": "默认值:{value}", - "advancedSettings.field.defaultValueTypeJsonText": "默认值:{value}", - "advancedSettings.field.deprecationClickAreaLabel": "单击以查看 {settingName} 的过时文档。", - "advancedSettings.field.helpText": "此设置已由 Kibana 服务器覆盖,无法更改。", - "advancedSettings.field.imageChangeErrorMessage": "图片无法保存", - "advancedSettings.field.invalidIconLabel": "无效", - "advancedSettings.field.offLabel": "关闭", - "advancedSettings.field.onLabel": "开启", - "advancedSettings.field.resetToDefaultLinkAriaLabel": "将 {ariaName} 重置为默认值", - "advancedSettings.field.resetToDefaultLinkText": "重置为默认值", - "advancedSettings.field.settingIsUnsaved": "设备当前未保存。", - "advancedSettings.field.unsavedIconLabel": "未保存", - "advancedSettings.form.cancelButtonLabel": "取消更改", - "advancedSettings.form.clearNoSearchResultText": "(清除搜索)", - "advancedSettings.form.clearSearchResultText": "(清除搜索)", - "advancedSettings.form.countOfSettingsChanged": "{unsavedCount} 个未保存{unsavedCount, plural, other {设置} }{hiddenCount, plural, =0 {} other {,# 个已隐藏} }", - "advancedSettings.form.noSearchResultText": "找不到 {queryText} {clearSearch} 的设置", - "advancedSettings.form.requiresPageReloadToastButtonLabel": "重新加载页面", - "advancedSettings.form.requiresPageReloadToastDescription": "一个或多个设置需要您重新加载页面才能生效。", - "advancedSettings.form.saveButtonLabel": "保存更改", - "advancedSettings.form.saveButtonTooltipWithInvalidChanges": "保存前请修复无效的设置。", - "advancedSettings.form.saveErrorMessage": "无法保存", - "advancedSettings.form.searchResultText": "搜索词隐藏了 {settingsCount} 个设置{clearSearch}", - "advancedSettings.pageTitle": "设置", - "advancedSettings.searchBar.unableToParseQueryErrorMessage": "无法解析查询", - "advancedSettings.searchBarAriaLabel": "搜索高级设置", - "advancedSettings.voiceAnnouncement.ariaLabel": "“高级设置”的结果信息", - "advancedSettings.voiceAnnouncement.noSearchResultScreenReaderMessage": "{sectionLenght, plural, other {# 个部分}}中有 {optionLenght, plural, other {# 个选项}}", - "advancedSettings.voiceAnnouncement.searchResultScreenReaderMessage": "您搜索了“{query}”。{sectionLenght, plural, other {# 个部分}}中有 {optionLenght, plural, other {# 个选项}}", - "alerts.documentationTitle": "查看文档", - "alerts.noPermissionsMessage": "要查看告警,必须对 Kibana 工作区中的告警功能有权限。有关详细信息,请联系您的 Kibana 管理员。", - "alerts.noPermissionsTitle": "需要 Kibana 功能权限", - "autocomplete.fieldRequiredError": "值不能为空", - "autocomplete.invalidDateError": "不是有效日期", - "autocomplete.invalidNumberError": "不是有效数字", - "autocomplete.loadingDescription": "正在加载……", - "autocomplete.selectField": "请首先选择字段......", - "bfetch.disableBfetchCompression": "禁用批量压缩", - "bfetch.disableBfetchCompressionDesc": "禁用批量压缩。这允许您对单个请求进行故障排查,但会增加响应大小。", - "charts.advancedSettings.visualization.colorMappingText": "使用兼容性调色板将值映射到图表中的特定颜色。", - "charts.advancedSettings.visualization.colorMappingTextDeprecation": "此设置已过时,在未来版本中将不受支持。", - "charts.advancedSettings.visualization.colorMappingTitle": "颜色映射", "charts.advancedSettings.visualization.useLegacyTimeAxis.description": "在 Lens、Discover、Visualize 和 TSVB 中为图表启用旧版时间轴", "charts.advancedSettings.visualization.useLegacyTimeAxis.name": "旧版图表时间轴", - "charts.colormaps.bluesText": "蓝色", - "charts.colormaps.greensText": "绿色", - "charts.colormaps.greenToRedText": "绿到红", - "charts.colormaps.greysText": "灰色", - "charts.colormaps.redsText": "红色", - "charts.colormaps.yellowToRedText": "黄到红", - "charts.colorPicker.clearColor": "重置颜色", - "charts.colorPicker.setColor.screenReaderDescription": "为值 {legendDataLabel} 设置颜色", - "charts.countText": "计数", - "charts.functions.palette.args.colorHelpText": "调色板颜色。接受 {html} 颜色名称 {hex}、{hsl}、{hsla}、{rgb} 或 {rgba}。", - "charts.functions.palette.args.gradientHelpText": "受支持时提供渐变的调色板?", - "charts.functions.palette.args.reverseHelpText": "反转调色板?", - "charts.functions.palette.args.stopHelpText": "调色板颜色停止。使用时,必须与每个颜色关联。", - "charts.functions.paletteHelpText": "创建颜色调色板。", - "charts.functions.systemPalette.args.nameHelpText": "调色板列表中的调色板名称", - "charts.functions.systemPaletteHelpText": "创建动态颜色调色板。", - "charts.legend.toggleLegendButtonAriaLabel": "切换图例", - "charts.legend.toggleLegendButtonTitle": "切换图例", - "charts.palettes.complimentaryLabel": "免费", - "charts.palettes.coolLabel": "冷", - "charts.palettes.customLabel": "定制", - "charts.palettes.defaultPaletteLabel": "默认", - "charts.palettes.grayLabel": "灰", - "charts.palettes.kibanaPaletteLabel": "兼容性", - "charts.palettes.negativeLabel": "负", - "charts.palettes.positiveLabel": "正", - "charts.palettes.statusLabel": "状态", - "charts.palettes.temperatureLabel": "温度", - "charts.palettes.warmLabel": "暖", - "charts.partialData.bucketTooltipText": "选定的时间范围不包括此整个存储桶。其可能包含部分数据。", - "console.autocomplete.addMethodMetaText": "方法", - "console.consoleDisplayName": "控制台", - "console.consoleMenu.copyAsCurlFailedMessage": "无法将请求复制为 cURL", - "console.consoleMenu.copyAsCurlMessage": "请求已复制为 cURL", - "console.deprecations.enabled.manualStepOneMessage": "打开 kibana.yml 配置文件。", - "console.deprecations.enabled.manualStepTwoMessage": "将“console.enabled”设置更改为“console.ui.enabled”。", - "console.deprecations.enabledMessage": "要禁止用户访问 Console UI,请使用“console.ui.enabled”设置,而不是“console.enabled”。", - "console.deprecations.enabledTitle": "“console.enabled”设置已过时", - "console.deprecations.proxyConfig.manualStepOneMessage": "打开 kibana.yml 配置文件。", - "console.deprecations.proxyConfig.manualStepThreeMessage": "使用“server.ssl.*”设置配置 Kibana 与 Elasticsearch 之间的安全连接。", - "console.deprecations.proxyConfig.manualStepTwoMessage": "移除“console.proxyConfig”设置。", - "console.deprecations.proxyConfigMessage": "配置“console.proxyConfig”已过时,将在 8.0.0 中移除。为保护 Kibana 与 Elasticsearch 之间的连接,请改为使用标准“server.ssl.*”设置。", - "console.deprecations.proxyConfigTitle": "“console.proxyConfig”设置已过时", - "console.deprecations.proxyFilter.manualStepOneMessage": "打开 kibana.yml 配置文件。", - "console.deprecations.proxyFilter.manualStepThreeMessage": "使用“server.ssl.*”设置配置 Kibana 与 Elasticsearch 之间的安全连接。", - "console.deprecations.proxyFilter.manualStepTwoMessage": "移除“console.proxyFilter”设置。", - "console.deprecations.proxyFilterMessage": "配置“console.proxyFilter”已过时,将在 8.0.0 中移除。为保护 Kibana 与 Elasticsearch 之间的连接,请改为使用标准“server.ssl.*”设置。", - "console.deprecations.proxyFilterTitle": "“console.proxyFilter”设置已过时", - "console.devToolsDescription": "跳过 cURL 并使用 JSON 接口在控制台中处理您的数据。", - "console.devToolsTitle": "与 Elasticsearch API 进行交互", - "console.exampleOutputTextarea": "开发工具控制台编辑器示例", - "console.helpPage.keyboardCommands.autoIndentDescription": "自动缩进当前请求", - "console.helpPage.keyboardCommands.closeAutoCompleteMenuDescription": "关闭自动完成菜单", - "console.helpPage.keyboardCommands.collapseAllScopesDescription": "折叠当前范围除外的所有范围。通过加按 Shift 键来展开。", - "console.helpPage.keyboardCommands.collapseExpandCurrentScopeDescription": "折叠/展开当前范围。", - "console.helpPage.keyboardCommands.jumpToPreviousNextRequestDescription": "跳转至前一/后一请求开头或结尾。", - "console.helpPage.keyboardCommands.openAutoCompleteDescription": "打开自动完成(即使未键入)", - "console.helpPage.keyboardCommands.openDocumentationDescription": "打开当前请求的文档", - "console.helpPage.keyboardCommands.selectCurrentlySelectedInAutoCompleteMenuDescription": "选择自动完成菜单中当前选定的词或最顶部的词", - "console.helpPage.keyboardCommands.submitRequestDescription": "提交请求", - "console.helpPage.keyboardCommands.switchFocusToAutoCompleteMenuDescription": "将焦点切换到自动完成菜单。使用箭头进一步选择词", - "console.helpPage.keyboardCommandsTitle": "键盘命令", - "console.helpPage.pageTitle": "帮助", - "console.helpPage.requestFormatDescription": "您可以在空白编辑器中键入一个或多个请求。Console 理解紧凑格式的请求:", - "console.helpPage.requestFormatTitle": "请求格式", - "console.historyPage.applyHistoryButtonLabel": "应用", - "console.historyPage.clearHistoryButtonLabel": "清除", - "console.historyPage.closehistoryButtonLabel": "关闭", - "console.historyPage.itemOfRequestListAriaLabel": "请求:{historyItem}", - "console.historyPage.noHistoryTextMessage": "没有可用的历史记录", - "console.historyPage.pageTitle": "历史记录", - "console.historyPage.requestListAriaLabel": "已发送请求的历史记录", - "console.inputTextarea": "开发工具控制台", - "console.loadFromDataUriErrorMessage": "无法从 URL 中的 load_from 查询参数加载数据", - "console.loadingError.buttonLabel": "重新加载控制台", - "console.loadingError.message": "尝试重新加载以获取最新的数据。", - "console.loadingError.title": "无法加载控制台", - "console.notification.error.couldNotSaveRequestTitle": "无法将请求保存到控制台历史记录。", - "console.notification.error.historyQuotaReachedMessage": "请求历史记录已满。请清除控制台历史记录以保存新的请求。", - "console.notification.error.noRequestSelectedTitle": "未选择任何请求。将鼠标置于请求内即可选择。", - "console.notification.error.unknownErrorTitle": "未知请求错误", - "console.outputTextarea": "开发工具控制台输出", - "console.pageHeading": "控制台", - "console.requestInProgressBadgeText": "进行中的请求", - "console.requestOptions.autoIndentButtonLabel": "自动缩进", - "console.requestOptions.copyAsUrlButtonLabel": "复制为 cURL", - "console.requestOptions.openDocumentationButtonLabel": "打开文档", - "console.requestOptionsButtonAriaLabel": "请求选项", - "console.requestTimeElapasedBadgeTooltipContent": "已用时间", - "console.sendRequestButtonTooltip": "单击以发送请求", - "console.settingsPage.autocompleteLabel": "自动完成", - "console.settingsPage.cancelButtonLabel": "取消", - "console.settingsPage.fieldsLabelText": "字段", - "console.settingsPage.fontSizeLabel": "字体大小", - "console.settingsPage.indicesAndAliasesLabelText": "索引和别名", - "console.settingsPage.jsonSyntaxLabel": "JSON 语法", - "console.settingsPage.pageTitle": "控制台设置", - "console.settingsPage.refreshButtonLabel": "刷新自动完成建议", - "console.settingsPage.refreshingDataDescription": "控制台通过查询 Elasticsearch 来刷新自动完成建议。如果您的集群较大或您的网络有限制,则自动刷新可能会造成问题。", - "console.settingsPage.refreshingDataLabel": "正在刷新自动完成建议", - "console.settingsPage.saveButtonLabel": "保存", - "console.settingsPage.templatesLabelText": "模板", - "console.settingsPage.tripleQuotesMessage": "在输出窗格中使用三重引号", - "console.settingsPage.wrapLongLinesLabelText": "长行换行", - "console.topNav.helpTabDescription": "帮助", - "console.topNav.helpTabLabel": "帮助", - "console.topNav.historyTabDescription": "历史记录", - "console.topNav.historyTabLabel": "历史记录", - "console.topNav.settingsTabDescription": "设置", - "console.topNav.settingsTabLabel": "设置", - "console.welcomePage.closeButtonLabel": "关闭", - "console.welcomePage.pageTitle": "欢迎使用 Console", - "console.welcomePage.quickIntroDescription": "Console UI 分为两个窗格:编辑器窗格(左)和响应窗格(右)。使用编辑器键入请求并将它们提交到 Elasticsearch。结果将显示在右侧的响应窗格中。", - "console.welcomePage.quickIntroTitle": "UI 简介", - "console.welcomePage.quickTips.cUrlFormatForRequestsDescription": "您可以粘贴 cURL 格式的请求,这些请求将转换成 Console 语法格式。", - "console.welcomePage.quickTips.keyboardShortcutsDescription": "学习“帮助”按钮下的键盘快捷方式。那里有非常实用的信息!", - "console.welcomePage.quickTips.resizeEditorDescription": "您可以通过拖动编辑器和输出窗格之间的分隔条来调整它们的大小。", - "console.welcomePage.quickTips.submitRequestDescription": "使用绿色三角按钮将请求提交到 ES。", - "console.welcomePage.quickTips.useWrenchMenuDescription": "使用扳手菜单执行其他有用的操作。", - "console.welcomePage.quickTipsTitle": "有几个需要您注意的有用提示", - "console.welcomePage.supportedRequestFormatDescription": "键入请求时,控制台将提供建议,您可以通过按 Enter/Tab 键来接受建议。这些建议基于请求结构以及索引和类型进行提供。", - "console.welcomePage.supportedRequestFormatTitle": "Console 理解紧凑格式的请求,类似于 cURL:", - "core.application.appContainer.loadingAriaLabel": "正在加载应用程序", - "core.application.appNotFound.pageDescription": "在此 URL 未找到任何应用程序。尝试返回或从菜单中选择应用。", - "core.application.appNotFound.title": "未找到应用程序", - "core.application.appRenderError.defaultTitle": "应用程序错误", - "core.chrome.browserDeprecationLink": "我们网站上的支持矩阵", - "core.chrome.browserDeprecationWarning": "本软件的未来版本将放弃对 Internet Explorer 的支持,请查看{link}。", - "core.chrome.legacyBrowserWarning": "您的浏览器不满足 Kibana 的安全要求。", - "core.deprecations.deprecations.fetchFailed.manualStepOneMessage": "请在 Kibana 服务器日志中查看错误消息。", - "core.deprecations.deprecations.fetchFailedMessage": "无法提取插件 {domainId} 的弃用信息。", - "core.deprecations.deprecations.fetchFailedTitle": "无法提取 {domainId} 的弃用信息", - "core.deprecations.elasticsearchSSL.manualSteps1": "将“{missingSetting}”设置添加到 kibana.yml。", - "core.deprecations.elasticsearchSSL.manualSteps2": "或者,如果不想使用相互 TLS 身份验证,请从 kibana.yml 中移除“{existingSetting}”。", - "core.deprecations.elasticsearchSSL.message": "同时使用“{existingSetting}”和“{missingSetting}”,以便 Kibana 将相互 TLS 身份验证用于 Elasticsearch。", - "core.deprecations.elasticsearchSSL.title": "使用不含“{missingSetting}”的“{existingSetting}”无效", - "core.deprecations.elasticsearchUsername.manualSteps1": "使用 elasticsearch-service-tokens CLI 工具为“elastic/kibana”服务帐户创建新的服务帐户令牌。", - "core.deprecations.elasticsearchUsername.manualSteps2": "将“elasticsearch.serviceAccountToken”设置添加到 kibana.yml。", - "core.deprecations.elasticsearchUsername.manualSteps3": "从 kibana.yml 中移除“elasticsearch.username”和“elasticsearch.password”。", - "core.deprecations.elasticsearchUsername.message": "Kibana 已配置为通过“{username}”用户验证到 Elasticsearch。改为使用服务帐户令牌。", - "core.deprecations.elasticsearchUsername.title": "使用“elasticsearch.username: {username}”已过时", - "core.deprecations.noCorrectiveAction": "无法自动解决此弃用。", - "core.euiAccordion.isLoading": "正在加载", - "core.euiBasicTable.noItemsMessage": "找不到项目", - "core.euiBasicTable.selectAllRows": "选择所有行", - "core.euiBasicTable.selectThisRow": "选择此行", - "core.euiBasicTable.tableAutoCaptionWithoutPagination": "此表包含 {itemCount} 行。", - "core.euiBasicTable.tableAutoCaptionWithPagination": "此表包含 {itemCount} 行,共有 {totalItemCount} 行;第 {page} 页,共 {pageCount} 页。", - "core.euiBasicTable.tableCaptionWithPagination": "{tableCaption};第 {page} 页,共 {pageCount} 页。", - "core.euiBasicTable.tablePagination": "表分页:{tableCaption}", - "core.euiBasicTable.tableSimpleAutoCaptionWithPagination": "此表包含 {itemCount} 行;第 {page} 页,共 {pageCount} 页。", - "core.euiBottomBar.customScreenReaderAnnouncement": "有称作 {landmarkHeading} 且页面级别控件位于文档结尾的新地区地标。", - "core.euiBottomBar.screenReaderAnnouncement": "有页面级别控件位于文档结尾的新地区地标。", - "core.euiBottomBar.screenReaderHeading": "页面级别控件", - "core.euiBreadcrumbs.collapsedBadge.ariaLabel": "查看折叠的痕迹导航", - "core.euiBreadcrumbs.nav.ariaLabel": "痕迹导航", - "core.euiCardSelect.select": "选择", - "core.euiCardSelect.selected": "已选定", - "core.euiCardSelect.unavailable": "不可用", - "core.euiCodeBlock.copyButton": "复制", - "core.euiCodeBlock.fullscreenCollapse": "折叠", - "core.euiCodeBlock.fullscreenExpand": "展开", - "core.euiCollapsedItemActions.allActions": "所有操作", - "core.euiColorPicker.alphaLabel": "Alpha 通道(不透明度)值", - "core.euiColorPicker.closeLabel": "按向下箭头键可打开包含颜色选项的弹出框", - "core.euiColorPicker.colorErrorMessage": "颜色值无效", - "core.euiColorPicker.colorLabel": "颜色值", - "core.euiColorPicker.openLabel": "按 Esc 键关闭弹出框", - "core.euiColorPicker.popoverLabel": "颜色选择对话框", - "core.euiColorPicker.transparent": "透明", - "core.euiColorPickerSwatch.ariaLabel": "选择 {color} 作为颜色", - "core.euiColorStops.screenReaderAnnouncement": "{label}:{readOnly} {disabled} 颜色停止点选取器。每个停止点由数字和相应颜色值构成。使用向下和向上箭头键选择单个停止点。按 Enter 键创建新的停止点。", - "core.euiColorStopThumb.buttonAriaLabel": "按 Enter 键修改此停止点。按 Esc 键聚焦该组", - "core.euiColorStopThumb.buttonTitle": "单击编辑,拖动重新定位", - "core.euiColorStopThumb.removeLabel": "删除此停止点", - "core.euiColorStopThumb.screenReaderAnnouncement": "打开颜色停止点编辑表单的弹出式窗口。按 Tab 键正向依次选择表单控件或按 Esc 键关闭此弹出式窗口。", - "core.euiColorStopThumb.stopErrorMessage": "值超出范围", - "core.euiColorStopThumb.stopLabel": "停止点值", - "core.euiColumnActions.hideColumn": "隐藏列", - "core.euiColumnActions.moveLeft": "左移", - "core.euiColumnActions.moveRight": "右移", - "core.euiColumnActions.sort": "排序 {schemaLabel}", - "core.euiColumnSelector.button": "列", "core.euiColumnSelector.buttonActivePlural": "{numberOfHiddenFields} 列已隐藏", "core.euiColumnSelector.buttonActiveSingular": "{numberOfHiddenFields} 列已隐藏", "core.euiColumnSelector.hideAll": "全部隐藏", @@ -2987,6 +2740,35 @@ "expressionTagcloud.functions.tagcloudHelpText": "标签云图可视化。", "expressionTagcloud.renderer.tagcloud.displayName": "标签云图可视化", "expressionTagcloud.renderer.tagcloud.helpDescription": "呈现标签云图", + "expressionPartitionVis.reusable.function.dimension.buckets": "切片", + "expressionPartitionVis.reusable.function.args.legendDisplayHelpText": "显示图表图例", + "expressionPartitionVis.reusable.function.args.addTooltipHelpText": "在切片上悬浮时显示工具提示", + "expressionPartitionVis.reusable.function.args.bucketsHelpText": "存储桶维度配置", + "expressionPartitionVis.pieVis.function.args.distinctColorsHelpText": "每个切片映射不同颜色。具有相同值的切片具有相同的颜色", + "expressionPartitionVis.reusable.function.args.isDonutHelpText": "将饼图显示为圆环图", + "expressionPartitionVis.reusable.function.args.labelsHelpText": "饼图标签配置", + "expressionPartitionVis.reusable.function.args.legendPositionHelpText": "将图例定位于图表的顶部、底部、左侧、右侧", + "expressionPartitionVis.reusable.function.args.maxLegendLinesHelpText": "定义每个图例项的行数", + "expressionPartitionVis.reusable.function.args.metricHelpText": "指标维度配置", + "expressionPartitionVis.reusable.function.args.nestedLegendHelpText": "显示更详细的图例", + "expressionPartitionVis.reusable.function.args.paletteHelpText": "定义图表调色板名称", + "expressionPartitionVis.reusable.function.args.splitColumnHelpText": "按列维度配置拆分", + "expressionPartitionVis.reusable.function.args.splitRowHelpText": "按行维度配置拆分", + "expressionPartitionVis.reusable.function.args.truncateLegendHelpText": "定义是否将截断图例项", + "expressionPartitionVis.reusable.function.dimension.metric": "切片大小", + "expressionPartitionVis.reusable.function.dimension.splitcolumn": "列拆分", + "expressionPartitionVis.reusable.function.dimension.splitrow": "行拆分", + "expressionPartitionVis.partitionLabels.function.help": "生成饼图标签对象", + "expressionPartitionVis.partitionLabels.function.args.percentDecimals.help": "定义在值中将显示为百分比的小数位数", + "expressionPartitionVis.partitionLabels.function.args.position.help": "定义标签位置", + "expressionPartitionVis.partitionLabels.function.args.values.help": "定义切片内的值", + "expressionPartitionVis.partitionLabels.function.args.valuesFormat.help": "定义值的格式", + "expressionPartitionVis.pieVis.function.help": "饼图可视化", + "expressionPartitionVis.legend.filterForValueButtonAriaLabel": "筛留值", + "expressionPartitionVis.legend.filterOptionsLegend": "{legendDataLabel}, 筛选选项", + "expressionPartitionVis.legend.filterOutValueButtonAriaLabel": "筛除值", + "expressionPartitionVis.negativeValuesFound": "饼图/圆环图无法使用负值进行呈现。", + "expressionPartitionVis.noResultsFoundTitle": "找不到结果", "fieldFormats.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数值格式", "fieldFormats.advancedSettings.format.bytesFormatText": "“字节”格式的默认{numeralFormatLink}", "fieldFormats.advancedSettings.format.bytesFormatTitle": "字节格式", @@ -5222,7 +5004,6 @@ "visTypePie.advancedSettings.visualization.legacyPieChartsLibrary.name": "饼图旧版图表库", "visTypePie.controls.truncateLabel": "截断", "visTypePie.controls.truncateTooltip": "标签位于图表之外的字符数。", - "visTypePie.editors.pie.addLegendLabel": "显示图例", "visTypePie.editors.pie.decimalSliderLabel": "百分比的最大小数位数", "visTypePie.editors.pie.distinctColorsLabel": "每个切片使用不同的颜色", "visTypePie.editors.pie.donutLabel": "圆环图", @@ -6442,8 +6223,6 @@ "xpack.apm.alertTypes.transactionErrorRate.description": "当服务中的事务错误率超过定义的阈值时告警。", "xpack.apm.analyzeDataButton.label": "浏览数据", "xpack.apm.analyzeDataButton.tooltip": "“浏览数据”允许您选择和筛选任意维度中的结果数据以及查找性能问题的原因或影响", - "xpack.apm.analyzeDataButtonLabel": "浏览数据", - "xpack.apm.analyzeDataButtonLabel.message": "“浏览数据”允许您选择和筛选任意维度中的结果数据以及查找性能问题的原因或影响。", "xpack.apm.anomaly_detection.error.invalid_license": "要使用异常检测,必须订阅 Elastic 白金级许可证。有了该许可证,您便可借助 Machine Learning 监测服务。", "xpack.apm.anomaly_detection.error.missing_read_privileges": "必须对 Machine Learning 和 APM 具有“读”权限,才能查看“异常检测”作业", "xpack.apm.anomaly_detection.error.missing_write_privileges": "必须对 Machine Learning 和 APM 具有“写”权限,才能创建“异常检测”作业", @@ -6485,7 +6264,6 @@ "xpack.apm.chart.error": "尝试提取数据时发生错误。请重试", "xpack.apm.chart.memorySeries.systemAverageLabel": "平均值", "xpack.apm.chart.memorySeries.systemMaxLabel": "最大值", - "xpack.apm.clearFilters": "清除筛选", "xpack.apm.compositeSpanCallsLabel": ",{count} 个调用,平均 {duration}", "xpack.apm.compositeSpanDurationLabel": "平均持续时间", "xpack.apm.correlations.cancelButtonTitle": "取消", @@ -6553,12 +6331,6 @@ "xpack.apm.correlations.progressAriaLabel": "进度", "xpack.apm.correlations.progressTitle": "进度:{progress}%", "xpack.apm.correlations.refreshButtonTitle": "刷新", - "xpack.apm.csm.breakdownFilter.browser": "浏览器", - "xpack.apm.csm.breakdownFilter.device": "设备", - "xpack.apm.csm.breakdownFilter.location": "位置", - "xpack.apm.csm.breakDownFilter.noBreakdown": "无细目", - "xpack.apm.csm.breakdownFilter.os": "OS", - "xpack.apm.csm.pageViews.analyze": "分析", "xpack.apm.customLink.buttom.create": "创建定制链接", "xpack.apm.customLink.buttom.create.title": "创建", "xpack.apm.customLink.buttom.manage": "管理定制链接", @@ -6578,7 +6350,6 @@ "xpack.apm.deprecations.steps.switch": "单击“切换到 Elastic 代理”。将指导您完成此过程", "xpack.apm.emptyMessage.noDataFoundDescription": "尝试其他时间范围或重置搜索筛选。", "xpack.apm.emptyMessage.noDataFoundLabel": "未找到任何数据。", - "xpack.apm.emptyState.loadingMessage": "正在加载……", "xpack.apm.environmentsSelectCustomOptionText": "将 \\{searchValue\\} 添加为新环境", "xpack.apm.environmentsSelectPlaceholder": "选择环境", "xpack.apm.error.prompt.body": "有关详情,请查看您的浏览器开发者控制台。", @@ -6789,7 +6560,6 @@ "xpack.apm.localFilters.titles.os": "OS", "xpack.apm.localFilters.titles.serviceName": "服务名称", "xpack.apm.localFilters.titles.transactionUrl": "URL", - "xpack.apm.localFiltersTitle": "筛选", "xpack.apm.managedTable.errorMessage": "无法提取", "xpack.apm.managedTable.loadingDescription": "正在加载……", "xpack.apm.metrics.transactionChart.machineLearningLabel": "Machine Learning", @@ -6825,56 +6595,6 @@ "xpack.apm.propertiesTable.tabs.logStacktraceLabel": "日志堆栈跟踪", "xpack.apm.propertiesTable.tabs.metadataLabel": "元数据", "xpack.apm.propertiesTable.tabs.timelineLabel": "时间线", - "xpack.apm.rum.coreVitals.dataUndefined": "不可用", - "xpack.apm.rum.coreVitals.fcp": "首次内容绘制", - "xpack.apm.rum.coreVitals.fcpTooltip": "首次内容绘制 (FCP) 侧重于初始渲染,并度量从页面开始加载到页面内容的任何部分显示在屏幕的时间。", - "xpack.apm.rum.coreVitals.tbt": "阻止总时间", - "xpack.apm.rum.coreVitals.tbtTooltip": "总阻塞时间 (TBT) 是指在首次内容绘制与事务完成之间发生的各个长任务的阻塞时间(持续时间超过50毫秒)之和。", - "xpack.apm.rum.dashboard.backend": "后端", - "xpack.apm.rum.dashboard.dataMissing": "不可用", - "xpack.apm.rum.dashboard.frontend": "前端", - "xpack.apm.rum.dashboard.impactfulMetrics.highTrafficPages": "高流量页面", - "xpack.apm.rum.dashboard.impactfulMetrics.jsErrors": "JavaScript 错误", - "xpack.apm.rum.dashboard.overall.label": "总体", - "xpack.apm.rum.dashboard.pageLoad.label": "页面加载", - "xpack.apm.rum.dashboard.pageLoadDistribution.label": "页面加载分布", - "xpack.apm.rum.dashboard.pageLoadDuration.label": "页面加载持续时间", - "xpack.apm.rum.dashboard.pageLoadTime.label": "页面加载时间(秒)", - "xpack.apm.rum.dashboard.pageLoadTimes.label": "页面加载时间", - "xpack.apm.rum.dashboard.pagesLoaded.label": "已加载页面", - "xpack.apm.rum.dashboard.pageViews": "页面总查看次数", - "xpack.apm.rum.dashboard.resetZoom.label": "重置缩放比例", - "xpack.apm.rum.dashboard.tooltips.backEnd": "后端时间表示接收到第一个字节所需的时间 (TTFB),即从请求发出后接收到第一个响应数据包的时间", - "xpack.apm.rum.dashboard.tooltips.frontEnd": "前端时间表示页面加载总持续时间减去后端时间", - "xpack.apm.rum.dashboard.tooltips.totalPageLoad": "合计表示整个页面加载持续时间", - "xpack.apm.rum.dashboard.totalPageLoad": "合计", - "xpack.apm.rum.filterGroup.breakdown": "细目", - "xpack.apm.rum.filterGroup.coreWebVitals": "网站体验核心指标", - "xpack.apm.rum.filterGroup.seconds": "秒", - "xpack.apm.rum.filterGroup.selectBreakdown": "选择细分", - "xpack.apm.rum.filters.filterByUrl": "按 URL 筛选", - "xpack.apm.rum.filters.searchResults": "{total} 项搜索结果", - "xpack.apm.rum.filters.select": "选择", - "xpack.apm.rum.filters.topPages": "排名靠前页面", - "xpack.apm.rum.filters.url": "URL", - "xpack.apm.rum.filters.url.loadingResults": "正在加载结果", - "xpack.apm.rum.filters.url.noResults": "没有可用结果", - "xpack.apm.rum.jsErrors.errorMessage": "错误消息", - "xpack.apm.rum.jsErrors.errorRate": "错误率", - "xpack.apm.rum.jsErrors.impactedPageLoads": "受影响的页面加载", - "xpack.apm.rum.jsErrors.totalErrors": "错误总数", - "xpack.apm.rum.jsErrorsTable.errorMessage": "无法提取", - "xpack.apm.rum.uxMetrics.longestLongTasks": "长任务最长持续时间", - "xpack.apm.rum.uxMetrics.longestLongTasksTooltip": "最长任务的持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。", - "xpack.apm.rum.uxMetrics.noOfLongTasks": "长任务数目", - "xpack.apm.rum.uxMetrics.noOfLongTasksTooltip": "长任务的数量。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。", - "xpack.apm.rum.uxMetrics.sumLongTasks": "长任务总持续时间", - "xpack.apm.rum.uxMetrics.sumLongTasksTooltip": "长任务的总持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。", - "xpack.apm.rum.visitorBreakdown": "访问者细分", - "xpack.apm.rum.visitorBreakdown.browser": "浏览器", - "xpack.apm.rum.visitorBreakdown.operatingSystem": "操作系统", - "xpack.apm.rum.visitorBreakdownMap.avgPageLoadDuration": "页面加载平均持续时间", - "xpack.apm.rum.visitorBreakdownMap.pageLoadDurationByRegion": "按区域列出的页面加载持续时间(平均值)", "xpack.apm.searchInput.filter": "筛选...", "xpack.apm.selectPlaceholder": "选择选项:", "xpack.apm.serviceDependencies.breakdownChartTitle": "依赖项花费的时间", @@ -7221,9 +6941,6 @@ "xpack.apm.transactionDetails.statusCode": "状态代码", "xpack.apm.transactionDetails.syncBadgeAsync": "异步", "xpack.apm.transactionDetails.syncBadgeBlocking": "正在阻止", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaDescription": "失败事务相关性不是 GA 版。请通过报告错误来帮助我们。", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaLabel": "公测版", - "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsBetaTitle": "失败事务相关性", "xpack.apm.transactionDetails.tabs.failedTransactionsCorrelationsLabel": "失败事务相关性", "xpack.apm.transactionDetails.tabs.latencyLabel": "延迟相关性", "xpack.apm.transactionDetails.tabs.traceSamplesLabel": "跟踪样例", @@ -7410,28 +7127,6 @@ "xpack.apm.tutorial.windowsServerInstructions.textPre": "1.从[下载页面]({downloadPageLink})下载 APM Server Windows zip 文件。\n2.将 zip 文件的内容解压缩到 {zipFileExtractFolder}。\n3.将 {apmServerDirectory} 目录重命名为 `APM-Server`。\n4.以管理员身份打开 PowerShell 提示符(右键单击 PowerShell 图标,然后选择**以管理员身份运行**)。如果运行的是 Windows XP,则可能需要下载并安装 PowerShell。\n5.从 PowerShell 提示符处,运行以下命令以将 APM Server 安装为 Windows 服务:", "xpack.apm.unitLabel": "选择单位", "xpack.apm.unsavedChanges": "{unsavedChangesCount, plural, =0{0 个未保存更改} one {1 个未保存更改} other {# 个未保存更改}} ", - "xpack.apm.urlFilter.wildcard": "使用通配符 *{wildcard}*", - "xpack.apm.ux.breadcrumbs.dashboard": "仪表板", - "xpack.apm.ux.breadcrumbs.root": "用户体验", - "xpack.apm.ux.jsErrors.percent": "{pageLoadPercent}%", - "xpack.apm.ux.localFilters.titles.webApplication": "Web 应用程序", - "xpack.apm.ux.median": "中值", - "xpack.apm.ux.metrics": "指标", - "xpack.apm.ux.overview.agent.description": "使用 APM 代理来收集 APM 数据。我们的代理支持许多流行语言,可以轻松使用。", - "xpack.apm.ux.overview.agent.title": "添加 APM 集成", - "xpack.apm.ux.overview.beatsCard.description": "通过 APM 代理启用 RUM,以收集用户体验数据。", - "xpack.apm.ux.overview.beatsCard.title": "添加 APM 集成", - "xpack.apm.ux.overview.heading": "仪表板", - "xpack.apm.ux.overview.solutionName": "Observability", - "xpack.apm.ux.percentile.50thMedian": "第 50 个(中值)", - "xpack.apm.ux.percentile.75th": "第 75 个", - "xpack.apm.ux.percentile.90th": "第 90 个", - "xpack.apm.ux.percentile.95th": "第 95 个", - "xpack.apm.ux.percentile.99th": "第 99 个", - "xpack.apm.ux.percentile.label": "百分位数", - "xpack.apm.ux.percentiles.label": "第 {value} 个百分位", - "xpack.apm.ux.title": "仪表板", - "xpack.apm.ux.visitorBreakdown.noData": "无数据。", "xpack.apm.views.dependencies.title": "依赖项", "xpack.apm.views.dependenciesInventory.title": "依赖项", "xpack.apm.views.errors.title": "错误", @@ -7454,6 +7149,81 @@ "xpack.apm.views.transactions.title": "事务", "xpack.apm.waterfall.errorCount": "{errorCount, plural, one {查看相关错误} other {查看 # 个相关错误}}", "xpack.apm.waterfall.exceedsMax": "此跟踪中的项目数超过显示的项目数", + "xpack.ux.breakdownFilter.browser": "浏览器", + "xpack.ux.breakdownFilter.device": "设备", + "xpack.ux.breakdownFilter.location": "位置", + "xpack.ux.breakDownFilter.noBreakdown": "无细目", + "xpack.ux.breakdownFilter.os": "OS", + "xpack.ux.pageViews.analyze": "分析", + "xpack.ux.coreVitals.dataUndefined": "不可用", + "xpack.ux.coreVitals.fcp": "首次内容绘制", + "xpack.ux.coreVitals.fcpTooltip": "首次内容绘制 (FCP) 侧重于初始渲染,并度量从页面开始加载到页面内容的任何部分显示在屏幕的时间。", + "xpack.ux.coreVitals.tbt": "阻止总时间", + "xpack.ux.coreVitals.tbtTooltip": "总阻塞时间 (TBT) 是指在首次内容绘制与事务完成之间发生的各个长任务的阻塞时间(持续时间超过50毫秒)之和。", + "xpack.ux.dashboard.backend": "后端", + "xpack.ux.dashboard.dataMissing": "不可用", + "xpack.ux.dashboard.frontend": "前端", + "xpack.ux.dashboard.impactfulMetrics.highTrafficPages": "高流量页面", + "xpack.ux.dashboard.impactfulMetrics.jsErrors": "JavaScript 错误", + "xpack.ux.dashboard.overall.label": "总体", + "xpack.ux.dashboard.pageLoad.label": "页面加载", + "xpack.ux.dashboard.pageLoadDistribution.label": "页面加载分布", + "xpack.ux.dashboard.pageLoadDuration.label": "页面加载持续时间", + "xpack.ux.dashboard.pageLoadTime.label": "页面加载时间(秒)", + "xpack.ux.dashboard.pageLoadTimes.label": "页面加载时间", + "xpack.ux.dashboard.pagesLoaded.label": "已加载页面", + "xpack.ux.dashboard.pageViews": "页面总查看次数", + "xpack.ux.dashboard.resetZoom.label": "重置缩放比例", + "xpack.ux.dashboard.tooltips.backEnd": "后端时间表示接收到第一个字节所需的时间 (TTFB),即从请求发出后接收到第一个响应数据包的时间", + "xpack.ux.dashboard.tooltips.frontEnd": "前端时间表示页面加载总持续时间减去后端时间", + "xpack.ux.dashboard.tooltips.totalPageLoad": "合计表示整个页面加载持续时间", + "xpack.ux.dashboard.totalPageLoad": "合计", + "xpack.ux.filterGroup.breakdown": "细目", + "xpack.ux.filterGroup.coreWebVitals": "网站体验核心指标", + "xpack.ux.filterGroup.seconds": "秒", + "xpack.ux.filterGroup.selectBreakdown": "选择细分", + "xpack.ux.filters.filterByUrl": "按 URL 筛选", + "xpack.ux.filters.searchResults": "{total} 项搜索结果", + "xpack.ux.filters.select": "选择", + "xpack.ux.filters.topPages": "排名靠前页面", + "xpack.ux.filters.url": "URL", + "xpack.ux.filters.url.loadingResults": "正在加载结果", + "xpack.ux.filters.url.noResults": "没有可用结果", + "xpack.ux.jsErrors.errorMessage": "错误消息", + "xpack.ux.jsErrors.errorRate": "错误率", + "xpack.ux.jsErrors.impactedPageLoads": "受影响的页面加载", + "xpack.ux.jsErrors.totalErrors": "错误总数", + "xpack.ux.jsErrorsTable.errorMessage": "无法提取", + "xpack.ux.uxMetrics.longestLongTasks": "长任务最长持续时间", + "xpack.ux.uxMetrics.longestLongTasksTooltip": "最长任务的持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。", + "xpack.ux.uxMetrics.noOfLongTasks": "长任务数目", + "xpack.ux.uxMetrics.noOfLongTasksTooltip": "长任务的数量。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。", + "xpack.ux.uxMetrics.sumLongTasks": "长任务总持续时间", + "xpack.ux.uxMetrics.sumLongTasksTooltip": "长任务的总持续时间。长任务是指独占用户界面线程较长时间(大于50毫秒)并阻止其他关键任务(帧速率或输入延迟)执行的任何用户活动或浏览器任务。", + "xpack.ux.visitorBreakdown": "访问者细分", + "xpack.ux.visitorBreakdown.browser": "浏览器", + "xpack.ux.visitorBreakdown.operatingSystem": "操作系统", + "xpack.ux.visitorBreakdownMap.avgPageLoadDuration": "页面加载平均持续时间", + "xpack.ux.visitorBreakdownMap.pageLoadDurationByRegion": "按区域列出的页面加载持续时间(平均值)", + "xpack.ux.breadcrumbs.dashboard": "仪表板", + "xpack.ux.breadcrumbs.root": "用户体验", + "xpack.ux.jsErrors.percent": "{pageLoadPercent}%", + "xpack.ux.localFilters.titles.webApplication": "Web 应用程序", + "xpack.ux.median": "中值", + "xpack.ux.metrics": "指标", + "xpack.ux.overview.beatsCard.description": "通过 APM 代理启用 RUM,以收集用户体验数据。", + "xpack.ux.overview.beatsCard.title": "添加 APM 集成", + "xpack.ux.overview.heading": "仪表板", + "xpack.ux.overview.solutionName": "可观测性", + "xpack.ux.percentile.50thMedian": "第 50 个(中值)", + "xpack.ux.percentile.75th": "第 75 个", + "xpack.ux.percentile.90th": "第 90 个", + "xpack.ux.percentile.95th": "第 95 个", + "xpack.ux.percentile.99th": "第 99 个", + "xpack.ux.percentile.label": "百分位数", + "xpack.ux.percentiles.label": "第 {value} 个百分位", + "xpack.ux.title": "仪表板", + "xpack.ux.visitorBreakdown.noData": "无数据。", "xpack.banners.settings.backgroundColor.description": "设置横幅广告的背景色。{subscriptionLink}", "xpack.banners.settings.backgroundColor.title": "横幅广告背景色", "xpack.banners.settings.placement.description": "在 Elastic 页眉上显示此工作区的顶部横幅广告。{subscriptionLink}", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx index 4e03a2a09bed4..1c21170005265 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/add_message_variables.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { AddMessageVariables } from './add_message_variables'; describe('AddMessageVariables', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx index a96e1fc3dcb5d..742e8981a1a1a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_connector.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EmailActionConnector } from '../types'; import EmailActionConnectorFields from './email_connector'; import * as hooks from './use_email_config'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx index 6a173cfd80926..1762fc0b39f8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/email_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import EmailParamsFields from './email_params'; describe('EmailParamsFields renders', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.test.tsx index 2a08c9b19e602..d87efe5134794 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email/exchange_form.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EmailActionConnector } from '../types'; import ExchangeFormFields from './exchange_form'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx index 9ef498334ad3d..6aa7fde6d23e1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { EsIndexActionConnector } from '../types'; import IndexActionConnectorFields from './es_index_connector'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx index 75c4fe2d5a055..6125255b3a52a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import ParamsFields from './es_index_params'; import { AlertHistoryEsIndexConnectorId } from '../../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx index 4859c25adcc06..1c8c58c7c4a16 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import JiraConnectorFields from './jira_connectors'; import { JiraActionConnector } from './types'; jest.mock('../../../../common/lib/kibana'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx index 8be15ddaa6bca..0d10223af66cf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { PagerDutyActionConnector } from '.././types'; import PagerDutyActionConnectorFields from './pagerduty_connectors'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx index f67267a75ed33..6eb349b8f511c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EventActionOptions, SeverityActionOptions } from '.././types'; import PagerDutyParamsFields from './pagerduty_params'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx index 35891f513be6b..13459888ac365 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import ResilientConnectorFields from './resilient_connectors'; import { ResilientActionConnector } from './types'; jest.mock('../../../../common/lib/kibana'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx index eba971dd66db4..baaffb6721fc4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/server_log/server_log_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ServerLogLevelOptions } from '.././types'; import ServerLogParamsFields from './server_log_params'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx index 03acb673bf5a4..786c84942e08f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_connectors.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act } from '@testing-library/react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { useKibana } from '../../../../common/lib/kibana'; import { ActionConnectorFieldsSetCallbacks } from '../../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx index bdb38d85a8923..a5804dc484cbb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import { ActionConnector } from '../../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx index de331893ad0cf..5fa15de036844 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { act } from '@testing-library/react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ActionConnector } from '../../../../types'; import { useGetChoices } from './use_get_choices'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx index ecb90051e78c2..00cce14906e82 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/update_connector.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { UpdateConnector, Props } from './update_connector'; import { ServiceNowActionConnector } from './types'; jest.mock('../../../../common/lib/kibana'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx index 0a37165bd7f5f..c0f6a17cae4fe 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import { SlackActionConnector } from '../types'; import SlackActionFields from './slack_connectors'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx index 74ee201340cc0..6611b33e094b2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import SlackParamsFields from './slack_params'; describe('SlackParamsFields renders', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx index 4829156380e94..47e5c09c13864 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { SwimlaneActionConnector } from './types'; import SwimlaneActionConnectorFields from './swimlane_connectors'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx index f1d7efca39551..03f20546b1003 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/swimlane/swimlane_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import SwimlaneParamsFields from './swimlane_params'; import { SwimlaneConnectorType } from './types'; import { mappings } from './mocks'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx index 5031b32281258..2d2b6d5052bb8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import { TeamsActionConnector } from '../types'; import TeamsActionFields from './teams_connectors'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx index 9d5e86d413d93..cf0bfe9db0e97 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/teams/teams_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import TeamsParamsFields from './teams_params'; jest.mock('../../../../common/lib/kibana'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx index ea40c1ddfb139..2579d8dd1a93b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_connectors.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { WebhookActionConnector } from '../types'; import WebhookActionConnectorFields from './webhook_connectors'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx index 7c9ffaa23659b..064d21b50e463 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/webhook/webhook_params.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import WebhookParamsFields from './webhook_params'; import { MockCodeEditor } from '../../../code_editor.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx index 6a705798a9b7f..068e4755be165 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/json_editor_with_message_variables.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { JsonEditorWithMessageVariables } from './json_editor_with_message_variables'; import { MockCodeEditor } from '../code_editor.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.test.tsx index 72ea099f87d26..0f3981391a49c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_area_with_message_variables.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TextAreaWithMessageVariables } from './text_area_with_message_variables'; describe('TextAreaWithMessageVariables', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.test.tsx index e0d74c5568c70..43c7ac51591b5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/text_field_with_message_variables.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { TextFieldWithMessageVariables } from './text_field_with_message_variables'; describe('TextFieldWithMessageVariables', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx index 3594374a54f16..51f80438c55cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import { RouteComponentProps, Router } from 'react-router-dom'; import { createMemoryHistory, createLocation } from 'history'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import TriggersActionsUIHome, { MatchParams } from './home'; import { useKibana } from '../common/lib/kibana'; jest.mock('../common/lib/kibana'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx index 5a4d682ff573b..4addfc3833aab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { UserConfiguredActionConnector, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index f7c15dade55e3..ace563aa96b9e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -6,7 +6,7 @@ */ import React, { lazy } from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { EuiAccordion } from '@elastic/eui'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { act } from 'react-dom/test-utils'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx index e8590595b9d61..e04b96b6839cf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ActionTypeForm } from './action_type_form'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx index e15916138af71..e8a0287d90026 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_menu.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ActionTypeMenu } from './action_type_menu'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index 8dbe5f105a0f7..9870786e5d3cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import ConnectorAddFlyout from './connector_add_flyout'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx index 1502b4255767d..0666f8be394d8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import ConnectorAddModal from './connector_add_modal'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index e6d3c0bde8113..e2bc9d44b147c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ConnectorValidationResult, GenericValidationResult } from '../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx index 6c383913a3c0f..1ecb651314ba9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connectors_selection.test.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { render, screen } from '@testing-library/react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { ConnectorsSelection } from './connectors_selection'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx index 7a3edcf4cb948..cc928d5c30fd9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/test_connector_form.test.tsx @@ -16,7 +16,7 @@ import { } from '../../../types'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { EuiFormRow, EuiFieldText, EuiText, EuiLink, EuiForm, EuiSelect } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; jest.mock('../../../common/lib/kibana'); const mockedActionParamsFields = lazy(async () => ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx index d477fcd0ddf74..1de8ad97af891 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; // eslint-disable-next-line @kbn/eslint/module_migration import { ThemeProvider } from 'styled-components'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import ActionsConnectorsList from './actions_connectors_list'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index b92b4289ea905..e12322c3b84ad 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from '@testing-library/react'; import { AlertDetails } from './alert_details'; import { Rule, ActionType, RuleTypeModel, RuleType } from '../../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 770b273b02c29..9bfb8f20744de 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { createMemoryHistory, createLocation } from 'history'; import { ToastsApi } from 'kibana/public'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alerts.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alerts.test.tsx index 075f6dedeb06a..909a562224ef7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alerts.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alerts.test.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import uuid from 'uuid'; import { shallow } from 'enzyme'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { Alerts, AlertListItem, alertToListItem } from './alerts'; import { Rule, AlertSummary, AlertStatus, RuleType } from '../../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 3187021518c9c..7e45dd8ac636a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -7,7 +7,7 @@ import uuid from 'uuid'; import React, { FunctionComponent } from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFormLabel } from '@elastic/eui'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions.test.tsx index f4ed8d0b05a85..07f7b1475b92f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { AlertConditions, ActionGroupWithCondition } from './alert_conditions'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.test.tsx index f730f73a417ba..49a2ec087a2b8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_conditions_group.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; import { AlertConditionsGroup } from './alert_conditions_group'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 226734ca46a67..ec33756cfec4a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { coreMock } from '../../../../../../../src/core/public/mocks'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index 37741dcaab6e5..49803c0cc419d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx index 1534e0ed4f6b8..c15f249fd7688 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { Rule } from '../../../types'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index ce94ef75ac156..2ca32ec04cd70 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/collapsed_item_actions.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/collapsed_item_actions.test.tsx index a54bc67244b35..a036feea0fbcb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/collapsed_item_actions.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/collapsed_item_actions.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { CollapsedItemActions } from './collapsed_item_actions'; import { act } from 'react-dom/test-utils'; import { ruleTypeRegistryMock } from '../../../rule_type_registry.mock'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_enabled_switch.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_enabled_switch.test.tsx index da75faeda95e9..0773d6c9c5ed0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_enabled_switch.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/rule_enabled_switch.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { RuleEnabledSwitch, ComponentOpts } from './rule_enabled_switch'; describe('RuleEnabledSwitch', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx index f524ff4426b6a..f98f82e412f86 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/execution_duration_chart.test.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { ExecutionDurationChart, padOrTruncateDurations } from './execution_duration_chart'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx index 653c0ebadcfae..bb94e1d38757a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { ThresholdExpression } from './threshold'; describe('threshold expression', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.test.tsx index e9a3dce84e149..6ff6206db456a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/value.test.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { ValueExpression } from './value'; -import { mountWithIntl, nextTick } from '@kbn/test/jest'; +import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; describe('value expression', () => { it('renders description and value', () => { diff --git a/x-pack/plugins/ui_actions_enhanced/common/index.ts b/x-pack/plugins/ui_actions_enhanced/common/index.ts index 7b5bd71c9e8be..30192e925d6f7 100644 --- a/x-pack/plugins/ui_actions_enhanced/common/index.ts +++ b/x-pack/plugins/ui_actions_enhanced/common/index.ts @@ -5,7 +5,9 @@ * 2.0. */ -// TODO: https://github.com/elastic/kibana/issues/109891 -/* eslint-disable @kbn/eslint/no_export_all */ - -export * from './types'; +export type { + BaseActionConfig, + SerializedAction, + SerializedEvent, + DynamicActionsState, +} from './types'; diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts index 889b6377b16b5..663161f5ebd2d 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_action.test.ts @@ -19,7 +19,7 @@ import { HELLO_WORLD_EMBEDDABLE, } from '../../../../src/plugins/embeddable/public/tests/fixtures'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; import { ReactElement } from 'react'; const createOpenModalMock = () => { diff --git a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.test.ts b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.test.ts index b4134ddd17395..f31e2552fa717 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.test.ts +++ b/x-pack/plugins/ui_actions_enhanced/public/custom_time_range_badge.test.ts @@ -12,7 +12,7 @@ import { mount } from 'enzyme'; import { TimeRangeEmbeddable, TimeRangeContainer, TIME_RANGE_EMBEDDABLE } from './test_helpers'; import { CustomTimeRangeBadge } from './custom_time_range_badge'; import { ReactElement } from 'react'; -import { nextTick } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; test('Removing custom time range from badge resets embeddable back to container time', async () => { const container = new TimeRangeContainer( diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/app.helpers.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/app.helpers.tsx index 55c8c7f721a49..25e38b4111e50 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/app.helpers.tsx +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/app/app.helpers.tsx @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { App } from '../../../public/application/app'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts index 11784d0269505..31bbcd01a7320 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecation_logs/es_deprecation_logs.helpers.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { EsDeprecationLogs } from '../../../public/application/components/es_deprecation_logs'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.ts index 28ec1983cbeda..08d53d1eaca7e 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/es_deprecations.helpers.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { EsDeprecations } from '../../../public/application/components/es_deprecations'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/kibana_deprecations.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/kibana_deprecations.helpers.ts index 8d14a211786c4..9f98496ac2edf 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/kibana_deprecations.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/kibana_deprecations/kibana_deprecations.helpers.ts @@ -5,7 +5,12 @@ * 2.0. */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig, findTestSubject } from '@kbn/test/jest'; +import { + registerTestBed, + TestBed, + AsyncTestBedConfig, + findTestSubject, +} from '@kbn/test-jest-helpers'; import { KibanaDeprecations } from '../../../public/application/components'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts index 34abaed727bd3..30a531091f166 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/overview.helpers.ts @@ -6,7 +6,7 @@ */ import { act } from 'react-dom/test-utils'; -import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test/jest'; +import { registerTestBed, TestBed, AsyncTestBedConfig } from '@kbn/test-jest-helpers'; import { Overview } from '../../../public/application/components/overview'; import { WithAppDependencies } from '../helpers'; diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index 2c369579b0150..f1b0b69ba61ec 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -41,4 +41,5 @@ export enum API_URLS { SERVICE_LOCATIONS = '/internal/uptime/service/locations', SYNTHETICS_MONITORS = '/internal/uptime/service/monitors', RUN_ONCE_MONITOR = '/internal/uptime/service/monitors/run_once', + TRIGGER_MONITOR = '/internal/uptime/service/monitors/trigger', } 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 fc2cd42500d6c..d43fd5ad001f2 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts @@ -27,6 +27,7 @@ export const StateType = t.intersection([ monitor: t.intersection([ t.partial({ name: t.string, + duration: t.type({ us: t.number }), }), t.type({ type: t.string, @@ -73,6 +74,7 @@ export const MonitorSummaryType = t.intersection([ t.partial({ histogram: HistogramType, minInterval: t.number, + configId: t.string, }), ]); diff --git a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts index f4a9fe31b2885..989d6d8ef941a 100644 --- a/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts +++ b/x-pack/plugins/uptime/common/runtime_types/ping/ping.ts @@ -220,6 +220,7 @@ export const PingType = t.intersection([ service: t.partial({ name: t.string, }), + config_id: t.string, }), ]); diff --git a/x-pack/plugins/uptime/e2e/fixtures/es_archiver/browser/mappings.json b/x-pack/plugins/uptime/e2e/fixtures/es_archiver/browser/mappings.json index 416b157b35a67..c022a7ab04249 100644 --- a/x-pack/plugins/uptime/e2e/fixtures/es_archiver/browser/mappings.json +++ b/x-pack/plugins/uptime/e2e/fixtures/es_archiver/browser/mappings.json @@ -10,7 +10,7 @@ "mappings": { "_meta": { "beat": "heartbeat", - "version": "8.1.0" + "version": "8.2.0" }, "date_detection": false, "dynamic_templates": [ @@ -7759,4 +7759,4 @@ } } } -} \ No newline at end of file +} diff --git a/x-pack/plugins/uptime/public/components/common/charts/chart_empty_state.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/chart_empty_state.test.tsx index 9d8aa3d4d091f..1f185b964b668 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/chart_empty_state.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/chart_empty_state.test.tsx @@ -6,7 +6,7 @@ */ import { ChartEmptyState } from './chart_empty_state'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; diff --git a/x-pack/plugins/uptime/public/components/common/charts/chart_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/chart_wrapper.test.tsx index 25b316344b32a..37354104cf871 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/chart_wrapper.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/chart_wrapper.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiSpacer } from '@elastic/eui'; import { mount } from 'enzyme'; -import { nextTick } from '@kbn/test/jest'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { nextTick } from '@kbn/test-jest-helpers'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ChartWrapper } from './chart_wrapper'; import { SnapshotHeading } from '../../overview/snapshot/snapshot_heading'; import { DonutChart } from './donut_chart'; diff --git a/x-pack/plugins/uptime/public/components/common/charts/donut_chart.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/donut_chart.test.tsx index ee01235a75fd7..ff83c51900cdf 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/donut_chart.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/donut_chart.test.tsx @@ -6,7 +6,7 @@ */ import { DonutChart } from './donut_chart'; -import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; describe('DonutChart component', () => { diff --git a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.test.tsx index f2f9757afce07..3e5a5c2539680 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl } from '@kbn/test/jest'; +import { renderWithIntl } from '@kbn/test-jest-helpers'; import { DonutChartLegend } from './donut_chart_legend'; diff --git a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.test.tsx index 1b73b80bacd57..cae142b2a4f79 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/donut_chart_legend_row.test.tsx @@ -6,7 +6,7 @@ */ import { DonutChartLegendRow } from './donut_chart_legend_row'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; describe('DonutChartLegendRow', () => { diff --git a/x-pack/plugins/uptime/public/components/common/location_link.test.tsx b/x-pack/plugins/uptime/public/components/common/location_link.test.tsx index 177015b93d3f3..285ee6cb47725 100644 --- a/x-pack/plugins/uptime/public/components/common/location_link.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/location_link.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { LocationLink } from './location_link'; diff --git a/x-pack/plugins/uptime/public/components/common/monitor_page_link.test.tsx b/x-pack/plugins/uptime/public/components/common/monitor_page_link.test.tsx index 317af4c083ca2..9bd7db2de7f6b 100644 --- a/x-pack/plugins/uptime/public/components/common/monitor_page_link.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/monitor_page_link.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { MonitorPageLink } from './monitor_page_link'; describe('MonitorPageLink component', () => { diff --git a/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.test.tsx index c88349282b7b4..5f46169901e57 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ml/confirm_delete.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { ConfirmJobDeletion } from './confirm_delete'; describe('ML Confirm Job Delete', () => { diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.test.tsx index dd42a14890793..f87501a79e964 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/expanded_row.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { mountWithIntl, renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { PingListExpandedRowComponent } from './expanded_row'; import { Ping } from '../../../../common/runtime_types'; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_headers.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_headers.test.tsx index aa1f85556bc5f..8bc9f2b7fed51 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_headers.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/ping_headers.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { PingHeaders } from './headers'; diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.test.tsx index 76ec33ccc5fe2..d4dd2f6f0678b 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/availability_reporting/tag_label.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { TagLabel } from './tag_label'; describe('TagLabel component', () => { diff --git a/x-pack/plugins/uptime/public/components/monitor/status_details/status_by_location.test.tsx b/x-pack/plugins/uptime/public/components/monitor/status_details/status_by_location.test.tsx index d86b7f13604dd..d7e8f70475f54 100644 --- a/x-pack/plugins/uptime/public/components/monitor/status_details/status_by_location.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/status_details/status_by_location.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { MonitorLocation } from '../../../../common/runtime_types'; import { StatusByLocations } from './index'; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx index d5dd333f7f6c7..5dc893356b214 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/browser_test_results.tsx @@ -20,7 +20,7 @@ interface Props { export const BrowserTestRunResult = ({ monitorId }: Props) => { const { data, loading, stepEnds, journeyStarted, summaryDoc, stepListData } = useBrowserRunOnceMonitors({ - monitorId, + configId: monitorId, }); const hits = data?.hits.hits; diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx index 3a126e6f69e99..f467bb642a13e 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.test.tsx @@ -26,7 +26,7 @@ describe('useBrowserRunOnceMonitors', function () { }, }); - const { result } = renderHook(() => useBrowserRunOnceMonitors({ monitorId: 'test-id' }), { + const { result } = renderHook(() => useBrowserRunOnceMonitors({ configId: 'test-id' }), { wrapper: WrappedHelper, }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts index 41f2b1cbe11f8..d051eaebe392e 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/browser/use_browser_run_once_monitors.ts @@ -15,10 +15,12 @@ import { fetchJourneySteps } from '../../../../state/api/journey'; import { isStepEnd } from '../../../synthetics/check_steps/steps_list'; export const useBrowserEsResults = ({ - monitorId, + configId, + testRunId, lastRefresh, }: { - monitorId: string; + configId: string; + testRunId?: string; lastRefresh: number; }) => { const { settings } = useSelector(selectDynamicSettings); @@ -37,7 +39,7 @@ export const useBrowserEsResults = ({ filter: [ { term: { - config_id: monitorId, + config_id: configId, }, }, { @@ -45,28 +47,47 @@ export const useBrowserEsResults = ({ 'synthetics.type': ['heartbeat/summary', 'journey/start'], }, }, + ...(testRunId + ? [ + { + term: { + test_run_id: testRunId, + }, + }, + ] + : []), ], }, }, }, size: 10, }), - [monitorId, settings?.heartbeatIndices, lastRefresh], + [configId, settings?.heartbeatIndices, lastRefresh], { name: 'TestRunData' } ); }; -export const useBrowserRunOnceMonitors = ({ monitorId }: { monitorId: string }) => { - const { refreshTimer, lastRefresh } = useTickTick(); +export const useBrowserRunOnceMonitors = ({ + configId, + testRunId, + skipDetails = false, + refresh = true, +}: { + configId: string; + testRunId?: string; + refresh?: boolean; + skipDetails?: boolean; +}) => { + const { refreshTimer, lastRefresh } = useTickTick(3 * 1000, refresh); const [checkGroupId, setCheckGroupId] = useState(''); const [stepEnds, setStepEnds] = useState([]); const [summary, setSummary] = useState(); - const { data, loading } = useBrowserEsResults({ monitorId, lastRefresh }); + const { data, loading } = useBrowserEsResults({ configId, testRunId, lastRefresh }); const { data: stepListData } = useFetcher(() => { - if (checkGroupId) { + if (checkGroupId && !skipDetails) { return fetchJourneySteps({ checkGroup: checkGroupId, }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx index 97097285d0bbc..507082c7fefb1 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/simple_test_results.tsx @@ -15,7 +15,7 @@ interface Props { } export function SimpleTestResults({ monitorId }: Props) { const [summaryDocs, setSummaryDocs] = useState([]); - const { summaryDoc, loading } = useSimpleRunOnceMonitors({ monitorId }); + const { summaryDoc, loading } = useSimpleRunOnceMonitors({ configId: monitorId }); useEffect(() => { if (summaryDoc) { diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts index 816f9b019c45a..fc43844103f25 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/simple/use_simple_run_once_monitors.ts @@ -12,8 +12,14 @@ import { Ping } from '../../../../../common/runtime_types'; import { createEsParams, useEsSearch } from '../../../../../../observability/public'; import { useTickTick } from '../use_tick_tick'; -export const useSimpleRunOnceMonitors = ({ monitorId }: { monitorId: string }) => { - const { refreshTimer, lastRefresh } = useTickTick(); +export const useSimpleRunOnceMonitors = ({ + configId, + testRunId, +}: { + configId: string; + testRunId?: string; +}) => { + const { refreshTimer, lastRefresh } = useTickTick(2 * 1000, false); const { settings } = useSelector(selectDynamicSettings); @@ -31,7 +37,7 @@ export const useSimpleRunOnceMonitors = ({ monitorId }: { monitorId: string }) = filter: [ { term: { - config_id: monitorId, + config_id: configId, }, }, { @@ -39,13 +45,22 @@ export const useSimpleRunOnceMonitors = ({ monitorId }: { monitorId: string }) = field: 'summary', }, }, + ...(testRunId + ? [ + { + term: { + test_run_id: testRunId, + }, + }, + ] + : []), ], }, }, }, size: 10, }), - [monitorId, settings?.heartbeatIndices, lastRefresh], + [configId, settings?.heartbeatIndices, lastRefresh], { name: 'TestRunData' } ); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_result_header.tsx b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_result_header.tsx index 51b120c3c7e5e..00caa48e5dc08 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_result_header.tsx +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/test_result_header.tsx @@ -63,7 +63,7 @@ export function TestResultHeader({ doc, title, summaryDocs, journeyStarted, isCo - {journeyStarted ? IN_PROGRESS : PENDING_LABEL} + {journeyStarted ? IN_PROGRESS_LABEL : PENDING_LABEL} @@ -86,7 +86,7 @@ export function TestResultHeader({ doc, title, summaryDocs, journeyStarted, isCo ); } -const PENDING_LABEL = i18n.translate('xpack.uptime.monitorManagement.pending', { +export const PENDING_LABEL = i18n.translate('xpack.uptime.monitorManagement.pending', { defaultMessage: 'PENDING', }); @@ -98,7 +98,7 @@ const COMPLETED_LABEL = i18n.translate('xpack.uptime.monitorManagement.completed defaultMessage: 'COMPLETED', }); -const IN_PROGRESS = i18n.translate('xpack.uptime.monitorManagement.inProgress', { +export const IN_PROGRESS_LABEL = i18n.translate('xpack.uptime.monitorManagement.inProgress', { defaultMessage: 'IN PROGRESS', }); diff --git a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/use_tick_tick.ts b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/use_tick_tick.ts index ff9b9f3f6154d..ce6d88806144c 100644 --- a/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/use_tick_tick.ts +++ b/x-pack/plugins/uptime/public/components/monitor_management/test_now_mode/use_tick_tick.ts @@ -8,13 +8,18 @@ import { useEffect, useState, useContext } from 'react'; import { UptimeRefreshContext } from '../../../contexts'; -export function useTickTick() { - const { refreshApp, lastRefresh } = useContext(UptimeRefreshContext); +export function useTickTick(interval?: number, refresh = true) { + const { refreshApp } = useContext(UptimeRefreshContext); + + const [nextTick, setNextTick] = useState(Date.now()); const [tickTick] = useState(() => setInterval(() => { - refreshApp(); - }, 5 * 1000) + if (refresh) { + refreshApp(); + } + setNextTick(Date.now()); + }, interval ?? 5 * 1000) ); useEffect(() => { @@ -23,5 +28,5 @@ export function useTickTick() { }; }, [tickTick]); - return { refreshTimer: tickTick, lastRefresh }; + return { refreshTimer: tickTick, lastRefresh: nextTick }; } diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/alert_field_number.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/alert_field_number.test.tsx index 32e16f1ae88eb..62823b4ca87d6 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/alert_field_number.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/alert_field_number.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; import { AlertFieldNumber, handleAlertFieldNumberChange } from './alert_field_number'; describe('AlertFieldNumber', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.test.tsx index 0598b675cab35..1290d4cb4ceba 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/down_number_select.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { DownNoExpressionSelect } from './down_number_select'; describe('DownNoExpressionSelect component', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.test.tsx index 4ce9720cfc4ce..87caf3f7cb3b6 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_expressions/time_expression_select.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { TimeExpressionSelect } from './time_expression_select'; describe('TimeExpressionSelect component', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/add_filter_btn.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/add_filter_btn.test.tsx index 5b32d9bc01992..f8f9e5a84bc5c 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/add_filter_btn.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/add_filter_btn.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, mountWithIntl } from '@kbn/test-jest-helpers'; import { AddFilterButton } from './add_filter_btn'; import { EuiButtonEmpty, EuiContextMenuItem } from '@elastic/eui'; diff --git a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/old_alert_callout.test.tsx b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/old_alert_callout.test.tsx index 5300b218b4d00..f0c41480f5172 100644 --- a/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/old_alert_callout.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/alerts/monitor_status_alert/old_alert_callout.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { OldAlertCallOut } from './old_alert_call_out'; describe('OldAlertCallOut', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.test.tsx index c05cc1e4b2a92..02fe666b79464 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.test.tsx @@ -229,7 +229,12 @@ describe('MonitorListStatusColumn', () => { it('provides expected tooltip and display times', async () => { const { getByText } = render( - + ); @@ -244,7 +249,12 @@ describe('MonitorListStatusColumn', () => { it('can handle a non-numeric timestamp value', () => { const { getByText } = render( - + ); @@ -259,6 +269,7 @@ describe('MonitorListStatusColumn', () => { ping.observer!.geo!.name! === 'Islamabad')} /> @@ -278,6 +289,7 @@ describe('MonitorListStatusColumn', () => { status="up" timestamp={new Date().toString()} summaryPings={summaryPings} + monitorType="http" /> ); @@ -295,6 +307,7 @@ describe('MonitorListStatusColumn', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.tsx index 6960bb5632852..60baedaa7830c 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/monitor_status_column.tsx @@ -5,13 +5,23 @@ * 2.0. */ -import React, { useContext } from 'react'; -import moment from 'moment'; +import React, { useCallback, useContext } from 'react'; +import moment, { Moment } from 'moment'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip, EuiBadge, EuiSpacer } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiToolTip, + EuiBadge, + EuiSpacer, + EuiHighlight, + EuiHorizontalRule, +} from '@elastic/eui'; +import { useDispatch, useSelector } from 'react-redux'; import { parseTimestamp } from '../parse_timestamp'; -import { Ping } from '../../../../../common/runtime_types'; +import { DataStream, Ping } from '../../../../../common/runtime_types'; import { STATUS, SHORT_TIMESPAN_LOCALE, @@ -22,10 +32,18 @@ import { import { UptimeThemeContext } from '../../../../contexts'; import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; import { STATUS_DOWN_LABEL, STATUS_UP_LABEL } from '../../../common/translations'; +import { MonitorProgress } from './progress/monitor_progress'; +import { refreshedMonitorSelector } from '../../../../state/reducers/monitor_list'; +import { testNowRunSelector } from '../../../../state/reducers/test_now_runs'; +import { clearTestNowMonitorAction } from '../../../../state/actions'; interface MonitorListStatusColumnProps { + configId?: string; + monitorId?: string; status: string; + monitorType: string; timestamp: string; + duration?: number; summaryPings: Ping[]; } @@ -63,7 +81,7 @@ export const getShortTimeStamp = (timeStamp: moment.Moment, relative = false) => shortTimestamp = timeStamp.fromNow(); } - // Reset it so, it does't impact other part of the app + // Reset it so, it doesn't impact other part of the app moment.locale(prevLocale); return shortTimestamp; } else { @@ -144,7 +162,11 @@ export const getLocationStatus = (summaryPings: Ping[], status: string) => { }; export const MonitorListStatusColumn = ({ + monitorType, + configId, + monitorId, status, + duration, summaryPings = [], timestamp: tsString, }: MonitorListStatusColumnProps) => { @@ -156,16 +178,39 @@ export const MonitorListStatusColumn = ({ const { statusMessage, locTooltip } = getLocationStatus(summaryPings, status); + const dispatch = useDispatch(); + + const stopProgressTrack = useCallback(() => { + if (configId) { + dispatch(clearTestNowMonitorAction(configId)); + } + }, [configId, dispatch]); + + const refreshedMonitorIds = useSelector(refreshedMonitorSelector); + + const testNowRun = useSelector(testNowRunSelector(configId)); + return (
- + - - {getHealthMessage(status)} - + {testNowRun && configId && testNowRun?.testRunId ? ( + + ) : ( + + {getHealthMessage(status)} + + )} @@ -183,20 +228,39 @@ export const MonitorListStatusColumn = ({ - {timestamp.toLocaleString()} - + <> + + {timestamp.fromNow()} + + + + {timestamp.toLocaleString()} + + } > - - Checked {getShortTimeStamp(timestamp)} - + {monitorId && refreshedMonitorIds?.includes(monitorId) ? ( + + {getCheckedLabel(timestamp)} + + ) : ( + + {getCheckedLabel(timestamp)} + + )}
); }; +const getCheckedLabel = (timestamp: Moment) => { + return i18n.translate('xpack.uptime.monitorList.statusColumn.checkedTimestamp', { + defaultMessage: 'Checked {timestamp}', + values: { timestamp: getShortTimeStamp(timestamp) }, + }); +}; + const PaddedText = euiStyled(EuiText)` padding-right: ${(props) => props.theme.eui.paddingSizes.xs}; `; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/browser_monitor_progress.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/browser_monitor_progress.tsx new file mode 100644 index 0000000000000..c0453573693c4 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/browser_monitor_progress.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiBadge, EuiProgress } from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import { useBrowserRunOnceMonitors } from '../../../../monitor_management/test_now_mode/browser/use_browser_run_once_monitors'; +import { + IN_PROGRESS_LABEL, + PENDING_LABEL, +} from '../../../../monitor_management/test_now_mode/test_result_header'; + +export const BrowserMonitorProgress = ({ + configId, + testRunId, + duration, + isUpdating, + updateMonitorStatus, +}: { + configId: string; + testRunId: string; + duration: number; + isUpdating: boolean; + updateMonitorStatus: () => void; +}) => { + const { journeyStarted, summaryDoc, data } = useBrowserRunOnceMonitors({ + configId, + testRunId, + refresh: false, + skipDetails: true, + }); + + const [startTime, setStartTime] = useState(Date.now()); + const [passedTime, setPassedTime] = useState(0); + + useEffect(() => { + if (summaryDoc) { + updateMonitorStatus(); + } + }, [updateMonitorStatus, summaryDoc]); + + useEffect(() => { + const interVal = setInterval(() => { + if (journeyStarted) { + setPassedTime((Date.now() - startTime) * 1000); + } + }, 500); + const startTimeValue = startTime; + return () => { + if ((Date.now() - startTimeValue) * 1000 > duration) { + clearInterval(interVal); + } + }; + }, [data, duration, journeyStarted, startTime]); + + useEffect(() => { + if (journeyStarted) { + setStartTime(Date.now()); + } + }, [journeyStarted]); + + if (isUpdating || passedTime > duration) { + return ( + <> + {IN_PROGRESS_LABEL} + + + ); + } + + return ( + + {journeyStarted ? ( + <> + {IN_PROGRESS_LABEL} + + + ) : ( + <> + {PENDING_LABEL} + + + )} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/monitor_progress.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/monitor_progress.tsx new file mode 100644 index 0000000000000..e4fc0cf90e87b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/monitor_progress.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { useSelector } from 'react-redux'; +import { SimpleMonitorProgress } from './simple_monitor_progress'; +import { BrowserMonitorProgress } from './browser_monitor_progress'; +import { DataStream } from '../../../../../../common/runtime_types'; +import { useUpdatedMonitor } from './use_updated_monitor'; +import { refreshedMonitorSelector } from '../../../../../state/reducers/monitor_list'; + +export const MonitorProgress = ({ + monitorId, + configId, + testRunId, + duration, + monitorType, + stopProgressTrack, +}: { + monitorId: string; + configId: string; + testRunId: string; + duration: number; + monitorType: DataStream; + stopProgressTrack: () => void; +}) => { + const { updateMonitorStatus, isUpdating } = useUpdatedMonitor({ + testRunId, + monitorId, + }); + + const refreshedMonitorId = useSelector(refreshedMonitorSelector); + + useEffect(() => { + if (refreshedMonitorId.includes(monitorId)) { + stopProgressTrack(); + } + }, [isUpdating, monitorId, refreshedMonitorId, stopProgressTrack]); + + return monitorType === 'browser' ? ( + + ) : ( + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/simple_monitor_progress.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/simple_monitor_progress.tsx new file mode 100644 index 0000000000000..d51ae3f4e9dff --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/simple_monitor_progress.tsx @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiBadge, EuiProgress } from '@elastic/eui'; +import React, { useEffect, useRef, useState } from 'react'; +import { useSimpleRunOnceMonitors } from '../../../../monitor_management/test_now_mode/simple/use_simple_run_once_monitors'; +import { IN_PROGRESS_LABEL } from '../../../../monitor_management/test_now_mode/test_result_header'; + +export const SimpleMonitorProgress = ({ + monitorId, + testRunId, + duration, + isUpdating, + updateMonitorStatus, +}: { + monitorId: string; + testRunId: string; + duration: number; + isUpdating: boolean; + updateMonitorStatus: () => void; +}) => { + const { summaryDoc, data } = useSimpleRunOnceMonitors({ + configId: monitorId, + testRunId, + }); + + const startTime = useRef(Date.now()); + + const [passedTime, setPassedTime] = useState(Date.now()); + + useEffect(() => { + if (summaryDoc) { + updateMonitorStatus(); + } + }, [updateMonitorStatus, summaryDoc]); + + useEffect(() => { + setPassedTime(Date.now() - startTime.current); + }, [data]); + + const passedTimeMicro = passedTime * 1000; + + if (isUpdating || passedTimeMicro > duration) { + return ( + <> + {IN_PROGRESS_LABEL} + + + ); + } + + return ( + + {IN_PROGRESS_LABEL} + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/use_updated_monitor.ts b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/use_updated_monitor.ts new file mode 100644 index 0000000000000..b76971d4079e3 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/progress/use_updated_monitor.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useCallback } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { getUpdatedMonitor, setUpdatingMonitorId } from '../../../../../state/actions'; +import { isUpdatingMonitorSelector } from '../../../../../state/reducers/monitor_list'; + +export const useUpdatedMonitor = ({ + testRunId, + monitorId, +}: { + testRunId: string; + monitorId: string; +}) => { + const dispatch = useDispatch(); + + const isUpdatingMonitors = useSelector(isUpdatingMonitorSelector); + + const updateMonitorStatus = useCallback(() => { + if (testRunId) { + dispatch( + getUpdatedMonitor.get({ + dateRangeStart: 'now-10m', + dateRangeEnd: 'now', + filters: JSON.stringify({ + bool: { + should: [{ match_phrase: { test_run_id: testRunId } }], + minimum_should_match: 1, + }, + }), + pageSize: 1, + }) + ); + dispatch(setUpdatingMonitorId(monitorId)); + } + }, [dispatch, monitorId, testRunId]); + + return { updateMonitorStatus, isUpdating: isUpdatingMonitors.includes(monitorId) }; +}; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/test_now_col.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/test_now_col.tsx new file mode 100644 index 0000000000000..de32c874ae24c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/columns/test_now_col.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { useDispatch, useSelector } from 'react-redux'; +import { EuiButtonIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; +import { testNowMonitorAction } from '../../../../state/actions'; +import { testNowRunSelector } from '../../../../state/reducers/test_now_runs'; + +export const TestNowColumn = ({ + monitorId, + configId, +}: { + monitorId: string; + configId?: string; +}) => { + const dispatch = useDispatch(); + + const testNowRun = useSelector(testNowRunSelector(configId)); + + if (!configId) { + return <>--; + } + + const testNowClick = () => { + dispatch(testNowMonitorAction.get(configId)); + }; + + if (testNowRun && testNowRun.status === 'loading') { + return ; + } + + return ( + + testNowClick()} + isDisabled={Boolean(testNowRun)} + aria-label={TEST_NOW_ARIA_LABEL} + /> + + ); +}; + +export const TEST_NOW_ARIA_LABEL = i18n.translate('xpack.uptime.monitorList.testNow.AriaLabel', { + defaultMessage: 'CLick to run test now', +}); + +export const TEST_NOW_LABEL = i18n.translate('xpack.uptime.monitorList.testNow.label', { + defaultMessage: 'Test now', +}); + +export const TEST_SCHEDULED_LABEL = i18n.translate('xpack.uptime.monitorList.testNow.scheduled', { + defaultMessage: 'Test is already scheduled', +}); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx index de077931167c1..0eafd3c2fd52a 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.test.tsx @@ -16,7 +16,6 @@ import { MonitorSummary, } from '../../../../common/runtime_types'; import { MonitorListComponent, noItemsMessage } from './monitor_list'; -import * as redux from 'react-redux'; import moment from 'moment'; import { IHttpFetchError, ResponseErrorBody } from '../../../../../../../src/core/public'; import { mockMoment } from '../../../lib/helper/test_helpers'; @@ -56,7 +55,7 @@ const testFooPings: Ping[] = [ const testFooSummary: MonitorSummary = { monitor_id: 'foo', state: { - monitor: { type: 'http' }, + monitor: { type: 'http', duration: { us: 1000 } }, summaryPings: testFooPings, summary: { up: 1, @@ -91,7 +90,7 @@ const testBarPings: Ping[] = [ const testBarSummary: MonitorSummary = { monitor_id: 'bar', state: { - monitor: { type: 'http' }, + monitor: { type: 'http', duration: { us: 1000 } }, summaryPings: testBarPings, summary: { up: 2, @@ -129,12 +128,6 @@ describe('MonitorList component', () => { }; beforeEach(() => { - const useDispatchSpy = jest.spyOn(redux, 'useDispatch'); - useDispatchSpy.mockReturnValue(jest.fn()); - - const useSelectorSpy = jest.spyOn(redux, 'useSelector'); - useSelectorSpy.mockReturnValue(true); - localStorageMock = { getItem: jest.fn().mockImplementation(() => '25'), setItem: jest.fn(), @@ -156,6 +149,7 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} + refreshedMonitorIds={[]} /> ); expect(await findByText(NO_DATA_MESSAGE)).toBeInTheDocument(); @@ -170,6 +164,7 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} + refreshedMonitorIds={[]} /> ); @@ -190,6 +185,7 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} + refreshedMonitorIds={[]} /> ); @@ -226,6 +222,7 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} + refreshedMonitorIds={[]} /> ); @@ -254,6 +251,7 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} + refreshedMonitorIds={[]} /> ); @@ -283,6 +281,7 @@ describe('MonitorList component', () => { }} pageSize={10} setPageSize={jest.fn()} + refreshedMonitorIds={[]} /> ); diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx index fd9c072b9004f..2dd4ed7bed481 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list.tsx @@ -32,15 +32,19 @@ import { CertStatusColumn } from './columns/cert_status_column'; import { MonitorListHeader } from './monitor_list_header'; import { TAGS_LABEL, URL_LABEL } from '../../common/translations'; import { EnableMonitorAlert } from './columns/enable_alert'; -import { STATUS_ALERT_COLUMN } from './translations'; +import { STATUS_ALERT_COLUMN, TEST_NOW_COLUMN } from './translations'; import { MonitorNameColumn } from './columns/monitor_name_col'; import { MonitorTags } from '../../common/monitor_tags'; import { useMonitorHistogram } from './use_monitor_histogram'; +import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; +import { TestNowColumn } from './columns/test_now_col'; +import { useUptimeSettingsContext } from '../../../contexts/uptime_settings_context'; interface Props extends MonitorListProps { pageSize: number; setPageSize: (val: number) => void; monitorList: MonitorList; + refreshedMonitorIds: string[]; } export const noItemsMessage = (loading: boolean, filters?: string) => { @@ -52,8 +56,15 @@ export const MonitorListComponent: ({ filters, monitorList: { list, error, loading }, pageSize, + refreshedMonitorIds, setPageSize, -}: Props) => any = ({ filters, monitorList: { list, error, loading }, pageSize, setPageSize }) => { +}: Props) => any = ({ + filters, + refreshedMonitorIds = [], + monitorList: { list, error, loading }, + pageSize, + setPageSize, +}) => { const [expandedDrawerIds, updateExpandedDrawerIds] = useState([]); const { width } = useWindowSize(); const [hideExtraColumns, setHideExtraColumns] = useState(false); @@ -94,6 +105,8 @@ export const MonitorListComponent: ({ }, {}); }; + const { config } = useUptimeSettingsContext(); + const columns = [ ...[ { @@ -103,12 +116,27 @@ export const MonitorListComponent: ({ mobileOptions: { fullWidth: true, }, - render: (status: string, { state: { timestamp, summaryPings } }: MonitorSummary) => { + render: ( + status: string, + { + monitor_id: monitorId, + state: { + timestamp, + summaryPings, + monitor: { type, duration }, + }, + configId, + }: MonitorSummary + ) => { return ( ); }, @@ -166,20 +194,31 @@ export const MonitorListComponent: ({ }, ] : []), - ...[ - { - align: 'center' as const, - field: '', - name: STATUS_ALERT_COLUMN, - width: '100px', - render: (item: MonitorSummary) => ( - - ), - }, - ], + { + align: 'center' as const, + field: '', + name: STATUS_ALERT_COLUMN, + width: '100px', + render: (item: MonitorSummary) => ( + + ), + }, + ...(config.ui?.monitorManagement?.enabled + ? [ + { + align: 'center' as const, + field: '', + name: TEST_NOW_COLUMN, + width: '100px', + render: (item: MonitorSummary) => ( + + ), + }, + ] + : []), ...(!hideExtraColumns ? [ { @@ -205,7 +244,7 @@ export const MonitorListComponent: ({ ]; return ( - + toggleDrawer(monitorId), 'aria-label': labels.getExpandDrawerLabel(monitorId), }) - : undefined + : ({ monitor_id: monitorId }) => ({ + className: refreshedMonitorIds.includes(monitorId) ? 'refresh-row' : undefined, + }) } /> @@ -253,6 +294,17 @@ export const MonitorListComponent: ({
- + ); }; + +const WrapperPanel = euiStyled(EuiPanel)` + &&& { + .refresh-row{ + background-color: #f0f4fb; + -webkit-transition: background-color 3000ms linear; + -ms-transition: background-color 3000ms linear; + transition: background-color 3000ms linear; + } + } +`; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx index e753d07f9db39..d01f9365a7ac0 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_container.tsx @@ -7,7 +7,7 @@ import React, { useContext, useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { getMonitorList } from '../../../state/actions'; +import { clearRefreshedMonitorId, getMonitorList } from '../../../state/actions'; import { esKuerySelector, monitorListSelector } from '../../../state/selectors'; import { MonitorListComponent } from './monitor_list'; import { useUrlParams } from '../../../hooks'; @@ -15,6 +15,7 @@ import { UptimeRefreshContext } from '../../../contexts'; import { getConnectorsAction, getMonitorAlertsAction } from '../../../state/alerts/alerts'; import { useMappingCheck } from '../../../hooks/use_mapping_check'; import { useOverviewFilterCheck } from '../../../hooks/use_overview_filter_check'; +import { refreshedMonitorSelector } from '../../../state/reducers/monitor_list'; export interface MonitorListProps { filters?: string; @@ -43,6 +44,8 @@ export const MonitorList: React.FC = (props) => { const { lastRefresh } = useContext(UptimeRefreshContext); + const refreshedMonitorIds = useSelector(refreshedMonitorSelector); + const monitorList = useSelector(monitorListSelector); useMappingCheck(monitorList.error); @@ -81,12 +84,23 @@ export const MonitorList: React.FC = (props) => { dispatch(getConnectorsAction.get()); }, [dispatch]); + useEffect(() => { + if (refreshedMonitorIds) { + refreshedMonitorIds.forEach((id) => { + setTimeout(() => { + dispatch(clearRefreshedMonitorId(id)); + }, 5 * 1000); + }); + } + }, [dispatch, refreshedMonitorIds]); + return ( ); }; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_group.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_group.test.tsx index c4de51f68a17a..5f50285063541 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_group.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_group.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { MonitorSummary, makePing } from '../../../../../common/runtime_types'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { IntegrationGroup, extractSummaryValues } from './actions_popover/integration_group'; describe('IntegrationGroup', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_link.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_link.test.tsx index dacdf38b1207f..7f4926f994b1f 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_link.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/integration_link.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { IntegrationLink } from './actions_popover/integration_link'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; describe('IntegrationLink component', () => { it('renders without errors', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/most_recent_error.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/most_recent_error.test.tsx index f0f330807ffbf..111a8a18ccad5 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/most_recent_error.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_drawer/most_recent_error.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl, renderWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl, renderWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import moment from 'moment'; import { BrowserRouter as Router } from 'react-router-dom'; diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_page_size_select.test.tsx b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_page_size_select.test.tsx index c8da376a7a8cd..9b7e2a1917835 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_page_size_select.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/monitor_list_page_size_select.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { MonitorListPageSizeSelectComponent } from './monitor_list_page_size_select'; -import { mountWithIntl } from '@kbn/test/jest'; +import { mountWithIntl } from '@kbn/test-jest-helpers'; describe('MonitorListPageSizeSelect', () => { it('updates the state when selection changes', () => { diff --git a/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts b/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts index fdb50bc295dda..ba006884a42cb 100644 --- a/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts +++ b/x-pack/plugins/uptime/public/components/overview/monitor_list/translations.ts @@ -73,3 +73,7 @@ export const RESPONSE_ANOMALY_SCORE = i18n.translate( export const STATUS_ALERT_COLUMN = i18n.translate('xpack.uptime.monitorList.statusAlert.label', { defaultMessage: 'Status alert', }); + +export const TEST_NOW_COLUMN = i18n.translate('xpack.uptime.monitorList.testNow.label', { + defaultMessage: 'Test now', +}); diff --git a/x-pack/plugins/uptime/public/components/overview/snapshot/snapshot.test.tsx b/x-pack/plugins/uptime/public/components/overview/snapshot/snapshot.test.tsx index 184f6b73cc7ab..3cea8199544d4 100644 --- a/x-pack/plugins/uptime/public/components/overview/snapshot/snapshot.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/snapshot/snapshot.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import { SnapshotComponent } from './snapshot'; import { Snapshot } from '../../../../common/runtime_types/snapshot'; import * as hook from './use_snap_shot'; diff --git a/x-pack/plugins/uptime/public/components/overview/snapshot_heading.test.tsx b/x-pack/plugins/uptime/public/components/overview/snapshot_heading.test.tsx index 04874a4177e07..f3616345a6f9b 100644 --- a/x-pack/plugins/uptime/public/components/overview/snapshot_heading.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/snapshot_heading.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { SnapshotHeading } from './snapshot/snapshot_heading'; diff --git a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx index ec9e5f958ec3a..0d2dc15f55725 100644 --- a/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx +++ b/x-pack/plugins/uptime/public/components/overview/synthetics_callout.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { SyntheticsCallout } from './synthetics_callout'; diff --git a/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx index b80613dbfece5..6a3c6d02151ed 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/console_event.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { ConsoleEvent } from './console_event'; diff --git a/x-pack/plugins/uptime/public/components/synthetics/empty_journey.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/empty_journey.test.tsx index 854006ba39356..cdbf69af0f874 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/empty_journey.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/empty_journey.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallowWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; import { EmptyJourney } from './empty_journey'; diff --git a/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts b/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts index 9593a84f08dad..bc09ef0514ef3 100644 --- a/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts +++ b/x-pack/plugins/uptime/public/lib/__mocks__/uptime_store.mock.ts @@ -59,6 +59,7 @@ export const mockState: AppState = { summaries: [], }, loading: false, + refreshedMonitorIds: [], }, monitorManagementList: { list: { @@ -115,4 +116,7 @@ export const mockState: AppState = { cacheSize: 0, hitCount: [], }, + testNowRuns: { + testNowRuns: [], + }, }; diff --git a/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx b/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx index 04ecc6bc9c72d..b8e1c055dce49 100644 --- a/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx +++ b/x-pack/plugins/uptime/public/lib/helper/enzyme_helpers.tsx @@ -10,7 +10,7 @@ import { Router } from 'react-router-dom'; import { MemoryHistory } from 'history/createMemoryHistory'; import { createMemoryHistory, History } from 'history'; // eslint-disable-next-line import/no-extraneous-dependencies -import { mountWithIntl, renderWithIntl, shallowWithIntl } from '@kbn/test/jest'; +import { mountWithIntl, renderWithIntl, shallowWithIntl } from '@kbn/test-jest-helpers'; import { MountWithReduxProvider } from './helper_with_redux'; import { AppState } from '../../state'; import { mockState } from '../__mocks__/uptime_store.mock'; diff --git a/x-pack/plugins/uptime/public/state/actions/monitor_list.ts b/x-pack/plugins/uptime/public/state/actions/monitor_list.ts index a85c0004545e4..b86853dcfbefe 100644 --- a/x-pack/plugins/uptime/public/state/actions/monitor_list.ts +++ b/x-pack/plugins/uptime/public/state/actions/monitor_list.ts @@ -7,9 +7,25 @@ import { createAction } from 'redux-actions'; import { FetchMonitorStatesQueryArgs, MonitorSummariesResult } from '../../../common/runtime_types'; +import { createAsyncAction } from './utils'; +import { TestNowResponse } from '../api'; export const getMonitorList = createAction('GET_MONITOR_LIST'); export const getMonitorListSuccess = createAction( 'GET_MONITOR_LIST_SUCCESS' ); export const getMonitorListFailure = createAction('GET_MONITOR_LIST_FAIL'); + +export const setUpdatingMonitorId = createAction('SET_UPDATING_MONITOR_ID'); +export const clearRefreshedMonitorId = createAction('CLEAR_REFRESH_MONITOR_ID'); + +export const testNowMonitorAction = createAsyncAction( + 'TEST_NOW_MONITOR_ACTION' +); + +export const clearTestNowMonitorAction = createAction('CLEAR_TEST_NOW_MONITOR_ACTION'); + +export const getUpdatedMonitor = createAsyncAction< + FetchMonitorStatesQueryArgs, + MonitorSummariesResult +>('GET_UPDATED_MONITOR'); diff --git a/x-pack/plugins/uptime/public/state/api/monitor_management.ts b/x-pack/plugins/uptime/public/state/api/monitor_management.ts index 06a49df596d13..ec2806907baa1 100644 --- a/x-pack/plugins/uptime/public/state/api/monitor_management.ts +++ b/x-pack/plugins/uptime/public/state/api/monitor_management.ts @@ -67,3 +67,13 @@ export const runOnceMonitor = async ({ }): Promise<{ errors: Array<{ error: Error }> }> => { return await apiService.post(API_URLS.RUN_ONCE_MONITOR + `/${id}`, monitor); }; + +export interface TestNowResponse { + errors?: Array<{ error: Error }>; + testRunId: string; + monitorId: string; +} + +export const testNowMonitor = async (configId: string): Promise => { + return await apiService.get(API_URLS.TRIGGER_MONITOR + `/${configId}`); +}; diff --git a/x-pack/plugins/uptime/public/state/api/utils.ts b/x-pack/plugins/uptime/public/state/api/utils.ts index 8dcef2239ca22..833a6845d6343 100644 --- a/x-pack/plugins/uptime/public/state/api/utils.ts +++ b/x-pack/plugins/uptime/public/state/api/utils.ts @@ -5,41 +5,11 @@ * 2.0. */ -import { PathReporter } from 'io-ts/lib/PathReporter'; import { isRight } from 'fp-ts/lib/Either'; +import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { HttpFetchQuery, HttpSetup } from 'src/core/public'; -import * as t from 'io-ts'; import { FETCH_STATUS, AddInspectorRequest } from '../../../../observability/public'; -function isObject(value: unknown) { - const type = typeof value; - return value != null && (type === 'object' || type === 'function'); -} - -/** - * @deprecated Use packages/kbn-securitysolution-io-ts-utils/src/format_errors/index.ts - */ -export const formatErrors = (errors: t.Errors): string[] => { - return errors.map((error) => { - if (error.message != null) { - return error.message; - } else { - const keyContext = error.context - .filter( - (entry) => entry.key != null && !Number.isInteger(+entry.key) && entry.key.trim() !== '' - ) - .map((entry) => entry.key) - .join('.'); - - const nameContext = error.context.find((entry) => entry.type?.name?.length > 0); - const suppliedValue = - keyContext !== '' ? keyContext : nameContext != null ? nameContext.type.name : ''; - const value = isObject(error.value) ? JSON.stringify(error.value) : error.value; - return `Invalid value "${value}" supplied to "${suppliedValue}"`; - } - }); -}; - class ApiService { private static instance: ApiService; private _http!: HttpSetup; @@ -109,6 +79,8 @@ class ApiService { body: JSON.stringify(data), }); + this.addInspectorRequest?.({ data: response, status: FETCH_STATUS.SUCCESS, loading: false }); + if (decodeType) { const decoded = decodeType.decode(response); if (isRight(decoded)) { @@ -116,7 +88,7 @@ class ApiService { } else { // eslint-disable-next-line no-console console.warn( - `API ${apiUrl} is not returning expected response, ${PathReporter.report(decoded)}` + `API ${apiUrl} is not returning expected response, ${formatErrors(decoded.left)}` ); } } @@ -136,7 +108,7 @@ class ApiService { } else { // eslint-disable-next-line no-console console.warn( - `API ${apiUrl} is not returning expected response, ${PathReporter.report(decoded)}` + `API ${apiUrl} is not returning expected response, ${formatErrors(decoded.left)}` ); } } diff --git a/x-pack/plugins/uptime/public/state/effects/index.ts b/x-pack/plugins/uptime/public/state/effects/index.ts index b1a51c5df096c..07b04f8c27c3d 100644 --- a/x-pack/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/plugins/uptime/public/state/effects/index.ts @@ -7,7 +7,11 @@ import { fork } from 'redux-saga/effects'; import { fetchMonitorDetailsEffect } from './monitor'; -import { fetchMonitorListEffect } from './monitor_list'; +import { + fetchMonitorListEffect, + fetchRunNowMonitorEffect, + fetchUpdatedMonitorEffect, +} from './monitor_list'; import { fetchMonitorManagementEffect } from './monitor_management'; import { fetchMonitorStatusEffect } from './monitor_status'; import { fetchDynamicSettingsEffect, setDynamicSettingsEffect } from './dynamic_settings'; @@ -27,6 +31,7 @@ import { export function* rootEffect() { yield fork(fetchMonitorDetailsEffect); yield fork(fetchMonitorListEffect); + yield fork(fetchUpdatedMonitorEffect); yield fork(fetchMonitorManagementEffect); yield fork(fetchMonitorStatusEffect); yield fork(fetchDynamicSettingsEffect); @@ -42,4 +47,5 @@ export function* rootEffect() { yield fork(fetchScreenshotBlocks); yield fork(generateBlockStatsOnPut); yield fork(pruneBlockCache); + yield fork(fetchRunNowMonitorEffect); } diff --git a/x-pack/plugins/uptime/public/state/effects/monitor_list.ts b/x-pack/plugins/uptime/public/state/effects/monitor_list.ts index 154d32ca47a7e..35322c3768190 100644 --- a/x-pack/plugins/uptime/public/state/effects/monitor_list.ts +++ b/x-pack/plugins/uptime/public/state/effects/monitor_list.ts @@ -5,9 +5,15 @@ * 2.0. */ -import { takeLatest } from 'redux-saga/effects'; -import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions'; -import { fetchMonitorList } from '../api'; +import { takeEvery, takeLatest } from 'redux-saga/effects'; +import { + getMonitorList, + getMonitorListSuccess, + getMonitorListFailure, + getUpdatedMonitor, + testNowMonitorAction, +} from '../actions'; +import { fetchMonitorList, testNowMonitor } from '../api'; import { fetchEffectFactory } from './fetch_effect'; export function* fetchMonitorListEffect() { @@ -16,3 +22,17 @@ export function* fetchMonitorListEffect() { fetchEffectFactory(fetchMonitorList, getMonitorListSuccess, getMonitorListFailure) ); } + +export function* fetchUpdatedMonitorEffect() { + yield takeLatest( + getUpdatedMonitor.get, + fetchEffectFactory(fetchMonitorList, getUpdatedMonitor.success, getUpdatedMonitor.fail) + ); +} + +export function* fetchRunNowMonitorEffect() { + yield takeEvery( + testNowMonitorAction.get, + fetchEffectFactory(testNowMonitor, testNowMonitorAction.success, testNowMonitorAction.fail) + ); +} diff --git a/x-pack/plugins/uptime/public/state/reducers/index.ts b/x-pack/plugins/uptime/public/state/reducers/index.ts index 0142d271159ce..2b6986c88b746 100644 --- a/x-pack/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/plugins/uptime/public/state/reducers/index.ts @@ -23,6 +23,7 @@ import { journeyReducer } from './journey'; import { networkEventsReducer } from './network_events'; import { syntheticsReducer } from './synthetics'; import { monitorManagementListReducer } from './monitor_management'; +import { testNowRunsReducer } from './test_now_runs'; export const rootReducer = combineReducers({ monitor: monitorReducer, @@ -42,4 +43,5 @@ export const rootReducer = combineReducers({ journeys: journeyReducer, networkEvents: networkEventsReducer, synthetics: syntheticsReducer, + testNowRuns: testNowRunsReducer, }); diff --git a/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts b/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts index 2e833bd033c46..8083b50367fe7 100644 --- a/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts +++ b/x-pack/plugins/uptime/public/state/reducers/monitor_list.ts @@ -7,13 +7,24 @@ import { handleActions, Action } from 'redux-actions'; import { IHttpFetchError, ResponseErrorBody } from 'src/core/public'; -import { getMonitorList, getMonitorListSuccess, getMonitorListFailure } from '../actions'; +import { + getMonitorList, + getMonitorListSuccess, + getMonitorListFailure, + getUpdatedMonitor, + clearRefreshedMonitorId, + setUpdatingMonitorId, +} from '../actions'; import { MonitorSummariesResult } from '../../../common/runtime_types'; +import { AppState } from '../index'; +import { TestNowResponse } from '../api'; export interface MonitorList { - error?: IHttpFetchError; loading: boolean; + refreshedMonitorIds?: string[]; + isUpdating?: string[]; list: MonitorSummariesResult; + error?: IHttpFetchError; } export const initialState: MonitorList = { @@ -23,9 +34,13 @@ export const initialState: MonitorList = { summaries: [], }, loading: false, + refreshedMonitorIds: [], }; -type Payload = MonitorSummariesResult & IHttpFetchError; +type Payload = MonitorSummariesResult & + IHttpFetchError & + string & + TestNowResponse; export const monitorListReducer = handleActions( { @@ -50,6 +65,64 @@ export const monitorListReducer = handleActions( error: action.payload, loading: false, }), + [String(setUpdatingMonitorId)]: (state: MonitorList, action: Action) => ({ + ...state, + isUpdating: [...(state.isUpdating ?? []), action.payload], + }), + [String(getUpdatedMonitor.get)]: (state: MonitorList) => ({ + ...state, + }), + [String(getUpdatedMonitor.success)]: ( + state: MonitorList, + action: Action + ) => { + const summaries = state.list.summaries; + + const newSummary = action.payload.summaries?.[0]; + + if (!newSummary) { + return { ...state, isUpdating: [] }; + } + + return { + ...state, + loading: false, + error: undefined, + isUpdating: state.isUpdating?.filter((item) => item !== newSummary.monitor_id), + refreshedMonitorIds: [...(state.refreshedMonitorIds ?? []), newSummary.monitor_id], + list: { + ...state.list, + summaries: summaries.map((summary) => { + if (summary.monitor_id === newSummary.monitor_id) { + return newSummary; + } + return summary; + }), + }, + }; + }, + [String(getUpdatedMonitor.fail)]: ( + state: MonitorList, + action: Action> + ) => ({ + ...state, + error: action.payload, + loading: false, + isUpdating: [], + }), + [String(clearRefreshedMonitorId)]: (state: MonitorList, action: Action) => ({ + ...state, + refreshedMonitorIds: (state.refreshedMonitorIds ?? []).filter( + (item) => item !== action.payload + ), + }), }, initialState ); + +export const refreshedMonitorSelector = ({ monitorList }: AppState) => { + return monitorList.refreshedMonitorIds ?? []; +}; + +export const isUpdatingMonitorSelector = ({ monitorList }: AppState) => + monitorList.isUpdating ?? []; diff --git a/x-pack/plugins/uptime/public/state/reducers/test_now_runs.ts b/x-pack/plugins/uptime/public/state/reducers/test_now_runs.ts new file mode 100644 index 0000000000000..4b0ce493ba13c --- /dev/null +++ b/x-pack/plugins/uptime/public/state/reducers/test_now_runs.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { createReducer, PayloadAction } from '@reduxjs/toolkit'; +import { WritableDraft } from 'immer/dist/types/types-external'; +import { clearTestNowMonitorAction, testNowMonitorAction } from '../actions'; +import { TestNowResponse } from '../api'; +import { AppState } from '../index'; + +export enum TestRunStats { + LOADING = 'loading', + IN_PROGRESS = 'in-progress', + COMPLETED = 'completed', +} + +interface TestNowRun { + monitorId: string; + testRunId?: string; + status: TestRunStats; +} + +export interface TestNowRunsState { + testNowRuns: TestNowRun[]; +} + +export const initialState: TestNowRunsState = { + testNowRuns: [], +}; + +export const testNowRunsReducer = createReducer(initialState, (builder) => { + builder + .addCase( + String(testNowMonitorAction.get), + (state: WritableDraft, action: PayloadAction) => ({ + ...state, + testNowRuns: [ + ...state.testNowRuns, + { monitorId: action.payload, status: TestRunStats.LOADING }, + ], + }) + ) + .addCase( + String(testNowMonitorAction.success), + (state: WritableDraft, { payload }: PayloadAction) => ({ + ...state, + testNowRuns: state.testNowRuns.map((tRun) => + tRun.monitorId === payload.monitorId + ? { + monitorId: payload.monitorId, + testRunId: payload.testRunId, + status: TestRunStats.IN_PROGRESS, + } + : tRun + ), + }) + ) + .addCase( + String(testNowMonitorAction.fail), + (state: WritableDraft, action: PayloadAction) => ({ + ...state, + testNowRuns: [...(state.testNowRuns ?? [])], + }) + ) + .addCase( + String(clearTestNowMonitorAction), + (state: WritableDraft, action: PayloadAction) => ({ + ...state, + testNowRuns: state.testNowRuns.filter((tRun) => tRun.monitorId !== action.payload), + }) + ); +}); + +export const testNowRunsSelector = ({ testNowRuns }: AppState) => testNowRuns.testNowRuns; + +export const testNowRunSelector = + (monitorId?: string) => + ({ testNowRuns }: AppState) => + testNowRuns.testNowRuns.find((tRun) => monitorId && monitorId === tRun.monitorId); diff --git a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts index f72af3311affe..de09c686d4441 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts @@ -11,6 +11,7 @@ import { CollectorFetchContext, UsageCollectionSetup } from 'src/plugins/usage_c import { PageViewParams, UptimeTelemetry, Usage } from './types'; import { savedObjectsAdapter } from '../../saved_objects/saved_objects'; import { UptimeESClient, createUptimeESClient } from '../../lib'; +import { createEsQuery } from '../../../../common/utils/es_search'; interface UptimeTelemetryCollector { [key: number]: UptimeTelemetry; @@ -215,7 +216,7 @@ export class KibanaTelemetryAdapter { savedObjectsClient: SavedObjectsClientContract ) { const dynamicSettings = await savedObjectsAdapter.getUptimeDynamicSettings(savedObjectsClient); - const params = { + const params = createEsQuery({ index: dynamicSettings.heartbeatIndices, body: { query: { @@ -229,6 +230,11 @@ export class KibanaTelemetryAdapter { }, }, }, + { + exists: { + field: 'summary', + }, + }, ], }, }, @@ -271,9 +277,9 @@ export class KibanaTelemetryAdapter { }, }, }, - }; + }); - const { body: result } = await callCluster.search(params); + const { body: result } = await callCluster.search(params, 'telemetryLog'); const numberOfUniqueMonitors: number = result?.aggregations?.unique_monitors?.value ?? 0; const numberOfUniqueLocations: number = result?.aggregations?.unique_locations?.value ?? 0; @@ -318,6 +324,11 @@ export class KibanaTelemetryAdapter { }, }, }, + { + exists: { + field: 'summary', + }, + }, { term: { 'monitor.fleet_managed': true, @@ -356,7 +367,7 @@ export class KibanaTelemetryAdapter { }, }; - const { body: result } = await callCluster.search(params); + const { body: result } = await callCluster.search(params, 'telemetryLogFleet'); const numberOfUniqueMonitors: number = result?.aggregations?.unique_monitors?.value ?? 0; const monitorNameStats = result?.aggregations?.monitor_name; diff --git a/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts b/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts index eceee3505dd7e..7095ae8e38ab5 100644 --- a/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts +++ b/x-pack/plugins/uptime/server/lib/adapters/telemetry/types.ts @@ -12,6 +12,7 @@ export interface PageViewParams { autoRefreshEnabled: boolean; autorefreshInterval: number; refreshTelemetryHistory?: boolean; + refreshEsData?: boolean; } export interface Stats { diff --git a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts index f8357e8066573..5a714fd2514d8 100644 --- a/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts +++ b/x-pack/plugins/uptime/server/lib/requests/search/refine_potential_matches.ts @@ -83,11 +83,13 @@ export const summaryPingsToSummary = (summaryPings: Ping[]): MonitorSummary => { const latest = summaryPings[summaryPings.length - 1]; return { monitor_id: latest.monitor.id, + configId: latest.config_id, state: { timestamp: latest.timestamp, monitor: { name: latest.monitor?.name, type: latest.monitor?.type, + duration: latest.monitor?.duration, }, url: latest.url ?? {}, summary: { diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts index 79164cade9aa4..20f11fe3b8900 100644 --- a/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/synthetics_service.ts @@ -179,7 +179,15 @@ export class SyntheticsService { }; } - async pushConfigs(request?: KibanaRequest, configs?: SyntheticsMonitorWithId[]) { + async pushConfigs( + request?: KibanaRequest, + configs?: Array< + SyntheticsMonitorWithId & { + fields_under_root?: boolean; + fields?: { config_id: string }; + } + > + ) { const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs())); if (monitors.length === 0) { this.logger.debug('No monitor found which can be pushed to service.'); @@ -226,6 +234,32 @@ export class SyntheticsService { } } + async triggerConfigs( + request?: KibanaRequest, + configs?: Array< + SyntheticsMonitorWithId & { + fields_under_root?: boolean; + fields?: { config_id: string; test_run_id: string }; + } + > + ) { + const monitors = this.formatConfigs(configs || (await this.getMonitorConfigs())); + if (monitors.length === 0) { + return; + } + const data = { + monitors, + output: await this.getOutput(request), + }; + + try { + return await this.apiClient.runOnce(data); + } catch (e) { + this.logger.error(e); + throw e; + } + } + async deleteConfigs(request: KibanaRequest, configs: SyntheticsMonitorWithId[]) { const data = { monitors: this.formatConfigs(configs), diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 383d999f29cc6..780a67c0941e1 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -37,6 +37,7 @@ import { addSyntheticsMonitorRoute } from './synthetics_service/add_monitor'; import { editSyntheticsMonitorRoute } from './synthetics_service/edit_monitor'; import { deleteSyntheticsMonitorRoute } from './synthetics_service/delete_monitor'; import { runOnceSyntheticsMonitorRoute } from './synthetics_service/run_once_monitor'; +import { testNowMonitorRoute } from './synthetics_service/test_now_monitor'; export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; @@ -69,4 +70,5 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [ editSyntheticsMonitorRoute, deleteSyntheticsMonitorRoute, runOnceSyntheticsMonitorRoute, + testNowMonitorRoute, ]; diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts index 1750466b6c3e6..38def648f6e5c 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/add_monitor.ts @@ -38,6 +38,10 @@ export const addSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ { ...newMonitor.attributes, id: newMonitor.id, + fields: { + config_id: newMonitor.id, + }, + fields_under_root: true, }, ]); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts index a95876d8d3ea6..530716f709f64 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/edit_monitor.ts @@ -47,6 +47,10 @@ export const editSyntheticsMonitorRoute: UMRestApiRouteFactory = () => ({ { ...(editMonitor.attributes as SyntheticsMonitor), id: editMonitor.id, + fields: { + config_id: editMonitor.id, + }, + fields_under_root: true, }, ]); diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/monitor_validation.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/monitor_validation.ts index 9f14c414e5cad..446eaa5847319 100644 --- a/x-pack/plugins/uptime/server/rest_api/synthetics_service/monitor_validation.ts +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/monitor_validation.ts @@ -6,7 +6,7 @@ */ import { isLeft } from 'fp-ts/lib/Either'; -import { PathReporter } from 'io-ts/lib/PathReporter'; +import { formatErrors } from '@kbn/securitysolution-io-ts-utils'; import { BrowserFieldsCodec, @@ -49,7 +49,7 @@ export function validateMonitor(monitorFields: MonitorFields): { return { valid: false, reason: `Monitor type is invalid`, - details: PathReporter.report(decodedType).join(' | '), + details: formatErrors(decodedType.left).join(' | '), payload: monitorFields, }; } @@ -72,7 +72,7 @@ export function validateMonitor(monitorFields: MonitorFields): { return { valid: false, reason: `Monitor is not a valid monitor of type ${monitorType}`, - details: PathReporter.report(decodedMonitor).join(' | '), + details: formatErrors(decodedMonitor.left).join(' | '), payload: monitorFields, }; } diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/test_now_monitor.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/test_now_monitor.ts new file mode 100644 index 0000000000000..2ad270db213fb --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/test_now_monitor.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { schema } from '@kbn/config-schema'; +import { v4 as uuidv4 } from 'uuid'; +import { SyntheticsMonitor } from '../../../common/runtime_types'; +import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; +import { syntheticsMonitorType } from '../../lib/saved_objects/synthetics_monitor'; + +export const testNowMonitorRoute: UMRestApiRouteFactory = () => ({ + method: 'GET', + path: API_URLS.TRIGGER_MONITOR + '/{monitorId}', + validate: { + params: schema.object({ + monitorId: schema.string({ minLength: 1, maxLength: 1024 }), + }), + }, + handler: async ({ request, savedObjectsClient, response, server }): Promise => { + const { monitorId } = request.params; + const monitor = await savedObjectsClient.get( + syntheticsMonitorType, + monitorId + ); + + const { syntheticsService } = server; + + const testRunId = uuidv4(); + + const errors = await syntheticsService.triggerConfigs(request, [ + { + ...monitor.attributes, + id: monitorId, + fields_under_root: true, + fields: { config_id: monitorId, test_run_id: testRunId }, + }, + ]); + + if (errors && errors?.length > 0) { + return { errors, testRunId, monitorId }; + } + + return { testRunId, monitorId }; + }, +}); diff --git a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts index ec7de05dd2cf1..eb3447d85424b 100644 --- a/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts +++ b/x-pack/plugins/uptime/server/rest_api/telemetry/log_page_view.ts @@ -21,18 +21,25 @@ export const createLogPageViewRoute: UMRestApiRouteFactory = () => ({ dateEnd: schema.string(), autoRefreshEnabled: schema.boolean(), autorefreshInterval: schema.number(), + refreshEsData: schema.maybe(schema.boolean()), refreshTelemetryHistory: schema.maybe(schema.boolean()), }), }, handler: async ({ savedObjectsClient, uptimeEsClient, request }): Promise => { const pageView = request.body as PageViewParams; if (pageView.refreshTelemetryHistory) { + // this is primarily only used for API testing KibanaTelemetryAdapter.clearLocalTelemetry(); } - await KibanaTelemetryAdapter.countNoOfUniqueMonitorAndLocations( - uptimeEsClient, - savedObjectsClient - ); + + if (pageView.refreshEsData) { + // this is primarily only used for API testing + + await KibanaTelemetryAdapter.countNoOfUniqueMonitorAndLocations( + uptimeEsClient, + savedObjectsClient + ); + } await KibanaTelemetryAdapter.countNoOfUniqueFleetManagedMonitors(uptimeEsClient); return KibanaTelemetryAdapter.countPageView(pageView as PageViewParams); }, diff --git a/x-pack/plugins/ux/.prettierrc b/x-pack/plugins/ux/.prettierrc new file mode 100644 index 0000000000000..650cb880f6f5a --- /dev/null +++ b/x-pack/plugins/ux/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "semi": true +} diff --git a/x-pack/plugins/ux/.storybook/main.js b/x-pack/plugins/ux/.storybook/main.js new file mode 100644 index 0000000000000..86b48c32f103e --- /dev/null +++ b/x-pack/plugins/ux/.storybook/main.js @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/x-pack/plugins/ux/CONTRIBUTING.md b/x-pack/plugins/ux/CONTRIBUTING.md new file mode 100644 index 0000000000000..722de5d4d29ce --- /dev/null +++ b/x-pack/plugins/ux/CONTRIBUTING.md @@ -0,0 +1 @@ +## Observability User Experience Application diff --git a/x-pack/plugins/ux/common/agent_name.ts b/x-pack/plugins/ux/common/agent_name.ts new file mode 100644 index 0000000000000..d8e079400bff4 --- /dev/null +++ b/x-pack/plugins/ux/common/agent_name.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AgentName } from '../../apm/typings/es_schemas/ui/fields/agent'; + +export const RUM_AGENT_NAMES: AgentName[] = [ + 'js-base', + 'rum-js', + 'opentelemetry/webjs', +]; + +export function isRumAgentName( + agentName?: string +): agentName is 'js-base' | 'rum-js' | 'opentelemetry/webjs' { + return RUM_AGENT_NAMES.includes(agentName! as AgentName); +} diff --git a/x-pack/plugins/ux/common/elasticsearch_fieldnames.ts b/x-pack/plugins/ux/common/elasticsearch_fieldnames.ts new file mode 100644 index 0000000000000..9bfc0f7596d09 --- /dev/null +++ b/x-pack/plugins/ux/common/elasticsearch_fieldnames.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SERVICE = 'service'; +export const SERVICE_NAME = 'service.name'; +export const SERVICE_ENVIRONMENT = 'service.environment'; + +export const AGENT = 'agent'; +export const AGENT_NAME = 'agent.name'; +export const AGENT_VERSION = 'agent.version'; + +export const URL_FULL = 'url.full'; +export const USER_AGENT_NAME = 'user_agent.name'; + +export const TRANSACTION_DURATION = 'transaction.duration.us'; +export const TRANSACTION_TYPE = 'transaction.type'; +export const TRANSACTION_RESULT = 'transaction.result'; +export const TRANSACTION_NAME = 'transaction.name'; +export const TRANSACTION_ID = 'transaction.id'; + +export const CLIENT_GEO_COUNTRY_ISO_CODE = 'client.geo.country_iso_code'; + +// RUM Labels +export const TRANSACTION_URL = 'url.full'; +export const CLIENT_GEO = 'client.geo'; +export const USER_AGENT_DEVICE = 'user_agent.device.name'; +export const USER_AGENT_OS = 'user_agent.os.name'; + +export const TRANSACTION_TIME_TO_FIRST_BYTE = + 'transaction.marks.agent.timeToFirstByte'; +export const TRANSACTION_DOM_INTERACTIVE = + 'transaction.marks.agent.domInteractive'; + +export const FCP_FIELD = 'transaction.marks.agent.firstContentfulPaint'; +export const LCP_FIELD = 'transaction.marks.agent.largestContentfulPaint'; +export const TBT_FIELD = 'transaction.experience.tbt'; +export const FID_FIELD = 'transaction.experience.fid'; +export const CLS_FIELD = 'transaction.experience.cls'; diff --git a/x-pack/plugins/ux/common/environment_filter_values.ts b/x-pack/plugins/ux/common/environment_filter_values.ts new file mode 100644 index 0000000000000..e8b805e5326dd --- /dev/null +++ b/x-pack/plugins/ux/common/environment_filter_values.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { SERVICE_ENVIRONMENT } from './elasticsearch_fieldnames'; +import { Environment } from './environment_rt'; + +const ENVIRONMENT_ALL_VALUE = 'ENVIRONMENT_ALL' as const; +const ENVIRONMENT_NOT_DEFINED_VALUE = 'ENVIRONMENT_NOT_DEFINED' as const; + +export function getEnvironmentLabel(environment: string) { + if (!environment || environment === ENVIRONMENT_NOT_DEFINED_VALUE) { + return i18n.translate('xpack.ux.filter.environment.notDefinedLabel', { + defaultMessage: 'Not defined', + }); + } + + if (environment === ENVIRONMENT_ALL_VALUE) { + return i18n.translate('xpack.ux.filter.environment.allLabel', { + defaultMessage: 'All', + }); + } + + return environment; +} + +export const ENVIRONMENT_ALL = { + value: ENVIRONMENT_ALL_VALUE, + text: getEnvironmentLabel(ENVIRONMENT_ALL_VALUE), +}; + +export const ENVIRONMENT_NOT_DEFINED = { + value: ENVIRONMENT_NOT_DEFINED_VALUE, + text: getEnvironmentLabel(ENVIRONMENT_NOT_DEFINED_VALUE), +}; + +export function getEnvironmentEsField(environment: string) { + if ( + !environment || + environment === ENVIRONMENT_NOT_DEFINED_VALUE || + environment === ENVIRONMENT_ALL_VALUE + ) { + return {}; + } + + return { [SERVICE_ENVIRONMENT]: environment }; +} + +// returns the environment url param that should be used +// based on the requested environment. If the requested +// environment is different from the URL parameter, we'll +// return ENVIRONMENT_ALL. If it's not, we'll just return +// the current environment URL param +export function getNextEnvironmentUrlParam({ + requestedEnvironment, + currentEnvironmentUrlParam, +}: { + requestedEnvironment?: string; + currentEnvironmentUrlParam: Environment; +}) { + const normalizedRequestedEnvironment = + requestedEnvironment || ENVIRONMENT_NOT_DEFINED.value; + const normalizedQueryEnvironment = + currentEnvironmentUrlParam || ENVIRONMENT_ALL.value; + + if (normalizedRequestedEnvironment === normalizedQueryEnvironment) { + return currentEnvironmentUrlParam || ENVIRONMENT_ALL.value; + } + + return ENVIRONMENT_ALL.value; +} diff --git a/x-pack/plugins/ux/common/environment_rt.ts b/x-pack/plugins/ux/common/environment_rt.ts new file mode 100644 index 0000000000000..e9337da9bdcf5 --- /dev/null +++ b/x-pack/plugins/ux/common/environment_rt.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import * as t from 'io-ts'; +import { nonEmptyStringRt } from '@kbn/io-ts-utils'; +import { + ENVIRONMENT_ALL, + ENVIRONMENT_NOT_DEFINED, +} from './environment_filter_values'; + +export const environmentRt = t.type({ + environment: t.union([ + t.literal(ENVIRONMENT_NOT_DEFINED.value), + t.literal(ENVIRONMENT_ALL.value), + nonEmptyStringRt, + ]), +}); + +export type Environment = t.TypeOf['environment']; diff --git a/x-pack/plugins/ux/common/fetch_options.ts b/x-pack/plugins/ux/common/fetch_options.ts new file mode 100644 index 0000000000000..2f862ec7cbc38 --- /dev/null +++ b/x-pack/plugins/ux/common/fetch_options.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { HttpFetchOptions } from 'kibana/public'; + +export type FetchOptions = Omit & { + pathname: string; + isCachable?: boolean; + method?: string; + body?: any; +}; diff --git a/x-pack/plugins/ux/common/index_pattern_constants.ts b/x-pack/plugins/ux/common/index_pattern_constants.ts new file mode 100644 index 0000000000000..4b67bba1fef91 --- /dev/null +++ b/x-pack/plugins/ux/common/index_pattern_constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const APM_STATIC_INDEX_PATTERN_ID = 'apm_static_index_pattern_id'; diff --git a/x-pack/plugins/ux/common/transaction_types.ts b/x-pack/plugins/ux/common/transaction_types.ts new file mode 100644 index 0000000000000..87c14fbd496f7 --- /dev/null +++ b/x-pack/plugins/ux/common/transaction_types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const TRANSACTION_PAGE_LOAD = 'page-load'; +export const TRANSACTION_REQUEST = 'request'; +export const TRANSACTION_ROUTE_CHANGE = 'route-change'; diff --git a/x-pack/plugins/ux/common/utils/pick_keys.ts b/x-pack/plugins/ux/common/utils/pick_keys.ts new file mode 100644 index 0000000000000..fe45e9a0e42c8 --- /dev/null +++ b/x-pack/plugins/ux/common/utils/pick_keys.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash'; + +export function pickKeys(obj: T, ...keys: K[]) { + return pick(obj, keys) as Pick; +} diff --git a/x-pack/plugins/ux/common/ux_ui_filter.ts b/x-pack/plugins/ux/common/ux_ui_filter.ts new file mode 100644 index 0000000000000..aeea1cddae30d --- /dev/null +++ b/x-pack/plugins/ux/common/ux_ui_filter.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { + CLIENT_GEO_COUNTRY_ISO_CODE, + SERVICE_NAME, + TRANSACTION_URL, + USER_AGENT_DEVICE, + USER_AGENT_NAME, + USER_AGENT_OS, +} from './elasticsearch_fieldnames'; + +export const uxFiltersByName = { + transactionUrl: { + title: i18n.translate('xpack.ux.localFilters.titles.transactionUrl', { + defaultMessage: 'URL', + }), + fieldName: TRANSACTION_URL, + }, + transactionUrlExcluded: { + title: i18n.translate('xpack.ux.localFilters.titles.transactionUrl', { + defaultMessage: 'URL', + }), + fieldName: TRANSACTION_URL, + excluded: true, + }, + browser: { + title: i18n.translate('xpack.ux.localFilters.titles.browser', { + defaultMessage: 'Browser', + }), + fieldName: USER_AGENT_NAME, + }, + browserExcluded: { + title: i18n.translate('xpack.ux.localFilters.titles.browser', { + defaultMessage: 'Browser', + }), + fieldName: USER_AGENT_NAME, + excluded: true, + }, + device: { + title: i18n.translate('xpack.ux.localFilters.titles.device', { + defaultMessage: 'Device', + }), + fieldName: USER_AGENT_DEVICE, + }, + deviceExcluded: { + title: i18n.translate('xpack.ux.localFilters.titles.device', { + defaultMessage: 'Device', + }), + fieldName: USER_AGENT_DEVICE, + excluded: true, + }, + location: { + title: i18n.translate('xpack.ux.localFilters.titles.location', { + defaultMessage: 'Location', + }), + fieldName: CLIENT_GEO_COUNTRY_ISO_CODE, + }, + locationExcluded: { + title: i18n.translate('xpack.ux.localFilters.titles.location', { + defaultMessage: 'Location', + }), + fieldName: CLIENT_GEO_COUNTRY_ISO_CODE, + excluded: true, + }, + os: { + title: i18n.translate('xpack.ux.localFilters.titles.os', { + defaultMessage: 'OS', + }), + fieldName: USER_AGENT_OS, + }, + osExcluded: { + title: i18n.translate('xpack.ux.localFilters.titles.os', { + defaultMessage: 'OS', + }), + fieldName: USER_AGENT_OS, + excluded: true, + }, + serviceName: { + title: i18n.translate('xpack.ux.localFilters.titles.serviceName', { + defaultMessage: 'Service name', + }), + fieldName: SERVICE_NAME, + }, +}; + +export type UxLocalUIFilterName = keyof typeof uxFiltersByName; + +export interface UxLocalUIFilter { + name: UxLocalUIFilterName; + title: string; + fieldName: string; + excluded?: boolean; + value: string[]; +} + +type UxLocalUIFilterMap = { + [key in UxLocalUIFilterName]: UxLocalUIFilter; +}; + +export const uxLocalUIFilterNames = Object.keys( + uxFiltersByName +) as UxLocalUIFilterName[]; + +export const uxLocalUIFilters = uxLocalUIFilterNames.reduce((acc, key) => { + const field = uxFiltersByName[key]; + + return { + ...acc, + [key]: { + ...field, + name: key, + }, + }; +}, {} as UxLocalUIFilterMap); diff --git a/x-pack/plugins/ux/jest.config.js b/x-pack/plugins/ux/jest.config.js new file mode 100644 index 0000000000000..f380a059e5547 --- /dev/null +++ b/x-pack/plugins/ux/jest.config.js @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +const path = require('path'); + +module.exports = { + preset: '@kbn/test', + rootDir: path.resolve(__dirname, '../../..'), + roots: ['/x-pack/plugins/ux'], +}; diff --git a/x-pack/plugins/ux/kibana.json b/x-pack/plugins/ux/kibana.json new file mode 100644 index 0000000000000..bc57b115b7299 --- /dev/null +++ b/x-pack/plugins/ux/kibana.json @@ -0,0 +1,33 @@ +{ + "id": "ux", + "version": "8.0.0", + "kibanaVersion": "kibana", + "requiredPlugins": [ + "features", + "data", + "licensing", + "triggersActionsUi", + "embeddable", + "infra", + "inspector", + "apm" + ], + "optionalPlugins": [ + "cloud", + "usageCollection", + "taskManager", + "actions", + "alerts", + "observability", + "security", + "maps" + ], + "server": false, + "ui": true, + "configPath": ["xpack", "ux"], + "requiredBundles": ["kibanaReact", "observability", "maps"], + "owner": { + "name": "Uptime", + "githubTeam": "uptime" + } +} diff --git a/x-pack/plugins/ux/public/application/application.test.tsx b/x-pack/plugins/ux/public/application/application.test.tsx new file mode 100644 index 0000000000000..15b351581c680 --- /dev/null +++ b/x-pack/plugins/ux/public/application/application.test.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiErrorBoundary } from '@elastic/eui'; +import { mount } from 'enzyme'; + +import { UXAppRoot } from './ux_app'; +import { RumHome } from '../components/app/rum_dashboard/rum_home'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { createObservabilityRuleTypeRegistryMock } from '../../../observability/public'; +import { merge } from 'lodash'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/common'; +import { embeddablePluginMock } from '../../../../../src/plugins/embeddable/public/mocks'; + +jest.mock('../services/rest/data_view', () => ({ + createStaticDataView: () => Promise.resolve(undefined), +})); + +jest.mock('../components/app/rum_dashboard/rum_home', () => ({ + RumHome: () =>

Home Mock

, +})); + +const mockPlugin = { + data: { + query: { + timefilter: { timefilter: { setTime: () => {}, getTime: () => ({}) } }, + }, + }, + observability: { + isAlertingExperienceEnabled: () => false, + }, +}; + +const mockEmbeddable = embeddablePluginMock.createStartContract(); + +mockEmbeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({ + create: () => ({ + reload: jest.fn(), + setRenderTooltipContent: jest.fn(), + setLayerList: jest.fn(), + }), +})); + +const mockCorePlugins = { + embeddable: mockEmbeddable, + inspector: {}, + maps: {}, + observability: { + navigation: { + registerSections: () => jest.fn(), + PageTemplate: ({ children }: { children: React.ReactNode }) => ( +
hello worlds {children}
+ ), + }, + }, + data: {}, +}; +const coreStart = coreMock.createStart({ basePath: '/basepath' }); + +const mockCore = merge({}, coreStart, { + application: { + capabilities: { + apm: {}, + ml: {}, + }, + }, + uiSettings: { + get: (key: string) => { + const uiSettings: Record = { + [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: [ + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + ], + [UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS]: { + from: 'now-15m', + to: 'now', + }, + [UI_SETTINGS.TIMEPICKER_REFRESH_INTERVAL_DEFAULTS]: { + pause: false, + value: 100000, + }, + }; + return uiSettings[key]; + }, + }, +}); + +export const mockApmPluginContextValue = { + appMountParameters: coreMock.createAppMountParameters('/basepath'), + core: mockCore, + plugins: mockPlugin, + observabilityRuleTypeRegistry: createObservabilityRuleTypeRegistryMock(), + corePlugins: mockCorePlugins, + deps: {}, +}; + +describe('renderUxApp', () => { + it('has an error boundary for the UXAppRoot', async () => { + const wrapper = mount( + + ); + + wrapper + .find(RumHome) + .simulateError(new Error('Oh no, an unexpected error!')); + + expect(wrapper.find(RumHome)).toHaveLength(0); + expect(wrapper.find(EuiErrorBoundary)).toHaveLength(1); + expect(wrapper.find(EuiErrorBoundary).text()).toMatch( + /Error: Oh no, an unexpected error!/ + ); + }); +}); diff --git a/x-pack/plugins/apm/public/application/ux_app.tsx b/x-pack/plugins/ux/public/application/ux_app.tsx similarity index 54% rename from x-pack/plugins/apm/public/application/ux_app.tsx rename to x-pack/plugins/ux/public/application/ux_app.tsx index 982b3b7210ee6..29ec41ab67ab5 100644 --- a/x-pack/plugins/apm/public/application/ux_app.tsx +++ b/x-pack/plugins/ux/public/application/ux_app.tsx @@ -7,46 +7,55 @@ import { euiLightVars, euiDarkVars } from '@kbn/ui-theme'; import { EuiErrorBoundary } from '@elastic/eui'; -import { AppMountParameters, CoreStart } from 'kibana/public'; import React from 'react'; import ReactDOM from 'react-dom'; -import { Route as ReactRouterRoute } from 'react-router-dom'; -import { RouterProvider, createRouter } from '@kbn/typed-react-router-config'; +import { Redirect } from 'react-router-dom'; import { DefaultTheme, ThemeProvider } from 'styled-components'; +import { RouterProvider, createRouter } from '@kbn/typed-react-router-config'; import { i18n } from '@kbn/i18n'; -import type { ObservabilityRuleTypeRegistry } from '../../../observability/public'; +import { RouteComponentProps, RouteProps } from 'react-router-dom'; +import { + AppMountParameters, + CoreStart, + APP_WRAPPER_CLASS, +} from '../../../../../src/core/public'; + import { KibanaContextProvider, RedirectAppLinks, useUiSetting$, } from '../../../../../src/plugins/kibana_react/public'; -import { APMRouteDefinition } from '../application/routes'; -import { ScrollToTopOnPathChange } from '../components/app/main/scroll_to_top_on_path_change'; + import { - RumHome, DASHBOARD_LABEL, + RumHome, } from '../components/app/rum_dashboard/rum_home'; -import { ApmPluginContext } from '../context/apm_plugin/apm_plugin_context'; -import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; -import { ConfigSchema } from '../index'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; -import { createCallApmApi } from '../services/rest/create_call_apm_api'; -import { createStaticDataView } from '../services/rest/data_view'; import { UXActionMenu } from '../components/app/rum_dashboard/action_menu'; -import { redirectTo } from '../components/routing/redirect_to'; + import { InspectorContextProvider, useBreadcrumbs, } from '../../../observability/public'; -import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; -import { APP_WRAPPER_CLASS } from '../../../../../src/core/public'; -import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; +import { createStaticDataView } from '../services/rest/data_view'; +import { createCallApmApi } from '../services/rest/create_call_apm_api'; +import { useKibanaServices } from '../hooks/use_kibana_services'; + +export type BreadcrumbTitle = + | string + | ((props: RouteComponentProps) => string) + | null; -export const uxRoutes: APMRouteDefinition[] = [ +export interface RouteDefinition extends RouteProps { + breadcrumb: BreadcrumbTitle; +} + +export const uxRoutes: RouteDefinition[] = [ { exact: true, path: '/', - render: redirectTo('/ux'), + render: () => , breadcrumb: DASHBOARD_LABEL, }, ]; @@ -54,18 +63,18 @@ export const uxRoutes: APMRouteDefinition[] = [ function UxApp() { const [darkMode] = useUiSetting$('theme:darkMode'); - const { core } = useApmPluginContext(); - const basePath = core.http.basePath.get(); + const { http } = useKibanaServices(); + const basePath = http.basePath.get(); useBreadcrumbs([ { - text: i18n.translate('xpack.apm.ux.breadcrumbs.root', { + text: i18n.translate('xpack.ux.breadcrumbs.root', { defaultMessage: 'User Experience', }), href: basePath + '/app/ux', }, { - text: i18n.translate('xpack.apm.ux.breadcrumbs.dashboard', { + text: i18n.translate('xpack.ux.breadcrumbs.dashboard', { defaultMessage: 'Dashboard', }), }, @@ -84,66 +93,57 @@ function UxApp() { data-test-subj="csmMainContainer" role="main" > -
); } -const uxRouter = createRouter({}); +export const uxRouter = createRouter({}); export function UXAppRoot({ appMountParameters, core, deps, - config, corePlugins: { embeddable, inspector, maps, observability, data }, - observabilityRuleTypeRegistry, }: { appMountParameters: AppMountParameters; core: CoreStart; deps: ApmPluginSetupDeps; - config: ConfigSchema; corePlugins: ApmPluginStartDeps; - observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; }) { const { history } = appMountParameters; const i18nCore = core.i18n; const plugins = { ...deps, maps }; - const apmPluginContextValue = { - appMountParameters, - config, - core, - inspector, - plugins, - observability, - observabilityRuleTypeRegistry, - }; return ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + ); } @@ -156,18 +156,14 @@ export const renderApp = ({ core, deps, appMountParameters, - config, corePlugins, - observabilityRuleTypeRegistry, }: { core: CoreStart; deps: ApmPluginSetupDeps; appMountParameters: AppMountParameters; - config: ConfigSchema; corePlugins: ApmPluginStartDeps; - observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry; }) => { - const { element, theme$ } = appMountParameters; + const { element } = appMountParameters; createCallApmApi(core); @@ -178,16 +174,12 @@ export const renderApp = ({ }); ReactDOM.render( - - - , + , element ); return () => { diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/action_menu/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx similarity index 79% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/action_menu/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx index 1215048bc55a0..84c297dbdea29 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/action_menu/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/index.tsx @@ -14,17 +14,17 @@ import { HeaderMenuPortal, } from '../../../../../../observability/public'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { AppMountParameters } from '../../../../../../../../src/core/public'; -import { InspectorHeaderLink } from '../../../shared/apm_header_action_menu/inspector_header_link'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; +import { UxInspectorHeaderLink } from './inpector_link'; +import { useKibanaServices } from '../../../../hooks/use_kibana_services'; -const ANALYZE_DATA = i18n.translate('xpack.apm.analyzeDataButtonLabel', { +const ANALYZE_DATA = i18n.translate('xpack.ux.analyzeDataButtonLabel', { defaultMessage: 'Explore data', }); const ANALYZE_MESSAGE = i18n.translate( - 'xpack.apm.analyzeDataButtonLabel.message', + 'xpack.ux.analyzeDataButtonLabel.message', { defaultMessage: 'Explore Data allows you to select and filter result data in any dimension and look for the cause or impact of performance problems.', @@ -36,9 +36,7 @@ export function UXActionMenu({ }: { appMountParameters: AppMountParameters; }) { - const { - services: { http }, - } = useKibana(); + const { http, application } = useKibanaServices(); const { urlParams } = useLegacyUrlParams(); const { rangeTo, rangeFrom, serviceName } = urlParams; @@ -57,11 +55,9 @@ export function UXActionMenu({ }, ], }, - http?.basePath.get() + http.basePath.get() ); - const kibana = useKibana(); - return ( - {i18n.translate('xpack.apm.addDataButtonLabel', { + {i18n.translate('xpack.ux.addDataButtonLabel', { defaultMessage: 'Add data', })} - + ); diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/action_menu/inpector_link.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/inpector_link.tsx similarity index 54% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/action_menu/inpector_link.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/inpector_link.tsx index fc7fad24edb56..98622184a5cec 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/action_menu/inpector_link.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/action_menu/inpector_link.tsx @@ -8,19 +8,17 @@ import { EuiHeaderLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; -import { enableInspectEsQueries } from '../../../../../../observability/common/ui_settings_keys'; -import { useInspectorContext } from '../../../../../../observability/public'; +import { + useInspectorContext, + enableInspectEsQueries, +} from '../../../../../../observability/public'; +import { useKibanaServices } from '../../../../hooks/use_kibana_services'; export function UxInspectorHeaderLink() { - const { inspector } = useApmPluginContext(); const { inspectorAdapters } = useInspectorContext(); - const { - services: { uiSettings }, - } = useKibana(); + const { uiSettings, inspector } = useKibanaServices(); - const isInspectorEnabled = uiSettings?.get(enableInspectEsQueries); + const isInspectorEnabled = uiSettings.get(enableInspectEsQueries); const inspect = () => { inspector.open(inspectorAdapters); @@ -32,7 +30,7 @@ export function UxInspectorHeaderLink() { return ( - {i18n.translate('xpack.apm.inspectButtonText', { + {i18n.translate('xpack.ux.inspectButtonText', { defaultMessage: 'Inspect', })} diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/breakdowns/breakdown_filter.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/breakdowns/breakdown_filter.tsx similarity index 86% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/breakdowns/breakdown_filter.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/breakdowns/breakdown_filter.tsx index 3bf8d5e58a1e4..dd2718db6cfff 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/breakdowns/breakdown_filter.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/breakdowns/breakdown_filter.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { EuiSuperSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; + import { CLIENT_GEO_COUNTRY_ISO_CODE, USER_AGENT_DEVICE, @@ -31,35 +32,35 @@ export function BreakdownFilter({ const items: BreakdownItem[] = [ { - name: i18n.translate('xpack.apm.csm.breakDownFilter.noBreakdown', { + name: i18n.translate('xpack.ux.breakDownFilter.noBreakdown', { defaultMessage: 'No breakdown', }), fieldName: NO_BREAKDOWN, type: 'category', }, { - name: i18n.translate('xpack.apm.csm.breakdownFilter.browser', { + name: i18n.translate('xpack.ux.breakdownFilter.browser', { defaultMessage: 'Browser', }), fieldName: USER_AGENT_NAME, type: 'category', }, { - name: i18n.translate('xpack.apm.csm.breakdownFilter.os', { + name: i18n.translate('xpack.ux.breakdownFilter.os', { defaultMessage: 'OS', }), fieldName: USER_AGENT_OS, type: 'category', }, { - name: i18n.translate('xpack.apm.csm.breakdownFilter.device', { + name: i18n.translate('xpack.ux.breakdownFilter.device', { defaultMessage: 'Device', }), fieldName: USER_AGENT_DEVICE, type: 'category', }, { - name: i18n.translate('xpack.apm.csm.breakdownFilter.location', { + name: i18n.translate('xpack.ux.breakdownFilter.location', { defaultMessage: 'Location', }), fieldName: CLIENT_GEO_COUNTRY_ISO_CODE, diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/chart_wrapper/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/chart_wrapper/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/chart_wrapper/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/chart_wrapper/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/charts/page_load_dist_chart.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/charts/page_load_dist_chart.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/charts/page_load_dist_chart.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/charts/page_load_dist_chart.tsx index ff9fb97197db0..e04a23fd8ddbf 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/charts/page_load_dist_chart.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/charts/page_load_dist_chart.tsx @@ -32,9 +32,9 @@ import { PercentileAnnotations } from '../page_load_distribution/percentile_anno import { I18LABELS } from '../translations'; import { ChartWrapper } from '../chart_wrapper'; import { PercentileRange } from '../page_load_distribution'; -import { BreakdownItem } from '../../../../../typings/ui_filters'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; import { BreakdownSeries } from '../page_load_distribution/breakdown_series'; +import { BreakdownItem } from '../../../../../typings/ui_filters'; interface PageLoadData { pageLoadDistribution: Array<{ x: number; y: number }>; diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/charts/page_views_chart.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/charts/page_views_chart.tsx similarity index 97% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/charts/page_views_chart.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/charts/page_views_chart.tsx index 059feb0915fdb..79e60a2117540 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/charts/page_views_chart.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/charts/page_views_chart.tsx @@ -29,9 +29,9 @@ import React from 'react'; import { useHistory } from 'react-router-dom'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { ChartWrapper } from '../chart_wrapper'; import { I18LABELS } from '../translations'; +import { fromQuery, toQuery } from '../../../../../../observability/public'; interface Props { data?: { diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/charts/visitor_breakdown_chart.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/charts/visitor_breakdown_chart.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/charts/visitor_breakdown_chart.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/charts/visitor_breakdown_chart.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/client_metrics/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/client_metrics/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/client_metrics/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/client_metrics/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/client_metrics/metrics.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/client_metrics/metrics.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/client_metrics/metrics.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/client_metrics/metrics.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/csm_shared_context/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/csm_shared_context/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/csm_shared_context/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/csm_shared_context/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/empty_state_loading.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/empty_state_loading.tsx similarity index 91% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/empty_state_loading.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/empty_state_loading.tsx index b02672721ce8e..007cd7b97be02 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/empty_state_loading.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/empty_state_loading.tsx @@ -23,7 +23,7 @@ export function EmptyStateLoading() {

- {i18n.translate('xpack.apm.emptyState.loadingMessage', { + {i18n.translate('xpack.ux.emptyState.loadingMessage', { defaultMessage: 'Loading…', })}

diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx new file mode 100644 index 0000000000000..d896dfd6428d7 --- /dev/null +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/environment_filter/index.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { History } from 'history'; +import React from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; +import { fromQuery, toQuery } from '../../../../../../observability/public'; +import { useEnvironmentsFetcher } from '../../../../hooks/use_environments_fetcher'; +import { + ENVIRONMENT_ALL, + ENVIRONMENT_NOT_DEFINED, +} from '../../../../../common/environment_filter_values'; +import { useUxUrlParams } from '../../../../context/url_params_context/use_ux_url_params'; + +function updateEnvironmentUrl( + history: History, + location: ReturnType, + environment: string +) { + history.push({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + environment, + }), + }); +} + +const SEPARATOR_OPTION = { + text: `- ${i18n.translate( + 'xpack.ux.filter.environment.selectEnvironmentLabel', + { defaultMessage: 'Select environment' } + )} -`, + disabled: true, +}; + +function getOptions(environments: string[]) { + const environmentOptions = environments + .filter((env) => env !== ENVIRONMENT_NOT_DEFINED.value) + .map((environment) => ({ + value: environment, + text: environment, + })); + + return [ + ENVIRONMENT_ALL, + ...(environments.includes(ENVIRONMENT_NOT_DEFINED.value) + ? [ENVIRONMENT_NOT_DEFINED] + : []), + ...(environmentOptions.length > 0 ? [SEPARATOR_OPTION] : []), + ...environmentOptions, + ]; +} + +export interface EnvironmentFilterProps { + start?: string; + end?: string; + environment?: string; + serviceName?: string; +} + +export function EnvironmentFilter({ + start, + end, + environment, + serviceName, +}: EnvironmentFilterProps) { + const history = useHistory(); + const location = useLocation(); + const { environments, status = 'loading' } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); + + // Set the min-width so we don't see as much collapsing of the select during + // the loading state. 200px is what is looks like if "production" is + // the contents. + const minWidth = 200; + + const options = getOptions(environments); + + return ( + { + updateEnvironmentUrl(history, location, event.target.value); + }} + isLoading={status === 'loading'} + style={{ minWidth }} + /> + ); +} + +export function UxEnvironmentFilter() { + const { + urlParams: { start, end, environment, serviceName }, + } = useUxUrlParams(); + + return ( + + ); +} diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts similarity index 96% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts index 4d48135e9edb3..f76de5e82586f 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_has_rum_data.ts @@ -9,6 +9,6 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; export function useHasRumData() { return useFetcher((callApmApi) => { - return callApmApi('GET /api/apm/observability_overview/has_rum_data'); + return callApmApi('GET /api/apm/observability_overview/has_rum_data', {}); }, []); } diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_local_ui_filters.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_local_uifilters.ts similarity index 94% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_local_ui_filters.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_local_uifilters.ts index 5c0bc7fdfd462..c228f9acb77c8 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_local_ui_filters.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_local_uifilters.ts @@ -7,18 +7,16 @@ import { omit } from 'lodash'; import { useHistory } from 'react-router-dom'; +import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { getExcludedName } from '../local_uifilters'; + +import { fromQuery, toQuery } from '../../../../../../observability/public'; +import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; import { uxFiltersByName, UxLocalUIFilter, UxLocalUIFilterName, } from '../../../../../common/ux_ui_filter'; -import { - fromQuery, - toQuery, -} from '../../../../components/shared/links/url_helpers'; -import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; -import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { getExcludedName } from '../local_ui_filters'; export type FiltersUIHook = ReturnType; diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_ux_query.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_ux_query.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/hooks/use_ux_query.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/hooks/use_ux_query.ts diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/impactful_metrics/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/impactful_metrics/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx similarity index 90% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx index 0c6e6846b2feb..c8dada5dce40b 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/impactful_metrics/js_errors.tsx @@ -14,15 +14,16 @@ import { EuiTitle, EuiStat, EuiToolTip, + EuiLink, } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { CsmSharedContext } from '../csm_shared_context'; -import { ErrorDetailLink } from '../../../shared/links/apm/error_detail_link'; +import { FETCH_STATUS } from '../../../../../../observability/public'; interface JSErrorItem { errorMessage: string; @@ -67,12 +68,9 @@ export function JSErrors() { field: 'errorMessage', name: I18LABELS.errorMessage, render: (errorMessage: string, item: JSErrorItem) => ( - + {errorMessage} - + ), }, { @@ -81,7 +79,7 @@ export function JSErrors() { align: 'right' as const, render: (count: number) => (

- {i18n.translate('xpack.apm.localFiltersTitle', { + {i18n.translate('xpack.ux.localFiltersTitle', { defaultMessage: 'Filters', })}

diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/queries.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/queries.ts similarity index 99% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/queries.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/queries.ts index b20a122b35a7f..7c3f49ca9a869 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/queries.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/queries.ts @@ -6,6 +6,7 @@ */ import { ESFilter } from 'src/core/types/elasticsearch'; + import { SERVICE_ENVIRONMENT } from '../../../../../common/elasticsearch_fieldnames'; import { ENVIRONMENT_ALL, diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/selected_filters.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/selected_filters.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx index 787bcd88266e2..5bc2dff5924e6 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/selected_filters.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_filters.tsx @@ -11,10 +11,10 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FilterValueLabel } from '../../../../../../observability/public'; -import { FiltersUIHook } from '../hooks/use_local_ui_filters'; -import { UxLocalUIFilterName } from '../../../../../common/ux_ui_filter'; +import { FiltersUIHook } from '../hooks/use_local_uifilters'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { SelectedWildcards } from './selected_wildcards'; +import { UxLocalUIFilterName } from '../../../../../common/ux_ui_filter'; interface Props { indexPattern?: IndexPattern; @@ -84,7 +84,7 @@ export function SelectedFilters({ onClick={clearValues} data-cy="clearFilters" > - {i18n.translate('xpack.apm.clearFilters', { + {i18n.translate('xpack.ux.clearFilters', { defaultMessage: 'Clear filters', })} diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/selected_wildcards.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_wildcards.tsx similarity index 91% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/selected_wildcards.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_wildcards.tsx index d18381cee77af..8a104b1ae9bd0 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/selected_wildcards.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/selected_wildcards.tsx @@ -8,9 +8,12 @@ import * as React from 'react'; import { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { FilterValueLabel } from '../../../../../../observability/public'; +import { + FilterValueLabel, + fromQuery, + toQuery, +} from '../../../../../../observability/public'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { TRANSACTION_URL } from '../../../../../common/elasticsearch_fieldnames'; import { IndexPattern } from '../../../../../../../../src/plugins/data_views/common'; diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/use_data_view.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/use_data_view.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts index 40d0017d8d096..959da46da673e 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/local_ui_filters/use_data_view.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/local_uifilters/use_data_view.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { useDynamicDataViewFetcher } from '../../../../hooks/use_dynamic_data_view'; import { DataView } from '../../../../../../../../src/plugins/data/common'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { DataPublicPluginStart } from '../../../../../../../../src/plugins/data/public'; +import { useDynamicDataViewFetcher } from '../../../../hooks/use_dynamic_data_view'; export function useDataView() { const { dataView } = useDynamicDataViewFetcher(); diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/breakdown_series.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/breakdown_series.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/breakdown_series.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/breakdown_series.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx index 2df4c9de54f52..ef32ad53b3ccf 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/index.tsx @@ -19,10 +19,10 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../breakdowns/breakdown_filter'; import { PageLoadDistChart } from '../charts/page_load_dist_chart'; -import { BreakdownItem } from '../../../../../typings/ui_filters'; import { ResetPercentileZoom } from './reset_percentile_zoom'; import { createExploratoryViewUrl } from '../../../../../../observability/public'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { useKibanaServices } from '../../../../hooks/use_kibana_services'; +import { BreakdownItem } from '../../../../../typings/ui_filters'; export interface PercentileRange { min?: number | null; @@ -30,9 +30,7 @@ export interface PercentileRange { } export function PageLoadDistribution() { - const { - services: { http }, - } = useKibana(); + const { http } = useKibanaServices(); const { urlParams, uxUiFilters } = useLegacyUrlParams(); @@ -99,7 +97,7 @@ export function PageLoadDistribution() { }, ], }, - http?.basePath.get() + http.basePath.get() ); const showAnalyzeButton = false; @@ -131,7 +129,7 @@ export function PageLoadDistribution() { href={exploratoryViewLink} > diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/percentile_annotations.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/percentile_annotations.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/percentile_annotations.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/percentile_annotations.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/reset_percentile_zoom.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/use_breakdowns.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/use_breakdowns.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/page_load_distribution/use_breakdowns.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/page_load_distribution/use_breakdowns.ts diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_views_trend/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx similarity index 93% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/page_views_trend/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx index 24d5b3a5ab0d6..48cf17089edcb 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/page_views_trend/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/page_views_trend/index.tsx @@ -19,14 +19,12 @@ import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../breakdowns/breakdown_filter'; import { PageViewsChart } from '../charts/page_views_chart'; -import { BreakdownItem } from '../../../../../typings/ui_filters'; import { createExploratoryViewUrl } from '../../../../../../observability/public'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import { useKibanaServices } from '../../../../hooks/use_kibana_services'; +import { BreakdownItem } from '../../../../../typings/ui_filters'; export function PageViewsTrend() { - const { - services: { http }, - } = useKibana(); + const { http } = useKibanaServices(); const { urlParams, uxUiFilters } = useLegacyUrlParams(); const { serviceName } = uxUiFilters; @@ -74,7 +72,7 @@ export function PageViewsTrend() { }, ], }, - http?.basePath.get() + http.basePath.get() ); const showAnalyzeButton = false; @@ -102,7 +100,7 @@ export function PageViewsTrend() { href={exploratoryViewLink} > diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/panels/page_load_and_views.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/page_load_and_views.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/panels/page_load_and_views.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/panels/page_load_and_views.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/panels/visitor_breakdowns.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/visitor_breakdowns.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/panels/visitor_breakdowns.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/panels/visitor_breakdowns.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/panels/web_application_select.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/panels/web_application_select.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx index 64d9c3da7bdd8..e7d86db0557a5 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/panels/web_application_select.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/panels/web_application_select.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { ServiceNameFilter } from '../url_filter/service_name_filter'; import { useFetcher } from '../../../../hooks/use_fetcher'; -import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; export function WebApplicationSelect() { const { diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_dashboard.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_dashboard.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/rum_dashboard.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/rum_dashboard.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_datepicker/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_datepicker/index.tsx similarity index 86% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/rum_datepicker/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/rum_datepicker/index.tsx index 4e39ad4397b41..275a154c30856 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_datepicker/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_datepicker/index.tsx @@ -7,7 +7,8 @@ import React from 'react'; import { useUxUrlParams } from '../../../../context/url_params_context/use_ux_url_params'; import { useDateRangeRedirect } from '../../../../hooks/use_date_range_redirect'; -import { DatePicker } from '../../../shared/date_picker'; +import { DatePicker } from '../../../../../../observability/public'; +import { clearCache } from '../../../../services/rest/call_api'; export function RumDatePicker() { const { @@ -28,6 +29,7 @@ export function RumDatePicker() { refreshPaused={refreshPaused} refreshInterval={refreshInterval} onTimeRangeRefresh={({ start, end }) => { + clearCache(); refreshTimeRange({ rangeFrom: start, rangeTo: end }); }} /> diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_home.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx similarity index 78% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/rum_home.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx index bb0427d462d54..d3cbeef96078d 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/rum_home.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/rum_home.tsx @@ -8,24 +8,25 @@ import React, { Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiTitle, EuiFlexItem } from '@elastic/eui'; -import { RumOverview } from '../rum_dashboard'; +import { RumDashboard } from './rum_dashboard'; import { CsmSharedContextProvider } from './csm_shared_context'; import { WebApplicationSelect } from './panels/web_application_select'; -import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; -import { UxEnvironmentFilter } from '../../shared/environment_filter'; import { UserPercentile } from './user_percentile'; import { useBreakpoints } from '../../../hooks/use_breakpoints'; import { KibanaPageTemplateProps } from '../../../../../../../src/plugins/kibana_react/public'; import { useHasRumData } from './hooks/use_has_rum_data'; import { RumDatePicker } from './rum_datepicker'; import { EmptyStateLoading } from './empty_state_loading'; +import { useKibanaServices } from '../../../hooks/use_kibana_services'; +import { UxEnvironmentFilter } from './environment_filter'; -export const DASHBOARD_LABEL = i18n.translate('xpack.apm.ux.title', { +export const DASHBOARD_LABEL = i18n.translate('xpack.ux.title', { defaultMessage: 'Dashboard', }); export function RumHome() { - const { core, observability } = useApmPluginContext(); + const { docLinks, http, observability } = useKibanaServices(); + const PageTemplateComponent = observability.navigation.PageTemplate; const { data: rumHasData, status } = useHasRumData(); @@ -33,25 +34,25 @@ export function RumHome() { const noDataConfig: KibanaPageTemplateProps['noDataConfig'] = !rumHasData?.hasData ? { - solution: i18n.translate('xpack.apm.ux.overview.solutionName', { + solution: i18n.translate('xpack.ux.overview.solutionName', { defaultMessage: 'Observability', }), actions: { elasticAgent: { - title: i18n.translate('xpack.apm.ux.overview.beatsCard.title', { - defaultMessage: 'Add the APM integration', + title: i18n.translate('xpack.ux.overview.beatsCard.title', { + defaultMessage: 'Add RUM data', }), description: i18n.translate( - 'xpack.apm.ux.overview.beatsCard.description', + 'xpack.ux.overview.beatsCard.description', { defaultMessage: 'Enable RUM with the APM agent to collect user experience data.', } ), - href: core.http.basePath.prepend(`/app/home#/tutorial/apm`), + href: http.basePath.prepend(`/app/home#/tutorial/apm`), }, }, - docsLink: core.docLinks.links.observability.guide, + docsLink: docLinks.links.observability.guide, } : undefined; @@ -66,7 +67,7 @@ export function RumHome() { > {isLoading && }
- +
diff --git a/x-pack/plugins/ux/public/components/app/rum_dashboard/translations.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/translations.ts new file mode 100644 index 0000000000000..c7b585302e6ee --- /dev/null +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/translations.ts @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export const I18LABELS = { + dataMissing: i18n.translate('xpack.ux.dashboard.dataMissing', { + defaultMessage: 'N/A', + }), + totalPageLoad: i18n.translate('xpack.ux.dashboard.totalPageLoad', { + defaultMessage: 'Total', + }), + backEnd: i18n.translate('xpack.ux.dashboard.backend', { + defaultMessage: 'Backend', + }), + frontEnd: i18n.translate('xpack.ux.dashboard.frontend', { + defaultMessage: 'Frontend', + }), + pageViews: i18n.translate('xpack.ux.dashboard.pageViews', { + defaultMessage: 'Total page views', + }), + percPageLoaded: i18n.translate('xpack.ux.dashboard.pagesLoaded.label', { + defaultMessage: 'Pages loaded', + }), + pageLoadTime: i18n.translate('xpack.ux.dashboard.pageLoadTime.label', { + defaultMessage: 'Page load time (seconds)', + }), + pageLoadTimes: i18n.translate('xpack.ux.dashboard.pageLoadTimes.label', { + defaultMessage: 'Page load times', + }), + pageLoadDuration: i18n.translate( + 'xpack.ux.dashboard.pageLoadDuration.label', + { + defaultMessage: 'Page load duration', + } + ), + pageLoad: i18n.translate('xpack.ux.dashboard.pageLoad.label', { + defaultMessage: 'Page load', + }), + pageLoadDistribution: i18n.translate( + 'xpack.ux.dashboard.pageLoadDistribution.label', + { + defaultMessage: 'Page load distribution', + } + ), + jsErrors: i18n.translate('xpack.ux.dashboard.impactfulMetrics.jsErrors', { + defaultMessage: 'JavaScript errors', + }), + highTrafficPages: i18n.translate( + 'xpack.ux.dashboard.impactfulMetrics.highTrafficPages', + { + defaultMessage: 'High traffic pages', + } + ), + resetZoom: i18n.translate('xpack.ux.dashboard.resetZoom.label', { + defaultMessage: 'Reset zoom', + }), + overall: i18n.translate('xpack.ux.dashboard.overall.label', { + defaultMessage: 'Overall', + }), + selectBreakdown: i18n.translate('xpack.ux.filterGroup.selectBreakdown', { + defaultMessage: 'Select breakdown', + }), + breakdown: i18n.translate('xpack.ux.filterGroup.breakdown', { + defaultMessage: 'Breakdown', + }), + seconds: i18n.translate('xpack.ux.filterGroup.seconds', { + defaultMessage: 'seconds', + }), + coreWebVitals: i18n.translate('xpack.ux.filterGroup.coreWebVitals', { + defaultMessage: 'Core web vitals', + }), + browser: i18n.translate('xpack.ux.visitorBreakdown.browser', { + defaultMessage: 'Browser', + }), + operatingSystem: i18n.translate('xpack.ux.visitorBreakdown.operatingSystem', { + defaultMessage: 'Operating system', + }), + metrics: i18n.translate('xpack.ux.metrics', { + defaultMessage: 'Metrics', + }), + median: i18n.translate('xpack.ux.median', { + defaultMessage: 'median', + }), + avgPageLoadDuration: i18n.translate( + 'xpack.ux.visitorBreakdownMap.avgPageLoadDuration', + { + defaultMessage: 'Average page load duration', + } + ), + pageLoadDurationByRegion: i18n.translate( + 'xpack.ux.visitorBreakdownMap.pageLoadDurationByRegion', + { + defaultMessage: 'Page load duration by region (avg.)', + } + ), + filterByUrl: i18n.translate('xpack.ux.filters.filterByUrl', { + defaultMessage: 'Filter by URL', + }), + getSearchResultsLabel: (total: number) => + i18n.translate('xpack.ux.filters.searchResults', { + defaultMessage: '{total} Search results', + values: { total }, + }), + topPages: i18n.translate('xpack.ux.filters.topPages', { + defaultMessage: 'Top pages', + }), + select: i18n.translate('xpack.ux.filters.select', { + defaultMessage: 'Select', + }), + url: i18n.translate('xpack.ux.filters.url', { + defaultMessage: 'Url', + }), + loadingResults: i18n.translate('xpack.ux.filters.url.loadingResults', { + defaultMessage: 'Loading results', + }), + noResults: i18n.translate('xpack.ux.filters.url.noResults', { + defaultMessage: 'No results available', + }), + totalErrors: i18n.translate('xpack.ux.jsErrors.totalErrors', { + defaultMessage: 'Total errors', + }), + errorRate: i18n.translate('xpack.ux.jsErrors.errorRate', { + defaultMessage: 'Error rate', + }), + errorMessage: i18n.translate('xpack.ux.jsErrors.errorMessage', { + defaultMessage: 'Error message', + }), + impactedPageLoads: i18n.translate('xpack.ux.jsErrors.impactedPageLoads', { + defaultMessage: 'Impacted page loads', + }), + percentile: i18n.translate('xpack.ux.percentile.label', { + defaultMessage: 'Percentile', + }), + percentile50thMedian: i18n.translate('xpack.ux.percentile.50thMedian', { + defaultMessage: '50th (Median)', + }), + percentile75th: i18n.translate('xpack.ux.percentile.75th', { + defaultMessage: '75th', + }), + percentile90th: i18n.translate('xpack.ux.percentile.90th', { + defaultMessage: '90th', + }), + percentile95th: i18n.translate('xpack.ux.percentile.95th', { + defaultMessage: '95th', + }), + percentile99th: i18n.translate('xpack.ux.percentile.99th', { + defaultMessage: '99th', + }), + noData: i18n.translate('xpack.ux.visitorBreakdown.noData', { + defaultMessage: 'No data.', + }), + // Helper tooltips + totalPageLoadTooltip: i18n.translate( + 'xpack.ux.dashboard.tooltips.totalPageLoad', + { + defaultMessage: 'Total represents the full page load duration', + } + ), + frontEndTooltip: i18n.translate('xpack.ux.dashboard.tooltips.frontEnd', { + defaultMessage: + 'Frontend time represents the total page load duration minus the backend time', + }), + backEndTooltip: i18n.translate('xpack.ux.dashboard.tooltips.backEnd', { + defaultMessage: + 'Backend time represents time to first byte (TTFB), which is when the first response packet is received after the request has been made', + }), +}; + +export const VisitorBreakdownLabel = i18n.translate( + 'xpack.ux.visitorBreakdown', + { + defaultMessage: 'Visitor breakdown', + } +); diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/index.tsx similarity index 95% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/index.tsx index 558092db4a458..3d5c9ddb3b3a2 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/index.tsx @@ -9,7 +9,7 @@ import React, { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { omit } from 'lodash'; import { URLSearch } from './url_search'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; +import { fromQuery, toQuery } from '../../../../../../observability/public'; import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; export function URLFilter() { diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx similarity index 82% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx index b3560f0ebc97b..45b4c8ab8b660 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/service_name_filter/index.tsx @@ -10,10 +10,10 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { useLegacyUrlParams } from '../../../../../context/url_params_context/use_url_params'; -import { fromQuery, toQuery } from '../../../../shared/links/url_helpers'; +import { fromQuery, toQuery } from '../../../../../../../observability/public'; interface Props { - serviceNames: string[]; + serviceNames?: string[]; loading: boolean; } @@ -23,7 +23,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) { urlParams: { serviceName: selectedServiceName }, } = useLegacyUrlParams(); - const options = serviceNames.map((type) => ({ + const options = (serviceNames ?? []).map((type) => ({ text: type, value: type, })); @@ -47,7 +47,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) { ); useEffect(() => { - if (serviceNames?.length > 0) { + if (serviceNames && serviceNames?.length > 0) { // select first from the list if (!selectedServiceName) { updateServiceName(serviceNames[0], true); @@ -59,7 +59,7 @@ function ServiceNameFilter({ loading, serviceNames }: Props) { } } - if (selectedServiceName && serviceNames.length === 0 && !loading) { + if (selectedServiceName && serviceNames?.length === 0 && !loading) { updateServiceName(''); } }, [serviceNames, selectedServiceName, updateServiceName, loading]); @@ -67,12 +67,9 @@ function ServiceNameFilter({ loading, serviceNames }: Props) { return ( { }; const getWildcardLabel = (wildcard: string) => { - return i18n.translate('xpack.apm.urlFilter.wildcard', { + return i18n.translate('xpack.ux.urlFilter.wildcard', { defaultMessage: 'Use wildcard *{wildcard}*', values: { wildcard }, }); diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/url_search/render_option.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/url_search/render_option.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/url_search/render_option.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/url_search/render_option.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/url_search/use_url_search.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/url_search/use_url_search.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/url_filter/url_search/use_url_search.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/url_filter/url_search/use_url_search.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/user_percentile/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/user_percentile/index.tsx similarity index 96% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/user_percentile/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/user_percentile/index.tsx index a1e70f4f25d21..bac7b818fc03d 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/user_percentile/index.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/user_percentile/index.tsx @@ -10,8 +10,8 @@ import React, { useCallback, useEffect } from 'react'; import { EuiSelect } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; import { I18LABELS } from '../translations'; +import { fromQuery, toQuery } from '../../../../../../observability/public'; const DEFAULT_P = 50; diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/utils/test_helper.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/utils/test_helper.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/utils/test_helper.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/utils/test_helper.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/format_to_sec.test.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/format_to_sec.test.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/format_to_sec.test.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/format_to_sec.test.ts diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.test.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.test.tsx similarity index 82% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.test.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.test.tsx index 2f92e5efedf42..2b899aad783d7 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.test.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.test.tsx @@ -6,9 +6,10 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { render, Matcher } from '@testing-library/react'; import * as fetcherHook from '../../../../hooks/use_fetcher'; import { KeyUXMetrics } from './key_ux_metrics'; +import { FETCH_STATUS } from '../../../../../../observability/public'; describe('KeyUXMetrics', () => { it('renders metrics with correct formats', () => { @@ -18,7 +19,7 @@ describe('KeyUXMetrics', () => { sumOfLongTasks: 520.4375, longestLongTask: 271.4375, }, - status: fetcherHook.FETCH_STATUS.SUCCESS, + status: FETCH_STATUS.SUCCESS, refetch: jest.fn(), }); const { getAllByText } = render( @@ -38,9 +39,9 @@ describe('KeyUXMetrics', () => { /> ); - const checkText = (text: string) => { - return (content: any, node: any) => { - return node?.textContent?.includes(text); + const checkText = (text: string): Matcher => { + return (content: string, element?: Element | null) => { + return Boolean(element?.textContent?.includes(text)); }; }; diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/key_ux_metrics.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/translations.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/translations.ts similarity index 64% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/translations.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/translations.ts index 323c02e3620bc..d323263519c36 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_metrics/translations.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_metrics/translations.ts @@ -9,45 +9,39 @@ import { i18n } from '@kbn/i18n'; import { I18LABELS } from '../translations'; export const DATA_UNDEFINED_LABEL = i18n.translate( - 'xpack.apm.rum.coreVitals.dataUndefined', + 'xpack.ux.coreVitals.dataUndefined', { defaultMessage: 'N/A', } ); -export const FCP_LABEL = i18n.translate('xpack.apm.rum.coreVitals.fcp', { +export const FCP_LABEL = i18n.translate('xpack.ux.coreVitals.fcp', { defaultMessage: 'First contentful paint', }); -export const FCP_TOOLTIP = i18n.translate( - 'xpack.apm.rum.coreVitals.fcpTooltip', - { - defaultMessage: - 'First contentful paint (FCP) focusses on the initial rendering and measures the time from when the page starts loading to when any part of the page’s content is displayed on the screen.', - } -); +export const FCP_TOOLTIP = i18n.translate('xpack.ux.coreVitals.fcpTooltip', { + defaultMessage: + 'First contentful paint (FCP) focusses on the initial rendering and measures the time from when the page starts loading to when any part of the page’s content is displayed on the screen.', +}); -export const TBT_LABEL = i18n.translate('xpack.apm.rum.coreVitals.tbt', { +export const TBT_LABEL = i18n.translate('xpack.ux.coreVitals.tbt', { defaultMessage: 'Total blocking time', }); -export const TBT_TOOLTIP = i18n.translate( - 'xpack.apm.rum.coreVitals.tbtTooltip', - { - defaultMessage: - 'Total blocking time (TBT) is the sum of the blocking time (duration above 50 ms) for each long task that occurs between the First contentful paint and the time when the transaction is completed.', - } -); +export const TBT_TOOLTIP = i18n.translate('xpack.ux.coreVitals.tbtTooltip', { + defaultMessage: + 'Total blocking time (TBT) is the sum of the blocking time (duration above 50 ms) for each long task that occurs between the First contentful paint and the time when the transaction is completed.', +}); export const NO_OF_LONG_TASK = i18n.translate( - 'xpack.apm.rum.uxMetrics.noOfLongTasks', + 'xpack.ux.uxMetrics.noOfLongTasks', { defaultMessage: 'No. of long tasks', } ); export const NO_OF_LONG_TASK_TOOLTIP = i18n.translate( - 'xpack.apm.rum.uxMetrics.noOfLongTasksTooltip', + 'xpack.ux.uxMetrics.noOfLongTasksTooltip', { defaultMessage: 'The number of long tasks, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.', @@ -55,14 +49,14 @@ export const NO_OF_LONG_TASK_TOOLTIP = i18n.translate( ); export const LONGEST_LONG_TASK = i18n.translate( - 'xpack.apm.rum.uxMetrics.longestLongTasks', + 'xpack.ux.uxMetrics.longestLongTasks', { defaultMessage: 'Longest long task duration', } ); export const LONGEST_LONG_TASK_TOOLTIP = i18n.translate( - 'xpack.apm.rum.uxMetrics.longestLongTasksTooltip', + 'xpack.ux.uxMetrics.longestLongTasksTooltip', { defaultMessage: 'The duration of the longest long task, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.', @@ -70,14 +64,14 @@ export const LONGEST_LONG_TASK_TOOLTIP = i18n.translate( ); export const SUM_LONG_TASKS = i18n.translate( - 'xpack.apm.rum.uxMetrics.sumLongTasks', + 'xpack.ux.uxMetrics.sumLongTasks', { defaultMessage: 'Total long tasks duration', } ); export const SUM_LONG_TASKS_TOOLTIP = i18n.translate( - 'xpack.apm.rum.uxMetrics.sumLongTasksTooltip', + 'xpack.ux.uxMetrics.sumLongTasksTooltip', { defaultMessage: 'The total duration of long tasks, a long task is defined as any user activity or browser task that monopolizes the UI thread for extended periods (greater than 50 milliseconds) and blocks other critical tasks (frame rate or input latency) from being executed.', @@ -87,7 +81,7 @@ export const SUM_LONG_TASKS_TOOLTIP = i18n.translate( export const getPercentileLabel = (value: number) => { if (value === 50) return I18LABELS.median; - return i18n.translate('xpack.apm.ux.percentiles.label', { + return i18n.translate('xpack.ux.percentiles.label', { defaultMessage: '{value}th Perc.', values: { value, diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/ux_overview_fetchers.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/ux_overview_fetchers.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/ux_overview_fetchers.ts diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__mocks__/regions_layer.mock.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__mocks__/regions_layer.mock.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__mocks__/regions_layer.mock.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__mocks__/regions_layer.mock.ts diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/embedded_map.test.tsx.snap b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/embedded_map.test.tsx.snap similarity index 93% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/embedded_map.test.tsx.snap rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/embedded_map.test.tsx.snap index 67f79c9fc747e..70929d04d0548 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/embedded_map.test.tsx.snap +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/embedded_map.test.tsx.snap @@ -39,7 +39,7 @@ exports[`Embedded Map it renders 1`] = ` >
`; diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/map_tooltip.test.tsx.snap b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/map_tooltip.test.tsx.snap similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/map_tooltip.test.tsx.snap rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__snapshots__/map_tooltip.test.tsx.snap diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__stories__/map_tooltip.stories.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__stories__/map_tooltip.stories.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/__stories__/map_tooltip.stories.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/__stories__/map_tooltip.stories.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.test.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.test.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx similarity index 85% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx index 32dcf3d5d3439..55845c44e5e27 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/embedded_map.tsx @@ -14,7 +14,6 @@ import { MapEmbeddableInput, } from '../../../../../../maps/public'; import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { ErrorEmbeddable, ViewMode, @@ -22,11 +21,10 @@ import { } from '../../../../../../../../src/plugins/embeddable/public'; import { useLayerList } from './use_layer_list'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import type { RenderTooltipContentParams } from '../../../../../../maps/public'; import { MapToolTip } from './map_tooltip'; import { useMapFilters } from './use_map_filters'; -import { EmbeddableStart } from '../../../../../../../../src/plugins/embeddable/public'; +import { useKibanaServices } from '../../../../hooks/use_kibana_services'; const EmbeddedPanel = styled.div` z-index: auto; @@ -46,12 +44,8 @@ const EmbeddedPanel = styled.div` } `; -interface KibanaDeps { - embeddable: EmbeddableStart; -} export function EmbeddedMapComponent() { const { urlParams } = useLegacyUrlParams(); - const apmPluginContext = useApmPluginContext(); const { start, end, serviceName } = urlParams; @@ -66,16 +60,12 @@ export function EmbeddedMapComponent() { const embeddableRoot: React.RefObject = useRef(null); - const { - services: { embeddable: embeddablePlugin }, - } = useKibana(); + const { embeddable: embeddablePlugin, maps } = useKibanaServices(); if (!embeddablePlugin) { throw new Error('Embeddable start plugin not found'); } - const factory: any = embeddablePlugin.getEmbeddableFactory( - MAP_SAVED_OBJECT_TYPE - ); + const factory = embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE); const input: MapEmbeddableInput = { attributes: { title: '' }, @@ -148,8 +138,8 @@ export function EmbeddedMapComponent() { if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { embeddableObject.setRenderTooltipContent(renderTooltipContent); - const basemapLayerDescriptor = apmPluginContext.plugins.maps - ? await apmPluginContext.plugins.maps.createLayerDescriptors.createBasemapLayerDescriptor() + const basemapLayerDescriptor = maps + ? await maps.createLayerDescriptors.createBasemapLayerDescriptor() : null; if (basemapLayerDescriptor) { layerList.unshift(basemapLayerDescriptor); @@ -176,7 +166,7 @@ export function EmbeddedMapComponent() { return (
diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/index.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/index.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/index.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/index.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.test.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.test.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.test.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.tsx b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.tsx rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/map_tooltip.tsx diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.test.ts diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.ts similarity index 100% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.ts index f573a2641b864..bdef5c0a35782 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_layer_list.ts @@ -21,13 +21,13 @@ import { SYMBOLIZE_AS_TYPES, } from '../../../../../../maps/common'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME, TRANSACTION_TYPE, } from '../../../../../common/elasticsearch_fieldnames'; import { TRANSACTION_PAGE_LOAD } from '../../../../../common/transaction_types'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; const ES_TERM_SOURCE_COUNTRY: ESTermSourceDescriptor = { type: SOURCE_TYPES.ES_TERM_SOURCE, diff --git a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_map_filters.ts b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_map_filters.ts similarity index 99% rename from x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_map_filters.ts rename to x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_map_filters.ts index 0c1b7b6b9e7ee..6e07651f37567 100644 --- a/x-pack/plugins/apm/public/components/app/rum_dashboard/visitor_breakdown_map/use_map_filters.ts +++ b/x-pack/plugins/ux/public/components/app/rum_dashboard/visitor_breakdown_map/use_map_filters.ts @@ -8,6 +8,7 @@ import { useMemo } from 'react'; import { FieldFilter as Filter } from '@kbn/es-query'; import { useLegacyUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; import { CLIENT_GEO_COUNTRY_ISO_CODE, SERVICE_NAME, @@ -17,8 +18,6 @@ import { USER_AGENT_OS, } from '../../../../../common/elasticsearch_fieldnames'; -import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../common/index_pattern_constants'; - const getWildcardFilter = (field: string, value: string): Filter => { return { meta: { diff --git a/x-pack/plugins/ux/public/context/url_params_context/constants.ts b/x-pack/plugins/ux/public/context/url_params_context/constants.ts new file mode 100644 index 0000000000000..d791ae5f45f1c --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const TIME_RANGE_REFRESH = 'TIME_RANGE_REFRESH'; +export const LOCATION_UPDATE = 'LOCATION_UPDATE'; diff --git a/x-pack/plugins/ux/public/context/url_params_context/helpers.test.ts b/x-pack/plugins/ux/public/context/url_params_context/helpers.test.ts new file mode 100644 index 0000000000000..784b10b3f3ee1 --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/helpers.test.ts @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import datemath from '@elastic/datemath'; +import moment from 'moment-timezone'; +import * as helpers from './helpers'; + +describe('url_params_context helpers', () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + describe('getDateRange', () => { + describe('with non-rounded dates', () => { + describe('one minute', () => { + it('rounds the start value to minute', () => { + expect( + helpers.getDateRange({ + state: {}, + rangeFrom: '2021-01-28T05:47:52.134Z', + rangeTo: '2021-01-28T05:48:55.304Z', + }) + ).toEqual({ + start: '2021-01-28T05:47:00.000Z', + end: '2021-01-28T05:48:55.304Z', + exactStart: '2021-01-28T05:47:52.134Z', + exactEnd: '2021-01-28T05:48:55.304Z', + }); + }); + }); + describe('one day', () => { + it('rounds the start value to minute', () => { + expect( + helpers.getDateRange({ + state: {}, + rangeFrom: '2021-01-27T05:46:07.377Z', + rangeTo: '2021-01-28T05:46:13.367Z', + }) + ).toEqual({ + start: '2021-01-27T05:46:00.000Z', + end: '2021-01-28T05:46:13.367Z', + exactStart: '2021-01-27T05:46:07.377Z', + exactEnd: '2021-01-28T05:46:13.367Z', + }); + }); + }); + + describe('one year', () => { + it('rounds the start value to minute', () => { + expect( + helpers.getDateRange({ + state: {}, + rangeFrom: '2020-01-28T05:52:36.290Z', + rangeTo: '2021-01-28T05:52:39.741Z', + }) + ).toEqual({ + start: '2020-01-28T05:52:00.000Z', + end: '2021-01-28T05:52:39.741Z', + exactStart: '2020-01-28T05:52:36.290Z', + exactEnd: '2021-01-28T05:52:39.741Z', + }); + }); + }); + }); + + describe('when rangeFrom and rangeTo are not changed', () => { + it('returns the previous state', () => { + expect( + helpers.getDateRange({ + state: { + rangeFrom: 'now-1m', + rangeTo: 'now', + start: '1970-01-01T00:00:00.000Z', + end: '1971-01-01T00:00:00.000Z', + exactStart: '1970-01-01T00:00:00.000Z', + exactEnd: '1971-01-01T00:00:00.000Z', + }, + rangeFrom: 'now-1m', + rangeTo: 'now', + }) + ).toEqual({ + start: '1970-01-01T00:00:00.000Z', + end: '1971-01-01T00:00:00.000Z', + exactStart: '1970-01-01T00:00:00.000Z', + exactEnd: '1971-01-01T00:00:00.000Z', + }); + }); + }); + + describe('when rangeFrom or rangeTo are falsy', () => { + it('returns the previous state', () => { + // Disable console warning about not receiving a valid date for rangeFrom + jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); + + expect( + helpers.getDateRange({ + state: { + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: '', + rangeTo: 'now', + }) + ).toEqual({ + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + exactStart: undefined, + exactEnd: undefined, + }); + }); + }); + + describe('when the start or end are invalid', () => { + it('returns the previous state', () => { + const endDate = moment('2021-06-04T18:03:24.211Z'); + jest + .spyOn(datemath, 'parse') + .mockReturnValueOnce(undefined) + .mockReturnValueOnce(endDate) + .mockReturnValueOnce(undefined) + .mockReturnValueOnce(endDate); + expect( + helpers.getDateRange({ + state: { + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + exactStart: '1972-01-01T00:00:00.000Z', + exactEnd: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: 'nope', + rangeTo: 'now', + }) + ).toEqual({ + start: '1972-01-01T00:00:00.000Z', + exactStart: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + exactEnd: '1973-01-01T00:00:00.000Z', + }); + }); + }); + + describe('when rangeFrom or rangeTo have changed', () => { + it('returns new state', () => { + jest.spyOn(datemath, 'parse').mockReturnValue(moment(0).utc()); + + expect( + helpers.getDateRange({ + state: { + rangeFrom: 'now-1m', + rangeTo: 'now', + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: 'now-2m', + rangeTo: 'now', + }) + ).toEqual({ + start: '1970-01-01T00:00:00.000Z', + end: '1970-01-01T00:00:00.000Z', + exactStart: '1970-01-01T00:00:00.000Z', + exactEnd: '1970-01-01T00:00:00.000Z', + }); + }); + }); + }); + + describe('getExactDate', () => { + it('returns date when it is not not relative', () => { + expect(helpers.getExactDate('2021-01-28T05:47:52.134Z')).toEqual( + new Date('2021-01-28T05:47:52.134Z') + ); + }); + + ['s', 'm', 'h', 'd', 'w'].map((roundingOption) => + it(`removes /${roundingOption} rounding option from relative time`, () => { + const spy = jest.spyOn(datemath, 'parse'); + helpers.getExactDate(`now/${roundingOption}`); + expect(spy).toHaveBeenCalledWith('now', {}); + }) + ); + + it('removes rounding option but keeps subtracting time', () => { + const spy = jest.spyOn(datemath, 'parse'); + helpers.getExactDate('now-24h/h'); + expect(spy).toHaveBeenCalledWith('now-24h', {}); + }); + + it('removes rounding option but keeps adding time', () => { + const spy = jest.spyOn(datemath, 'parse'); + helpers.getExactDate('now+15m/h'); + expect(spy).toHaveBeenCalledWith('now+15m', {}); + }); + }); +}); diff --git a/x-pack/plugins/ux/public/context/url_params_context/helpers.ts b/x-pack/plugins/ux/public/context/url_params_context/helpers.ts new file mode 100644 index 0000000000000..ee6ac43c1aeab --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/helpers.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import datemath from '@elastic/datemath'; +import { compact, pickBy } from 'lodash'; +import moment from 'moment'; +import { UrlParams } from './types'; + +function getParsedDate(rawDate?: string, options = {}) { + if (rawDate) { + const parsed = datemath.parse(rawDate, options); + if (parsed && parsed.isValid()) { + return parsed.toDate(); + } + } +} + +export function getExactDate(rawDate: string) { + const isRelativeDate = rawDate.startsWith('now'); + if (isRelativeDate) { + // remove rounding from relative dates "Today" (now/d) and "This week" (now/w) + const rawDateWithouRounding = rawDate.replace(/\/([smhdw])$/, ''); + return getParsedDate(rawDateWithouRounding); + } + return getParsedDate(rawDate); +} + +export function getDateRange({ + state = {}, + rangeFrom, + rangeTo, +}: { + state?: Pick< + UrlParams, + 'rangeFrom' | 'rangeTo' | 'start' | 'end' | 'exactStart' | 'exactEnd' + >; + rangeFrom?: string; + rangeTo?: string; +}) { + // If the previous state had the same range, just return that instead of calculating a new range. + if (state.rangeFrom === rangeFrom && state.rangeTo === rangeTo) { + return { + start: state.start, + end: state.end, + exactStart: state.exactStart, + exactEnd: state.exactEnd, + }; + } + const start = getParsedDate(rangeFrom); + const end = getParsedDate(rangeTo, { roundUp: true }); + + const exactStart = rangeFrom ? getExactDate(rangeFrom) : undefined; + const exactEnd = rangeTo ? getExactDate(rangeTo) : undefined; + + // `getParsedDate` will return undefined for invalid or empty dates. We return + // the previous state if either date is undefined. + if (!start || !end) { + return { + start: state.start, + end: state.end, + exactStart: state.exactStart, + exactEnd: state.exactEnd, + }; + } + + // rounds down start to minute + const roundedStart = moment(start).startOf('minute'); + + return { + start: roundedStart.toISOString(), + end: end.toISOString(), + exactStart: exactStart?.toISOString(), + exactEnd: exactEnd?.toISOString(), + }; +} + +export function toNumber(value?: string) { + if (value !== undefined) { + return parseInt(value, 10); + } +} + +export function toString(value?: string) { + if (value === '' || value === 'null' || value === 'undefined') { + return; + } + return value; +} + +export function toBoolean(value?: string) { + return value === 'true'; +} + +export function getPathAsArray(pathname: string = '') { + return compact(pathname.split('/')); +} + +export function removeUndefinedProps(obj: T): Partial { + return pickBy(obj, (value) => value !== undefined); +} diff --git a/x-pack/plugins/ux/public/context/url_params_context/mock_url_params_context_provider.tsx b/x-pack/plugins/ux/public/context/url_params_context/mock_url_params_context_provider.tsx new file mode 100644 index 0000000000000..75cf050fcb089 --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/mock_url_params_context_provider.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { UrlParams } from './types'; +import { UrlParamsContext } from './url_params_context'; + +const defaultUrlParams = { + page: 0, + serviceName: 'opbeans-python', + transactionType: 'request', + start: '2018-01-10T09:51:41.050Z', + end: '2018-01-10T10:06:41.050Z', +}; + +interface Props { + params?: UrlParams; + children: React.ReactNode; + refreshTimeRange?: (time: any) => void; +} + +export function MockUrlParamsContextProvider({ + params, + children, + refreshTimeRange = () => undefined, +}: Props) { + const urlParams = { ...defaultUrlParams, ...params }; + return ( + + ); +} diff --git a/x-pack/plugins/ux/public/context/url_params_context/resolve_url_params.ts b/x-pack/plugins/ux/public/context/url_params_context/resolve_url_params.ts new file mode 100644 index 0000000000000..94beab5c3728e --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/resolve_url_params.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Location } from 'history'; +import { uxLocalUIFilterNames } from '../../../common/ux_ui_filter'; +import { pickKeys } from '../../../common/utils/pick_keys'; +import { + getDateRange, + removeUndefinedProps, + toBoolean, + toNumber, + toString, +} from './helpers'; +import { UrlParams, UxUrlParams } from './types'; +import { toQuery } from '../../../../observability/public'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; + +type TimeUrlParams = Pick< + UrlParams, + 'start' | 'end' | 'rangeFrom' | 'rangeTo' | 'exactStart' | 'exactEnd' +>; + +export function resolveUrlParams(location: Location, state: TimeUrlParams) { + const query = toQuery(location.search) as UxUrlParams; + + const { + page, + pageSize, + sortDirection, + sortField, + refreshPaused, + refreshInterval, + rangeFrom, + rangeTo, + environment, + searchTerm, + percentile, + } = query; + + const localUIFilters = pickKeys(query, ...uxLocalUIFilterNames); + + return removeUndefinedProps({ + // date params + ...getDateRange({ state, rangeFrom, rangeTo }), + rangeFrom, + rangeTo, + refreshPaused: refreshPaused ? toBoolean(refreshPaused) : undefined, + refreshInterval: refreshInterval ? toNumber(refreshInterval) : undefined, + + // query params + environment: toString(environment) || ENVIRONMENT_ALL.value, + sortDirection, + sortField, + page: toNumber(page) || 0, + pageSize: pageSize ? toNumber(pageSize) : undefined, + searchTerm: toString(searchTerm), + percentile: toNumber(percentile), + + ...localUIFilters, + }); +} diff --git a/x-pack/plugins/ux/public/context/url_params_context/types.ts b/x-pack/plugins/ux/public/context/url_params_context/types.ts new file mode 100644 index 0000000000000..8188132c1b941 --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UxLocalUIFilterName } from '../../../common/ux_ui_filter'; + +export type UrlParams = { + end?: string; + environment?: string; + rangeFrom?: string; + rangeTo?: string; + refreshInterval?: number; + refreshPaused?: boolean; + sortDirection?: string; + sortField?: string; + start?: string; + page?: number; + pageSize?: number; + searchTerm?: string; + percentile?: number; + exactStart?: string; + exactEnd?: string; +} & Partial>; + +type StringifyAll = { [K in keyof T]: string }; +export type UxUrlParams = StringifyAll; diff --git a/x-pack/plugins/ux/public/context/url_params_context/url_params_context.test.tsx b/x-pack/plugins/ux/public/context/url_params_context/url_params_context.test.tsx new file mode 100644 index 0000000000000..be4b8f046ac76 --- /dev/null +++ b/x-pack/plugins/ux/public/context/url_params_context/url_params_context.test.tsx @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { waitFor } from '@testing-library/react'; +import { mount } from 'enzyme'; +import { History, Location } from 'history'; +import moment from 'moment-timezone'; +import * as React from 'react'; +import { MemoryRouter, Router } from 'react-router-dom'; +import type { UrlParams } from './types'; +import { UrlParamsContext, UrlParamsProvider } from './url_params_context'; + +function mountParams(location: Location) { + return mount( + + + + {({ urlParams }: { urlParams: UrlParams }) => ( + {JSON.stringify(urlParams, null, 2)} + )} + + + + ); +} + +function getDataFromOutput(wrapper: ReturnType) { + return JSON.parse(wrapper.find('#data').text()); +} + +describe('UrlParamsContext', () => { + beforeAll(() => { + moment.tz.setDefault('Etc/GMT'); + }); + + afterAll(() => { + moment.tz.setDefault(''); + }); + + it('should read values in from location', () => { + const location = { + pathname: '/test/pathname', + search: + '?rangeFrom=2010-03-15T12:00:00Z&rangeTo=2010-04-10T12:00:00Z&transactionId=123abc', + } as Location; + + const wrapper = mountParams(location); + const params = getDataFromOutput(wrapper); + + expect([params.start, params.end]).toEqual([ + '2010-03-15T12:00:00.000Z', + '2010-04-10T12:00:00.000Z', + ]); + }); + + it('should update param values if location has changed', () => { + const location = { + pathname: '/test/updated', + search: + '?rangeFrom=2009-03-15T12:00:00Z&rangeTo=2009-04-10T12:00:00Z&transactionId=UPDATED', + } as Location; + + const wrapper = mountParams(location); + + // force an update + wrapper.setProps({ abc: 123 }); + const params = getDataFromOutput(wrapper); + + expect([params.start, params.end]).toEqual([ + '2009-03-15T12:00:00.000Z', + '2009-04-10T12:00:00.000Z', + ]); + }); + + it('should parse relative time ranges on mount', () => { + const location = { + pathname: '/test/updated', + search: '?rangeFrom=now-1d%2Fd&rangeTo=now-1d%2Fd&transactionId=UPDATED', + } as Location; + + const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(0); + + const wrapper = mountParams(location); + + // force an update + wrapper.setProps({ abc: 123 }); + const params = getDataFromOutput(wrapper); + + expect([params.start, params.end]).toEqual([ + '1969-12-31T00:00:00.000Z', + '1969-12-31T23:59:59.999Z', + ]); + + nowSpy.mockRestore(); + }); + + it('should refresh the time range with new values', async () => { + const calls = []; + const history = { + location: { + pathname: '/test', + }, + listen: jest.fn(), + } as unknown as History; + + const wrapper = mount( + + + + {({ urlParams, refreshTimeRange }) => { + calls.push({ urlParams }); + return ( + + {JSON.stringify(urlParams, null, 2)} +