diff --git a/.buildkite/pipelines/on_merge.yml b/.buildkite/pipelines/on_merge.yml index 3705a2c902642..df3a74270677f 100644 --- a/.buildkite/pipelines/on_merge.yml +++ b/.buildkite/pipelines/on_merge.yml @@ -39,7 +39,6 @@ steps: provider: gcp machineType: n2-highcpu-8 preemptible: true - key: quick_checks timeout_in_minutes: 60 retry: automatic: @@ -54,7 +53,6 @@ steps: provider: gcp machineType: n2-standard-16 preemptible: true - key: linting timeout_in_minutes: 60 retry: automatic: @@ -69,8 +67,23 @@ steps: provider: gcp machineType: n2-standard-32 preemptible: true - key: linting_with_types - timeout_in_minutes: 90 + timeout_in_minutes: 60 + retry: + automatic: + - exit_status: '-1' + limit: 3 + + - command: .buildkite/scripts/steps/check_types.sh + label: 'Check Types' + agents: + image: family/kibana-ubuntu-2004 + imageProject: elastic-images-prod + provider: gcp + machineType: c4-standard-4 + diskType: 'hyperdisk-balanced' + preemptible: true + spotZones: us-central1-a,us-central1-b,us-central1-c + timeout_in_minutes: 60 retry: automatic: - exit_status: '-1' @@ -136,11 +149,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 2 retry: @@ -156,11 +164,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 4 retry: @@ -176,11 +179,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 6 retry: @@ -196,11 +194,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 5 retry: @@ -216,11 +209,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 6 retry: @@ -236,11 +224,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 1 retry: @@ -256,11 +239,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 2 retry: @@ -276,11 +254,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 2 retry: @@ -296,11 +269,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 6 retry: @@ -316,11 +284,6 @@ steps: provider: gcp machineType: n2-standard-4 preemptible: true - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 8 retry: @@ -338,11 +301,6 @@ steps: localSsds: 1 localSsdInterface: nvme machineType: n2-standard-4 - depends_on: - - build - - quick_checks - - linting - - linting_with_types timeout_in_minutes: 60 parallelism: 20 retry: @@ -353,11 +311,6 @@ steps: - command: '.buildkite/scripts/steps/functional/on_merge_unsupported_ftrs.sh' label: Trigger unsupported ftr tests timeout_in_minutes: 10 - depends_on: - - build - - quick_checks - - linting - - linting_with_types agents: image: family/kibana-ubuntu-2004 imageProject: elastic-images-prod @@ -378,20 +331,6 @@ steps: - exit_status: '-1' limit: 3 - - command: .buildkite/scripts/steps/check_types.sh - label: 'Check Types' - agents: - image: family/kibana-ubuntu-2004 - imageProject: elastic-images-prod - provider: gcp - machineType: n2-standard-4 - preemptible: true - timeout_in_minutes: 70 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - command: .buildkite/scripts/steps/checks/capture_oas_snapshot.sh label: 'Check OAS Snapshot' agents: diff --git a/.buildkite/pipelines/pull_request/apm_cypress.yml b/.buildkite/pipelines/pull_request/apm_cypress.yml index 9d2cca6d9d452..c0cb60dbc986b 100644 --- a/.buildkite/pipelines/pull_request/apm_cypress.yml +++ b/.buildkite/pipelines/pull_request/apm_cypress.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 120 parallelism: 1 # TODO: Set parallelism when apm_cypress handles it retry: diff --git a/.buildkite/pipelines/pull_request/base.yml b/.buildkite/pipelines/pull_request/base.yml index fdc80e6cb8595..54840cb43c65f 100644 --- a/.buildkite/pipelines/pull_request/base.yml +++ b/.buildkite/pipelines/pull_request/base.yml @@ -50,7 +50,21 @@ steps: machineType: n2-standard-32 preemptible: true key: linting_with_types - timeout_in_minutes: 90 + timeout_in_minutes: 60 + retry: + automatic: + - exit_status: '-1' + limit: 3 + + - command: .buildkite/scripts/steps/check_types.sh + label: 'Check Types' + agents: + machineType: c4-standard-4 + diskType: 'hyperdisk-balanced' + preemptible: true + spotZones: us-central1-a,us-central1-b,us-central1-c + key: check_types + timeout_in_minutes: 60 retry: automatic: - exit_status: '-1' @@ -85,18 +99,6 @@ steps: - exit_status: '*' limit: 1 - - command: .buildkite/scripts/steps/check_types.sh - label: 'Check Types' - agents: - machineType: n2-standard-4 - preemptible: true - key: check_types - timeout_in_minutes: 70 - retry: - automatic: - - exit_status: '-1' - limit: 3 - - command: .buildkite/scripts/steps/checks.sh label: 'Checks' key: checks diff --git a/.buildkite/pipelines/pull_request/deploy_cloud.yml b/.buildkite/pipelines/pull_request/deploy_cloud.yml index e82d1ef2e494c..6b42037b95953 100644 --- a/.buildkite/pipelines/pull_request/deploy_cloud.yml +++ b/.buildkite/pipelines/pull_request/deploy_cloud.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 30 soft_fail: true retry: diff --git a/.buildkite/pipelines/pull_request/exploratory_view_plugin.yml b/.buildkite/pipelines/pull_request/exploratory_view_plugin.yml index 42aaf59b1c1f2..c46edb528987a 100644 --- a/.buildkite/pipelines/pull_request/exploratory_view_plugin.yml +++ b/.buildkite/pipelines/pull_request/exploratory_view_plugin.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 artifact_paths: - 'x-pack/plugins/observability_solution/exploratory_view/e2e/.journeys/**/*' diff --git a/.buildkite/pipelines/pull_request/fips.yml b/.buildkite/pipelines/pull_request/fips.yml index 3fa0ed9bd2062..1a759e1288328 100644 --- a/.buildkite/pipelines/pull_request/fips.yml +++ b/.buildkite/pipelines/pull_request/fips.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 soft_fail: true retry: diff --git a/.buildkite/pipelines/pull_request/fleet_cypress.yml b/.buildkite/pipelines/pull_request/fleet_cypress.yml index 071106209caaa..d20591728b788 100644 --- a/.buildkite/pipelines/pull_request/fleet_cypress.yml +++ b/.buildkite/pipelines/pull_request/fleet_cypress.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 50 parallelism: 6 retry: diff --git a/.buildkite/pipelines/pull_request/kbn_handlebars.yml b/.buildkite/pipelines/pull_request/kbn_handlebars.yml index ad338ec425a04..36901a5d5c552 100644 --- a/.buildkite/pipelines/pull_request/kbn_handlebars.yml +++ b/.buildkite/pipelines/pull_request/kbn_handlebars.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 5 retry: automatic: diff --git a/.buildkite/pipelines/pull_request/observability_onboarding_cypress.yml b/.buildkite/pipelines/pull_request/observability_onboarding_cypress.yml index d0afe1cd138da..8906cc72fa81f 100644 --- a/.buildkite/pipelines/pull_request/observability_onboarding_cypress.yml +++ b/.buildkite/pipelines/pull_request/observability_onboarding_cypress.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 120 retry: automatic: diff --git a/.buildkite/pipelines/pull_request/profiling_cypress.yml b/.buildkite/pipelines/pull_request/profiling_cypress.yml index 2b86cffe75fa6..100e42206b3a1 100644 --- a/.buildkite/pipelines/pull_request/profiling_cypress.yml +++ b/.buildkite/pipelines/pull_request/profiling_cypress.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 120 parallelism: 2 retry: diff --git a/.buildkite/pipelines/pull_request/response_ops.yml b/.buildkite/pipelines/pull_request/response_ops.yml index a5c9b27ee7ecf..f09beb168259f 100644 --- a/.buildkite/pipelines/pull_request/response_ops.yml +++ b/.buildkite/pipelines/pull_request/response_ops.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 120 parallelism: 9 retry: diff --git a/.buildkite/pipelines/pull_request/response_ops_cases.yml b/.buildkite/pipelines/pull_request/response_ops_cases.yml index 994fbb6c4963a..5382ab6017fa6 100644 --- a/.buildkite/pipelines/pull_request/response_ops_cases.yml +++ b/.buildkite/pipelines/pull_request/response_ops_cases.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 120 retry: automatic: diff --git a/.buildkite/pipelines/pull_request/security_solution/ai_assistant.yml b/.buildkite/pipelines/pull_request/security_solution/ai_assistant.yml index 6b87f41d585f8..de0cf4daefe73 100644 --- a/.buildkite/pipelines/pull_request/security_solution/ai_assistant.yml +++ b/.buildkite/pipelines/pull_request/security_solution/ai_assistant.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 1 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml b/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml index 93fad6eecf167..d2f1571f9d93f 100644 --- a/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml +++ b/.buildkite/pipelines/pull_request/security_solution/cloud_security_posture.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 1 retry: @@ -26,6 +27,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 1 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/cypress_burn.yml b/.buildkite/pipelines/pull_request/security_solution/cypress_burn.yml index 1ba22d058e6c1..fc9fab0dec2ae 100644 --- a/.buildkite/pipelines/pull_request/security_solution/cypress_burn.yml +++ b/.buildkite/pipelines/pull_request/security_solution/cypress_burn.yml @@ -11,6 +11,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 soft_fail: true parallelism: 1 @@ -27,6 +28,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 1 retry: @@ -43,6 +45,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 50 soft_fail: true retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/defend_workflows.yml b/.buildkite/pipelines/pull_request/security_solution/defend_workflows.yml index ea7613fd81cba..e8b399ed3ef60 100644 --- a/.buildkite/pipelines/pull_request/security_solution/defend_workflows.yml +++ b/.buildkite/pipelines/pull_request/security_solution/defend_workflows.yml @@ -11,6 +11,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 20 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/detection_engine.yml b/.buildkite/pipelines/pull_request/security_solution/detection_engine.yml index f18d187aab9e7..e879d2ad267f0 100644 --- a/.buildkite/pipelines/pull_request/security_solution/detection_engine.yml +++ b/.buildkite/pipelines/pull_request/security_solution/detection_engine.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 5 retry: @@ -26,6 +27,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 2 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/entity_analytics.yml b/.buildkite/pipelines/pull_request/security_solution/entity_analytics.yml index 16e1860a1453c..4e74af8f80742 100644 --- a/.buildkite/pipelines/pull_request/security_solution/entity_analytics.yml +++ b/.buildkite/pipelines/pull_request/security_solution/entity_analytics.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 2 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/explore.yml b/.buildkite/pipelines/pull_request/security_solution/explore.yml index 5fa4229e7dbde..5121899c17a59 100644 --- a/.buildkite/pipelines/pull_request/security_solution/explore.yml +++ b/.buildkite/pipelines/pull_request/security_solution/explore.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 2 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/investigations.yml b/.buildkite/pipelines/pull_request/security_solution/investigations.yml index 469f6d7a2c159..f33634118fad2 100644 --- a/.buildkite/pipelines/pull_request/security_solution/investigations.yml +++ b/.buildkite/pipelines/pull_request/security_solution/investigations.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 7 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/osquery_cypress.yml b/.buildkite/pipelines/pull_request/security_solution/osquery_cypress.yml index cb04f1559f3b1..8d7650e93647c 100644 --- a/.buildkite/pipelines/pull_request/security_solution/osquery_cypress.yml +++ b/.buildkite/pipelines/pull_request/security_solution/osquery_cypress.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 8 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/playwright.yml b/.buildkite/pipelines/pull_request/security_solution/playwright.yml index 98a939570b1be..3255d3b3203c7 100644 --- a/.buildkite/pipelines/pull_request/security_solution/playwright.yml +++ b/.buildkite/pipelines/pull_request/security_solution/playwright.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 1 retry: diff --git a/.buildkite/pipelines/pull_request/security_solution/rule_management.yml b/.buildkite/pipelines/pull_request/security_solution/rule_management.yml index 887e1c8597af7..f49a96da39a89 100644 --- a/.buildkite/pipelines/pull_request/security_solution/rule_management.yml +++ b/.buildkite/pipelines/pull_request/security_solution/rule_management.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 4 retry: @@ -26,6 +27,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 parallelism: 2 retry: diff --git a/.buildkite/pipelines/pull_request/slo_plugin_e2e.yml b/.buildkite/pipelines/pull_request/slo_plugin_e2e.yml index 025c80809ab35..3d1a4f9b46f41 100644 --- a/.buildkite/pipelines/pull_request/slo_plugin_e2e.yml +++ b/.buildkite/pipelines/pull_request/slo_plugin_e2e.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 30 artifact_paths: - 'x-pack/plugins/observability_solution/slo/e2e/.journeys/**/*' diff --git a/.buildkite/pipelines/pull_request/synthetics_plugin.yml b/.buildkite/pipelines/pull_request/synthetics_plugin.yml index 0707650aa7c01..f5d6b841a953d 100644 --- a/.buildkite/pipelines/pull_request/synthetics_plugin.yml +++ b/.buildkite/pipelines/pull_request/synthetics_plugin.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 artifact_paths: - 'x-pack/plugins/observability_solution/synthetics/e2e/.journeys/**/*' diff --git a/.buildkite/pipelines/pull_request/uptime_plugin.yml b/.buildkite/pipelines/pull_request/uptime_plugin.yml index 33a529739ae6f..a03915ef77099 100644 --- a/.buildkite/pipelines/pull_request/uptime_plugin.yml +++ b/.buildkite/pipelines/pull_request/uptime_plugin.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 artifact_paths: - 'x-pack/plugins/observability_solution/synthetics/e2e/.journeys/**/*' diff --git a/.buildkite/pipelines/pull_request/ux_plugin_e2e.yml b/.buildkite/pipelines/pull_request/ux_plugin_e2e.yml index 977701cc99485..cd95f44fa2e86 100644 --- a/.buildkite/pipelines/pull_request/ux_plugin_e2e.yml +++ b/.buildkite/pipelines/pull_request/ux_plugin_e2e.yml @@ -9,6 +9,7 @@ steps: - quick_checks - linting - linting_with_types + - check_types timeout_in_minutes: 60 artifact_paths: - 'x-pack/plugins/observability_solution/ux/e2e/.journeys/**/*' diff --git a/docs/user/dashboard/create-dashboards.asciidoc b/docs/user/dashboard/create-dashboards.asciidoc index b07b4e88a684a..8b0d5e5f524fd 100644 --- a/docs/user/dashboard/create-dashboards.asciidoc +++ b/docs/user/dashboard/create-dashboards.asciidoc @@ -83,6 +83,7 @@ image::https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/bltf75cdb828 [[save-dashboards]] . **Save** the dashboard. You can then leave the **Edit** mode and *Switch to view mode*. +NOTE: Managed dashboards can't be edited directly, but you can <> them and edit these duplicates. [[reset-the-dashboard]] === Reset dashboard changes @@ -155,6 +156,24 @@ Copy panels from one dashboard to another dashboard. [role="screenshot"] image:https://images.contentstack.io/v3/assets/bltefdd0b53724fa2ce/blt64206db263cf5514/66f49286833cffb09bebd18d/copy-to-dashboard-8.15.0.gif[Copy a panel to another dashboard, width 30%] +[[duplicate-dashboards]] +== Duplicate dashboards + +. Open the dashboard you want to duplicate. + +. In *View* mode, click *Duplicate* in the toolbar. + +. In the *Duplicate dashboard* window, enter a title and optional description and tags. + +. Click *Save*. + +You will be redirected to the duplicated dashboard. + +To duplicate a managed dashboard, follow the instructions above or click the *Managed* badge in the toolbar. Then click *Duplicate* in the dialogue that appears. + +[role="screenshot"] +image::images/managed-dashboard-popover-8.16.0.png[Managed badge dialog with Duplicate button, width=40%] + == Import dashboards You can import dashboards from the **Saved Objects** page under **Stack Management**. Refer to <>. diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 5ca198c9831af..2bc6738516f15 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -17,7 +17,7 @@ There are several <> in {kib} that let you create // add link to sharing section At any time, you can <> you've created with your team, in {kib} or outside. -Some dashboards are created and managed by the system, and are identified as `managed` in your list of of dashboards. This generally happens when you set up an integration to add data. You can't edit managed dashboards directly, but you can duplicate them and edit these duplicates. +Some dashboards are created and managed by the system, and are identified as `managed` in your list of dashboards. This generally happens when you set up an integration to add data. You can't edit managed dashboards directly, but you can <> them and edit these duplicates. -- diff --git a/docs/user/dashboard/drilldowns.asciidoc b/docs/user/dashboard/drilldowns.asciidoc index 6b3a6d80ecdda..cb568d97e69ee 100644 --- a/docs/user/dashboard/drilldowns.asciidoc +++ b/docs/user/dashboard/drilldowns.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[drilldowns]] -=== Drilldowns +=== Add drilldowns Panels have built-in interactive capabilities that apply filters to the dashboard data. For example, when you drag a time range or click a pie slice, a filter for the time range or pie slice is applied. Drilldowns let you customize the interactive behavior while keeping the context of the interaction. diff --git a/docs/user/dashboard/images/managed-dashboard-popover-8.16.0.png b/docs/user/dashboard/images/managed-dashboard-popover-8.16.0.png new file mode 100644 index 0000000000000..b1cd0562a42c7 Binary files /dev/null and b/docs/user/dashboard/images/managed-dashboard-popover-8.16.0.png differ diff --git a/package.json b/package.json index 2b7f9cfd87861..7720b030be546 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "@elastic/ecs": "^8.11.1", "@elastic/elasticsearch": "^8.15.0", "@elastic/ems-client": "8.5.3", - "@elastic/eui": "97.0.0-backport.0", + "@elastic/eui": "97.0.0-backport.1", "@elastic/filesaver": "1.1.2", "@elastic/node-crypto": "^1.2.3", "@elastic/numeral": "^2.5.1", diff --git a/packages/kbn-apm-synthtrace-client/index.ts b/packages/kbn-apm-synthtrace-client/index.ts index 6ac3b6525ec00..bdb93fbe20076 100644 --- a/packages/kbn-apm-synthtrace-client/index.ts +++ b/packages/kbn-apm-synthtrace-client/index.ts @@ -37,3 +37,4 @@ export type { ESDocumentWithOperation, SynthtraceESAction, SynthtraceGenerator } export { log, type LogDocument, LONG_FIELD_NAME } from './src/lib/logs'; export { type AssetDocument } from './src/lib/assets'; export { syntheticsMonitor, type SyntheticsMonitorDocument } from './src/lib/synthetics'; +export { type EntityFields, entities } from './src/lib/entities'; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.ts new file mode 100644 index 0000000000000..d7f34d7e9e484 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/container_entity.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityDataStreamType, EntityFields } from '.'; +import { Serializable } from '../serializable'; + +class ContainerEntity extends Serializable { + constructor(fields: EntityFields) { + super({ + ...fields, + 'entity.type': 'container', + 'entity.definition_id': 'builtin_containers_from_ecs_data', + 'entity.identity_fields': ['container.id'], + }); + } +} + +export function containerEntity({ + agentName, + dataStreamType, + containerId, + entityId, +}: { + agentName: string[]; + dataStreamType: EntityDataStreamType[]; + containerId: string; + entityId: string; +}) { + return new ContainerEntity({ + 'source_data_stream.type': dataStreamType, + 'agent.name': agentName, + 'container.id': containerId, + 'entity.display_name': containerId, + 'entity.id': entityId, + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.ts new file mode 100644 index 0000000000000..c2c0330bb03ca --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/host_entity.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityDataStreamType, EntityFields } from '.'; +import { Serializable } from '../serializable'; + +class HostEntity extends Serializable { + constructor(fields: EntityFields) { + super({ + ...fields, + 'entity.type': 'host', + 'entity.definition_id': 'builtin_hosts_from_ecs_data', + 'entity.identity_fields': ['host.name'], + }); + } +} + +export function hostEntity({ + agentName, + dataStreamType, + hostName, + entityId, +}: { + agentName: string[]; + dataStreamType: EntityDataStreamType[]; + hostName: string; + entityId: string; +}) { + return new HostEntity({ + 'source_data_stream.type': dataStreamType, + 'agent.name': agentName, + 'host.name': hostName, + 'entity.display_name': hostName, + 'entity.id': entityId, + }); +} diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/index.ts new file mode 100644 index 0000000000000..d791e9bdc6c34 --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/index.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Fields } from '../entity'; +import { serviceEntity } from './service_entity'; +import { hostEntity } from './host_entity'; +import { containerEntity } from './container_entity'; + +export type EntityDataStreamType = 'metrics' | 'logs' | 'traces'; +export type Schema = 'ecs' | 'semconv'; + +export type EntityFields = Fields & + Partial<{ + 'agent.name': string[]; + 'source_data_stream.type': string | string[]; + 'source_data_stream.dataset': string | string[]; + 'event.ingested': string; + source_index: string; + 'entity.last_seen_timestamp': string; + 'entity.schema_version': string; + 'entity.definition_version': string; + 'entity.display_name': string; + 'entity.identity_fields': string | string[]; + 'entity.id': string; + 'entity.type': string; + 'entity.definition_id': string; + [key: string]: any; + }>; + +export const entities = { serviceEntity, hostEntity, containerEntity }; diff --git a/packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.ts b/packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.ts new file mode 100644 index 0000000000000..e711f2dad223f --- /dev/null +++ b/packages/kbn-apm-synthtrace-client/src/lib/entities/service_entity.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { EntityDataStreamType, EntityFields } from '.'; +import { Serializable } from '../serializable'; + +class ServiceEntity extends Serializable { + constructor(fields: EntityFields) { + super({ + ...fields, + 'entity.type': 'service', + 'entity.definition_id': 'builtin_services_from_ecs_data', + 'entity.identity_fields': ['service.name'], + }); + } +} + +export function serviceEntity({ + agentName, + dataStreamType, + serviceName, + environment, + entityId, +}: { + agentName: string[]; + serviceName: string; + dataStreamType: EntityDataStreamType[]; + environment?: string; + entityId: string; +}) { + return new ServiceEntity({ + 'service.name': serviceName, + 'entity.display_name': serviceName, + 'service.environment': environment, + 'source_data_stream.type': dataStreamType, + 'agent.name': agentName, + 'entity.id': entityId, + }); +} diff --git a/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts b/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts new file mode 100644 index 0000000000000..4c5d17111fca6 --- /dev/null +++ b/packages/kbn-apm-synthtrace/src/lib/entities/entities_synthtrace_es_client.ts @@ -0,0 +1,94 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { Client } from '@elastic/elasticsearch'; +import { EntityFields, ESDocumentWithOperation } from '@kbn/apm-synthtrace-client'; +import { pipeline, Readable, Transform } from 'stream'; +import { SynthtraceEsClient, SynthtraceEsClientOptions } from '../shared/base_client'; +import { getDedotTransform } from '../shared/get_dedot_transform'; +import { getSerializeTransform } from '../shared/get_serialize_transform'; +import { Logger } from '../utils/create_logger'; + +export type EntitiesSynthtraceEsClientOptions = Omit; + +interface Pipeline { + includeSerialization?: boolean; +} + +export class EntitiesSynthtraceEsClient extends SynthtraceEsClient { + constructor(options: { client: Client; logger: Logger } & EntitiesSynthtraceEsClientOptions) { + super({ + ...options, + pipeline: entitiesPipeline(), + }); + this.indices = ['.entities.v1.latest.builtin*']; + } + + getDefaultPipeline({ includeSerialization }: Pipeline = { includeSerialization: true }) { + return entitiesPipeline({ includeSerialization }); + } +} + +function entitiesPipeline({ includeSerialization }: Pipeline = { includeSerialization: true }) { + return (base: Readable) => { + const serializationTransform = includeSerialization ? [getSerializeTransform()] : []; + + return pipeline( + // @ts-expect-error Some weird stuff here with the type definition for pipeline. We have tests! + base, + ...serializationTransform, + lastSeenTimestampTransform(), + getRoutingTransform(), + getDedotTransform(), + (err: unknown) => { + if (err) { + throw err; + } + } + ); + }; +} + +function lastSeenTimestampTransform() { + return new Transform({ + objectMode: true, + transform(document: ESDocumentWithOperation, encoding, callback) { + const timestamp = document['@timestamp']; + if (timestamp) { + const isoString = new Date(timestamp).toISOString(); + document['entity.last_seen_timestamp'] = isoString; + document['event.ingested'] = isoString; + delete document['@timestamp']; + } + callback(null, document); + }, + }); +} + +function getRoutingTransform() { + return new Transform({ + objectMode: true, + transform(document: ESDocumentWithOperation, encoding, callback) { + const entityType: string | undefined = document['entity.type']; + if (entityType === undefined) { + throw new Error(`entity.type was not defined: ${JSON.stringify(document)}`); + } + const entityIndexName = `${entityType}s`; + document._action = { + index: { + _index: + `.entities.v1.latest.builtin_${entityIndexName}_from_ecs_data`.toLocaleLowerCase(), + _id: document['entity.id'], + }, + }; + + callback(null, document); + }, + }); +} diff --git a/packages/kbn-investigation-shared/src/rest_specs/entity.ts b/packages/kbn-investigation-shared/src/rest_specs/entity.ts index 1c29192c2a098..8e571f3e2a4d4 100644 --- a/packages/kbn-investigation-shared/src/rest_specs/entity.ts +++ b/packages/kbn-investigation-shared/src/rest_specs/entity.ts @@ -19,13 +19,12 @@ const metricsSchema = z.object({ const entitySchema = z.object({ id: z.string(), - definitionId: z.string(), - definitionVersion: z.string(), - displayName: z.string(), - firstSeenTimestamp: z.string(), - lastSeenTimestamp: z.string(), - identityFields: z.array(z.string()), - schemaVersion: z.string(), + definition_id: z.string(), + definition_version: z.string(), + display_name: z.string(), + last_seen_timestamp: z.string(), + identity_fields: z.array(z.string()), + schema_version: z.string(), type: z.string(), metrics: metricsSchema, }); diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index c810a74091458..85cd6d7458ba9 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -51,7 +51,7 @@ export async function runDockerGenerator( */ if (flags.baseImage === 'wolfi') baseImageName = - 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:8cff240b81057968575dd28dab0c3609657cb7e0e60ff017261e5b721fad9e1b'; + 'docker.elastic.co/wolfi/chainguard-base:latest@sha256:7082adcc2c4380be273ab5b80c4a762b4f17279c13c6fc8f87a60190aee2e2cd'; let imageFlavor = ''; if (flags.baseImage === 'ubi') imageFlavor += `-ubi`; diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 59bf71885474c..02fbb94974ffa 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -87,7 +87,7 @@ export const LICENSE_OVERRIDES = { 'jsts@1.6.2': ['Eclipse Distribution License - v 1.0'], // cf. https://github.com/bjornharrtell/jsts '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint '@elastic/ems-client@8.5.3': ['Elastic License 2.0'], - '@elastic/eui@97.0.0-backport.0': ['Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0'], + '@elastic/eui@97.0.0-backport.1': ['Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0'], 'language-subtag-registry@0.3.21': ['CC-BY-4.0'], // retired ODC‑By license https://github.com/mattcg/language-subtag-registry 'buffers@0.1.1': ['MIT'], // license in importing module https://www.npmjs.com/package/binary '@bufbuild/protobuf@1.2.1': ['Apache-2.0'], // license (Apache-2.0 AND BSD-3-Clause) diff --git a/src/plugins/dashboard/kibana.jsonc b/src/plugins/dashboard/kibana.jsonc index 2bf60cde55ef0..77c2337d9944e 100644 --- a/src/plugins/dashboard/kibana.jsonc +++ b/src/plugins/dashboard/kibana.jsonc @@ -12,6 +12,7 @@ "dataViews", "dataViewEditor", "embeddable", + "fieldFormats", "controls", "inspector", "navigation", diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity.test.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity.test.ts index c8ef716371338..000edeadbcf83 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/entity.test.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/entity.test.ts @@ -9,19 +9,15 @@ import { entityLatestSchema, entityMetadataSchema } from './entity'; const entity = { entity: { - lastSeenTimestamp: '2024-08-06T17:03:50.722Z', - schemaVersion: 'v1', - definitionVersion: '999.999.999', - displayName: 'message_processor', - identityFields: ['log.logger', 'event.category'], + last_seen_timestamp: '2024-08-06T17:03:50.722Z', + schema_version: 'v1', + definition_version: '999.999.999', + display_name: 'message_processor', + identity_fields: ['log.logger', 'event.category'], id: '6UHVPiduEC2qk6rMjs1Jzg==', - metrics: { - logRate: 100, - errorRate: 0, - }, type: 'service', - firstSeenTimestamp: '2024-08-06T16:50:00.000Z', - definitionId: 'admin-console-services', + metrics: {}, + definition_id: 'admin-console-services', }, }; @@ -47,7 +43,7 @@ const metadata = { ingested: '2024-08-06T17:06:24.444700Z', category: '', }, - sourceIndex: ['kbn-data-forge-fake_stack.message_processor-2024-08-01'], + source_index: ['kbn-data-forge-fake_stack.message_processor-2024-08-01'], log: { logger: 'message_processor', }, diff --git a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts index 3eb87a797ef21..9ab02e0931d9c 100644 --- a/x-pack/packages/kbn-entities-schema/src/schema/entity.ts +++ b/x-pack/packages/kbn-entities-schema/src/schema/entity.ts @@ -11,12 +11,12 @@ import { arrayOfStringsSchema } from './common'; export const entityBaseSchema = z.object({ id: z.string(), type: z.string(), - identityFields: arrayOfStringsSchema, - displayName: z.string(), + identity_fields: arrayOfStringsSchema, + display_name: z.string(), metrics: z.record(z.string(), z.number()), - definitionVersion: z.string(), - schemaVersion: z.string(), - definitionId: z.string(), + definition_version: z.string(), + schema_version: z.string(), + definition_id: z.string(), }); export interface MetadataRecord { @@ -34,15 +34,8 @@ export const entityLatestSchema = z .object({ entity: entityBaseSchema.merge( z.object({ - lastSeenTimestamp: z.string(), + last_seen_timestamp: z.string(), }) ), }) .and(entityMetadataSchema); - -export const entityHistorySchema = z - .object({ - '@timestamp': z.string(), - entity: entityBaseSchema, - }) - .and(entityMetadataSchema); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx index c78bf3e918737..bf8cf009759d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/api_logs/components/empty_state.tsx @@ -11,7 +11,7 @@ import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { API_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyState: React.FC = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.apiLogs.empty.buttonLabel', { defaultMessage: 'View the API reference', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx index bef8ed4462fdc..2f66dc455442e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/crawl_rules_table.tsx @@ -23,11 +23,11 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../shared/doc_links'; import { clearFlashMessages, flashSuccessToast } from '../../../../shared/flash_messages'; import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; import { ItemWithAnID } from '../../../../shared/tables/types'; -import { CRAWL_RULES_DOCS_URL } from '../../../routes'; import { CrawlerSingleDomainLogic } from '../crawler_single_domain_logic'; import { CrawlerPolicies, @@ -53,7 +53,7 @@ const DEFAULT_DESCRIPTION = ( defaultMessage="Create a crawl rule to include or exclude pages whose URL matches the rule. Rules run in sequential order, and each URL is evaluated according to the first match. {link}" values={{ link: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.crawlRulesTable.descriptionLinkText', { defaultMessage: 'Learn more about crawl rules' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx index 26794d0421353..ef4d7448a5785 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/deduplication_panel/deduplication_panel.tsx @@ -27,7 +27,7 @@ import { EuiSelectableLIOption } from '@elastic/eui/src/components/selectable/se import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DUPLICATE_DOCS_URL } from '../../../../routes'; +import { docLinks } from '../../../../../shared/doc_links'; import { DataPanel } from '../../../data_panel'; import { CrawlerSingleDomainLogic } from '../../crawler_single_domain_logic'; @@ -84,7 +84,7 @@ export const DeduplicationPanel: React.FC = () => { documents on this domain. {documentationLink}." values={{ documentationLink: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.deduplicationPanel.learnMoreMessage', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx index 4fc7a0569ba0e..d4bfdf77704b8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/entry_points_table.tsx @@ -14,10 +14,10 @@ import { EuiFieldText, EuiLink, EuiSpacer, EuiText, EuiTitle } from '@elastic/eu import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../shared/doc_links'; import { GenericEndpointInlineEditableTable } from '../../../../shared/tables/generic_endpoint_inline_editable_table'; import { InlineEditableTableColumn } from '../../../../shared/tables/inline_editable_table/types'; import { ItemWithAnID } from '../../../../shared/tables/types'; -import { ENTRY_POINTS_DOCS_URL } from '../../../routes'; import { CrawlerDomain, EntryPoint } from '../types'; import { EntryPointsTableLogic } from './entry_points_table_logic'; @@ -80,7 +80,7 @@ export const EntryPointsTable: React.FC = ({ defaultMessage: 'Include the most important URLs for your website here. Entry point URLs will be the first pages to be indexed and processed for links to other pages.', })}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.entryPointsTable.learnMoreLinkText', { defaultMessage: 'Learn more about entry points.' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx index 4533ca04c75bc..cb0377a471b93 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/components/manage_crawls_popover/automatic_crawl_scheduler.tsx @@ -36,8 +36,8 @@ import { MONTHS_UNIT_LABEL, WEEKS_UNIT_LABEL, } from '../../../../../shared/constants/units'; +import { docLinks } from '../../../../../shared/doc_links'; -import { WEB_CRAWLER_DOCS_URL } from '../../../../routes'; import { CrawlUnits } from '../../types'; import { AutomaticCrawlSchedulerLogic } from './automatic_crawl_scheduler_logic'; @@ -81,7 +81,7 @@ export const AutomaticCrawlScheduler: React.FC = () => { defaultMessage="Don't worry about it, we'll start a crawl for you. {readMoreMessage}." values={{ readMoreMessage: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.automaticCrawlSchedule.readMoreLink', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx index 13a13c25a5ad8..d18f40c4c9f23 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/crawler/crawler_overview.tsx @@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { i18n } from '@kbn/i18n'; -import { WEB_CRAWLER_DOCS_URL, WEB_CRAWLER_LOG_DOCS_URL } from '../../routes'; +import { docLinks } from '../../../shared/doc_links'; import { getEngineBreadcrumbs } from '../engine'; import { AppSearchPageTemplate } from '../layout'; @@ -82,7 +82,7 @@ export const CrawlerOverview: React.FC = () => { defaultMessage: "Easily index your website's content. To get started, enter your domain name, provide optional entry points and crawl rules, and we will handle the rest.", })}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.empty.crawlerDocumentationLinkDescription', { @@ -125,7 +125,7 @@ export const CrawlerOverview: React.FC = () => { defaultMessage: "Recent crawl requests are logged here. Using the request ID of each crawl, you can track progress and examine crawl events in Kibana's Discover or Logs user interfaces.", })}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.configurationDocumentationLinkDescription', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts index 9676e7f859ac5..7468597294026 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/constants.ts @@ -7,8 +7,6 @@ import { i18n } from '@kbn/i18n'; -import { AUTHENTICATION_DOCS_URL } from '../../routes'; - export const CREDENTIALS_TITLE = i18n.translate( 'xpack.enterpriseSearch.appSearch.credentials.title', { defaultMessage: 'Credentials' } @@ -108,5 +106,3 @@ export const TOKEN_TYPE_INFO = [ ]; export const FLYOUT_ARIA_LABEL_ID = 'credentialsFlyoutTitle'; - -export const DOCS_HREF = AUTHENTICATION_DOCS_URL; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx index 2cf381d8f604f..5213f786dfead 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_flyout/form_components/key_type.tsx @@ -12,8 +12,10 @@ import { useValues, useActions } from 'kea'; import { EuiFormRow, EuiSelect, EuiText, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { docLinks } from '../../../../../shared/doc_links'; + import { AppLogic } from '../../../../app_logic'; -import { TOKEN_TYPE_DESCRIPTION, TOKEN_TYPE_INFO, DOCS_HREF } from '../../constants'; +import { TOKEN_TYPE_DESCRIPTION, TOKEN_TYPE_INFO } from '../../constants'; import { CredentialsLogic } from '../../credentials_logic'; export const FormKeyType: React.FC = () => { @@ -36,7 +38,7 @@ export const FormKeyType: React.FC = () => {

{tokenDescription}{' '} - + {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.documentationLink1', { defaultMessage: 'Visit the documentation', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx index e351cdf36c657..4f45da9e26046 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/credentials/credentials_list/credentials_list.tsx @@ -19,9 +19,9 @@ import { import { i18n } from '@kbn/i18n'; import { EDIT_BUTTON_LABEL, DELETE_BUTTON_LABEL } from '../../../../shared/constants'; +import { docLinks } from '../../../../shared/doc_links'; import { HiddenText } from '../../../../shared/hidden_text'; import { convertMetaToPagination, handlePageChange } from '../../../../shared/table_pagination'; -import { API_KEYS_DOCS_URL } from '../../../routes'; import { TOKEN_TYPE_DISPLAY_NAMES } from '../constants'; import { CredentialsLogic } from '../credentials_logic'; import { ApiToken } from '../types'; @@ -141,7 +141,7 @@ export const CredentialsList: React.FC = () => { defaultMessage: 'Allow applications to access Elastic App Search on your behalf.', })} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.credentials.empty.buttonLabel', { defaultMessage: 'Learn about API keys', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx index 10d81f1623959..363da83d56aac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/curations/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { CURATIONS_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyState: React.FC = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.curations.empty.buttonLabel', { defaultMessage: 'Read the curations guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx index 7bbe276aedf69..98da6dc88ef57 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx @@ -30,8 +30,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { DocumentCreationLogic } from '..'; import { CANCEL_BUTTON_LABEL } from '../../../../shared/constants'; +import { docLinks } from '../../../../shared/doc_links'; import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; -import { API_CLIENTS_DOCS_URL, INDEXING_DOCS_URL } from '../../../routes'; import { EngineLogic } from '../../engine'; import { EngineDetails } from '../../engine/types'; @@ -74,12 +74,12 @@ export const FlyoutBody: React.FC = () => { defaultMessage="The {documentsApiLink} can be used to add new documents to your engine, update documents, retrieve documents by id, and delete documents. There are a variety of {clientLibrariesLink} to help you get started." values={{ documentsApiLink: ( - + documents API ), clientLibrariesLink: ( - + client libraries ), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx index 07de7b3ec0c34..80e087e007671 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx @@ -26,9 +26,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { docLinks } from '../../../shared/doc_links'; import { parseQueryParams } from '../../../shared/query_params'; import { EuiCardTo } from '../../../shared/react_router_helpers'; -import { INDEXING_DOCS_URL, ENGINE_CRAWLER_PATH } from '../../routes'; +import { ENGINE_CRAWLER_PATH } from '../../routes'; import { generateEnginePath } from '../engine'; import illustration from './illustration.svg'; @@ -106,7 +107,7 @@ export const DocumentCreationButtons: React.FC = ({ )} {' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.emptyStateFooterLink', { defaultMessage: 'Read documentation' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx index 85e834b320751..a311899d380e9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { INDEXING_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyState = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.documents.empty.buttonLabel', { defaultMessage: 'Read the documents guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx index e31a17406ffd3..e4bcf810d58f8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -12,8 +12,8 @@ import { useValues } from 'kea'; import { EuiButton, EuiEmptyPrompt, EuiImage, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { docLinks } from '../../../shared/doc_links'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; -import { DOCS_URL } from '../../routes'; import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; import illustration from '../document_creation/illustration.svg'; @@ -85,7 +85,7 @@ export const EmptyEngineOverview: React.FC = () => { { defaultMessage: 'Engine setup' } ), rightSideItems: [ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.overview.empty.headingAction', { defaultMessage: 'View documentation' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx index 3cf461e3f7d45..0824997ba8896 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_meta_engines_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { META_ENGINES_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyMetaEnginesState: React.FC = () => ( (

} actions={ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engines.metaEngines.emptyPromptButtonLabel', { defaultMessage: 'Learn more about meta engines' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx index e30868beeb209..8b32ffe77e701 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/meta_engine_creation/constants.tsx @@ -11,7 +11,7 @@ import { EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { META_ENGINES_DOCS_URL } from '../../routes'; +import { docLinks } from '../../../shared/doc_links'; export const DEFAULT_LANGUAGE = 'Universal'; @@ -57,7 +57,7 @@ export const META_ENGINE_CREATION_FORM_DOCUMENTATION_DESCRIPTION = ( defaultMessage="{documentationLink} for information about how to get started." values={{ documentationLink: ( - + {META_ENGINE_CREATION_FORM_DOCUMENTATION_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx index f17f7a582efdf..b792dec2cba0e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { RELEVANCE_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyState: React.FC = () => ( ( } )} actions={ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.empty.buttonLabel', { defaultMessage: 'Read the relevance tuning guide' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx index e4b2027aa3d6d..d78949d0fbe74 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/components/precision_slider/precision_slider.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { PRECISION_DOCS_URL } from '../../../../routes'; +import { docLinks } from '../../../../../shared/doc_links'; import { RelevanceTuningLogic } from '../../relevance_tuning_logic'; import { STEP_DESCRIPTIONS } from './constants'; @@ -57,7 +57,11 @@ export const PrecisionSlider: React.FC = () => { defaultMessage: 'Fine tune the precision vs. recall settings on your engine.', } )}{' '} - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.precisionSlider.learnMore.link', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx index bf2c21a1003f5..ef0bea39439c5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_callouts.tsx @@ -13,8 +13,9 @@ import { EuiCallOut, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../shared/doc_links'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; -import { META_ENGINES_DOCS_URL, ENGINE_SCHEMA_PATH } from '../../routes'; +import { ENGINE_SCHEMA_PATH } from '../../routes'; import { EngineLogic, generateEnginePath } from '../engine'; import { RelevanceTuningLogic } from '.'; @@ -98,7 +99,7 @@ export const RelevanceTuningCallouts: React.FC = () => { values={{ schemaFieldsWithConflictsCount, link: ( - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.whatsThisLinkLabel', { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx index 7f91447b910b6..6434a877ead5e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { RESULT_SETTINGS_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyState: React.FC = () => ( ( } )} actions={ - + {i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.resultSettings.empty.buttonLabel', { defaultMessage: 'Read the result settings guide' } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx index aff138b9c3884..2ffe6cb357e54 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx @@ -12,6 +12,7 @@ import { useActions, useValues } from 'kea'; import { EuiSpacer } from '@elastic/eui'; import { APP_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { docLinks } from '../../../shared/doc_links'; import { RoleMappingsTable, RoleMappingsHeading, @@ -22,7 +23,6 @@ import { } from '../../../shared/role_mapping'; import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; -import { SECURITY_DOCS_URL } from '../../routes'; import { AppSearchPageTemplate } from '../layout'; import { ROLE_MAPPINGS_ENGINE_ACCESS_HEADING } from './constants'; @@ -57,7 +57,7 @@ export const RoleMappings: React.FC = () => { const rolesEmptyState = ( ); @@ -66,7 +66,7 @@ export const RoleMappings: React.FC = () => {
initializeRoleMapping()} /> { @@ -40,7 +40,12 @@ export const EmptyState: React.FC = () => {

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.schema.empty.buttonLabel', { defaultMessage: 'Read the indexing schema guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx index 9a663e1372211..e5b0f2facedbd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SEARCH_UI_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; export const EmptyState: React.FC = () => ( (

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.searchUI.empty.buttonLabel', { defaultMessage: 'Read the Search UI guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx index d7398357a5e58..cfef71d34fb9f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/search_ui/search_ui.tsx @@ -12,7 +12,7 @@ import { useActions, useValues } from 'kea'; import { EuiText, EuiFlexItem, EuiFlexGroup, EuiSpacer, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { SEARCH_UI_DOCS_URL } from '../../routes'; +import { docLinks } from '../../../shared/doc_links'; import { EngineLogic, getEngineBreadcrumbs } from '../engine'; import { AppSearchPageTemplate } from '../layout'; @@ -63,7 +63,7 @@ export const SearchUI: React.FC = () => { defaultMessage="Use the fields below to generate a sample search experience built with Search UI. Use the sample to preview search results, or build upon it to create your own custom search experience. {link}." values={{ link: ( - + { defaultMessage: 'Log retention is determined by the ILM policies for your deployment.', })}
- + {i18n.translate('xpack.enterpriseSearch.appSearch.settings.logRetention.learnMore', { defaultMessage: 'Learn more about log retention for Enterprise Search.', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx index ef5e1dafa443f..ff7e8ce16c6d2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/synonyms/components/empty_state.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SYNONYMS_DOCS_URL } from '../../../routes'; +import { docLinks } from '../../../../shared/doc_links'; import { SynonymModal, SynonymIcon } from '.'; @@ -35,7 +35,7 @@ export const EmptyState: React.FC = () => {

} actions={ - + {i18n.translate('xpack.enterpriseSearch.appSearch.engine.synonyms.empty.buttonLabel', { defaultMessage: 'Read the synonyms guide', })} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 1a41004c882e3..128af5adacfad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -5,30 +5,6 @@ * 2.0. */ -import { docLinks } from '../shared/doc_links'; - -export const API_DOCS_URL = docLinks.appSearchApis; -export const API_CLIENTS_DOCS_URL = docLinks.appSearchApiClients; -export const API_KEYS_DOCS_URL = docLinks.appSearchApiKeys; -export const AUTHENTICATION_DOCS_URL = docLinks.appSearchAuthentication; -export const CRAWL_RULES_DOCS_URL = docLinks.appSearchCrawlRules; -export const CURATIONS_DOCS_URL = docLinks.appSearchCurations; -export const DOCS_URL = docLinks.appSearchGuide; -export const DUPLICATE_DOCS_URL = docLinks.appSearchDuplicateDocuments; -export const ENTRY_POINTS_DOCS_URL = docLinks.appSearchEntryPoints; -export const INDEXING_DOCS_URL = docLinks.appSearchIndexingDocs; -export const INDEXING_SCHEMA_DOCS_URL = docLinks.appSearchIndexingDocsSchema; -export const LOG_SETTINGS_DOCS_URL = docLinks.appSearchLogSettings; -export const META_ENGINES_DOCS_URL = docLinks.appSearchMetaEngines; -export const PRECISION_DOCS_URL = docLinks.appSearchPrecision; -export const RELEVANCE_DOCS_URL = docLinks.appSearchRelevance; -export const RESULT_SETTINGS_DOCS_URL = docLinks.appSearchResultSettings; -export const SEARCH_UI_DOCS_URL = docLinks.appSearchSearchUI; -export const SECURITY_DOCS_URL = docLinks.appSearchSecurity; -export const SYNONYMS_DOCS_URL = docLinks.appSearchSynonyms; -export const WEB_CRAWLER_DOCS_URL = docLinks.appSearchWebCrawler; -export const WEB_CRAWLER_LOG_DOCS_URL = docLinks.appSearchWebCrawlerEventLogs; - export const ROOT_PATH = '/'; export const SETUP_GUIDE_PATH = '/setup_guide'; export const LIBRARY_PATH = '/library'; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx index 9a93b43f6b751..7e23474b207f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/connectors/create_connector/start_step.tsx @@ -27,6 +27,7 @@ import { import { i18n } from '@kbn/i18n'; import * as Constants from '../../../../shared/constants'; +import { isValidIndexName } from '../../../utils/validate_index_name'; import { GeneratedConfigFields } from '../../connector_detail/components/generated_config_fields'; import { ConnectorViewLogic } from '../../connector_detail/connector_view_logic'; @@ -71,6 +72,18 @@ export const StartStep: React.FC = ({ setRawName(e.target.value); }; + const formError = isValidIndexName(rawName) + ? error + : i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.nameInputHelpText.lineOne', + { + defaultMessage: '{connectorName} is an invalid index name', + values: { + connectorName: rawName, + }, + } + ); + return ( @@ -100,6 +113,22 @@ export const StartStep: React.FC = ({ 'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.connectorNameLabel', { defaultMessage: 'Connector name' } )} + helpText={ + <> + + {formError} + + + {i18n.translate( + 'xpack.enterpriseSearch.startStep.namesShouldBeLowercaseTextLabel', + { + defaultMessage: + 'The connector name should be lowercase and cannot contain spaces or special characters.', + } + )} + + + } > = ({ 'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.descriptionLabel', { defaultMessage: 'Description' } )} + labelAppend={ + + {i18n.translate( + 'xpack.enterpriseSearch.createConnector.startStep.euiFormRow.descriptionLabelAppend', + { defaultMessage: 'Optional' } + )} + + } > = ({ hasShadow={false} hasBorder paddingSize="l" - color={selectedConnector?.name ? 'plain' : 'subdued'} + color={ + selectedConnector?.name && isValidIndexName(rawName) && !error ? 'plain' : 'subdued' + } > - +

{i18n.translate( 'xpack.enterpriseSearch.createConnector.startStep.h4.deploymentLabel', @@ -218,7 +263,10 @@ export const StartStep: React.FC = ({

- +

{i18n.translate( 'xpack.enterpriseSearch.createConnector.startStep.p.youWillStartTheLabel', @@ -242,7 +290,7 @@ export const StartStep: React.FC = ({ } }} fill - disabled={!canConfigureConnector} + disabled={!canConfigureConnector || !isValidIndexName(rawName) || Boolean(error)} isLoading={isCreateLoading || isGenerateLoading} > {Constants.NEXT_BUTTON_LABEL} @@ -252,12 +300,20 @@ export const StartStep: React.FC = ({ ) : ( - +

{i18n.translate( 'xpack.enterpriseSearch.createConnector.startStep.h4.configureIndexAndAPILabel', @@ -268,7 +324,14 @@ export const StartStep: React.FC = ({

- +

{i18n.translate( 'xpack.enterpriseSearch.createConnector.startStep.p.thisProcessWillCreateLabel', @@ -309,7 +372,9 @@ export const StartStep: React.FC = ({ = ({ diff --git a/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts b/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts index 5ca3da77d7eba..674e870c898bc 100644 --- a/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts +++ b/x-pack/plugins/entity_manager/server/lib/auth/privileges.ts @@ -59,7 +59,7 @@ export const entityDefinitionRuntimePrivileges = { index: [ { names: [ENTITY_INTERNAL_INDICES_PATTERN], - privileges: ['create_index', 'index', 'create_doc', 'auto_configure', 'read'], + privileges: ['create_index', 'delete_index', 'index', 'create_doc', 'auto_configure', 'read'], }, { names: [...BUILT_IN_ALLOWED_INDICES, ENTITY_INTERNAL_INDICES_PATTERN], diff --git a/x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts b/x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts index d6aa4d08ad221..8be37435ca0cd 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/built_in/services_from_ecs_data.ts @@ -29,7 +29,7 @@ export const builtInServicesFromEcsEntityDefinition: EntityDefinition = identityFields: ['service.name'], displayNameTemplate: '{{service.name}}', metadata: [ - { source: '_index', destination: 'sourceIndex' }, + { source: '_index', destination: 'source_index' }, { source: 'data_stream.type', destination: 'source_data_stream.type', @@ -38,7 +38,7 @@ export const builtInServicesFromEcsEntityDefinition: EntityDefinition = source: 'data_stream.dataset', destination: 'source_data_stream.dataset', }, - { source: 'agent.name', aggregation: { type: 'terms', limit: 100 } }, + 'agent.name', 'service.environment', 'service.name', 'service.namespace', diff --git a/x-pack/plugins/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts b/x-pack/plugins/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts index 0b3900363c0c8..5511e50c36ab8 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/create_and_install_ingest_pipeline.ts @@ -25,7 +25,7 @@ export async function createAndInstallIngestPipelines( id: latestId, processors: latestProcessors, _meta: { - definitionVersion: definition.version, + definition_version: definition.version, managed: definition.managed, }, }), diff --git a/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts b/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts index d40d9975a8820..491cd08e6d48c 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/delete_index.ts @@ -14,11 +14,13 @@ export async function deleteIndices( definition: EntityDefinition, logger: Logger ) { + const index = generateLatestIndexName(definition); try { - const index = generateLatestIndexName(definition); await esClient.indices.delete({ index, ignore_unavailable: true }); } catch (e) { - logger.error(`Unable to remove entity definition index [${definition.id}}]`); + logger.error( + `Unable to remove entity definition index ${index} for definition [${definition.id}]` + ); throw e; } } diff --git a/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap index 37f600f3a271f..429b5fdce9309 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap +++ b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/__snapshots__/generate_latest_processors.test.ts.snap @@ -16,25 +16,25 @@ Array [ }, Object { "set": Object { - "field": "entity.definitionId", + "field": "entity.definition_id", "value": "builtin_mock_entity_definition", }, }, Object { "set": Object { - "field": "entity.definitionVersion", + "field": "entity.definition_version", "value": "1.0.0", }, }, Object { "set": Object { - "field": "entity.schemaVersion", + "field": "entity.schema_version", "value": "v1", }, }, Object { "set": Object { - "field": "entity.identityFields", + "field": "entity.identity_fields", "value": Array [ "log.logger", ], @@ -92,7 +92,7 @@ if (ctx.entity?.metadata?.sourceIndex?.data != null) { }, Object { "set": Object { - "field": "entity.displayName", + "field": "entity.display_name", "value": "{{log.logger}}", }, }, @@ -121,25 +121,25 @@ Array [ }, Object { "set": Object { - "field": "entity.definitionId", + "field": "entity.definition_id", "value": "admin-console-services", }, }, Object { "set": Object { - "field": "entity.definitionVersion", + "field": "entity.definition_version", "value": "1.0.0", }, }, Object { "set": Object { - "field": "entity.schemaVersion", + "field": "entity.schema_version", "value": "v1", }, }, Object { "set": Object { - "field": "entity.identityFields", + "field": "entity.identity_fields", "value": Array [ "log.logger", ], @@ -197,7 +197,7 @@ if (ctx.entity?.metadata?.sourceIndex?.data != null) { }, Object { "set": Object { - "field": "entity.displayName", + "field": "entity.display_name", "value": "{{log.logger}}", }, }, diff --git a/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts index 787633246dede..335c135448b10 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/ingest_pipeline/generate_latest_processors.ts @@ -117,25 +117,25 @@ export function generateLatestProcessors(definition: EntityDefinition) { }, { set: { - field: 'entity.definitionId', + field: 'entity.definition_id', value: definition.id, }, }, { set: { - field: 'entity.definitionVersion', + field: 'entity.definition_version', value: definition.version, }, }, { set: { - field: 'entity.schemaVersion', + field: 'entity.schema_version', value: ENTITY_SCHEMA_VERSION_V1, }, }, { set: { - field: 'entity.identityFields', + field: 'entity.identity_fields', value: definition.identityFields.map((identityField) => identityField.field), }, }, @@ -173,7 +173,7 @@ export function generateLatestProcessors(definition: EntityDefinition) { // This must happen AFTER we lift the identity fields into the root of the document { set: { - field: 'entity.displayName', + field: 'entity.display_name', value: definition.displayNameTemplate, }, }, diff --git a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts index e07670c58fd9b..4633885b51387 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.test.ts @@ -74,7 +74,7 @@ const assertHasCreatedDefinition = ( id: generateLatestIngestPipelineId(definition), processors: expect.anything(), _meta: { - definitionVersion: definition.version, + definition_version: definition.version, managed: definition.managed, }, }); @@ -112,7 +112,7 @@ const assertHasUpgradedDefinition = ( id: generateLatestIngestPipelineId(definition), processors: expect.anything(), _meta: { - definitionVersion: definition.version, + definition_version: definition.version, managed: definition.managed, }, }); @@ -260,7 +260,7 @@ describe('install_entity_definition', () => { describe('installBuiltInEntityDefinitions', () => { it('should install definition when not found', async () => { const builtInDefinitions = [mockEntityDefinition]; - const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; + const clusterClient = elasticsearchClientMock.createScopedClusterClient(); const soClient = savedObjectsClientMock.create(); soClient.find.mockResolvedValue({ saved_objects: [], total: 0, page: 1, per_page: 10 }); soClient.update.mockResolvedValue({ @@ -271,18 +271,19 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, definitions: builtInDefinitions, logger: loggerMock.create(), }); - assertHasCreatedDefinition(mockEntityDefinition, soClient, esClient); + assertHasCreatedDefinition(mockEntityDefinition, soClient, clusterClient.asSecondaryAuthUser); }); it('should reinstall when partial state found', async () => { const builtInDefinitions = [mockEntityDefinition]; - const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; + const clusterClient = elasticsearchClientMock.createScopedClusterClient(); + const esClient = clusterClient.asInternalUser; // mock partially installed definition esClient.ingest.getPipeline.mockResolvedValue({}); esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); @@ -314,14 +315,18 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, definitions: builtInDefinitions, logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, esClient); - assertHasUpgradedDefinition(mockEntityDefinition, soClient, esClient); + assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); + assertHasUpgradedDefinition( + mockEntityDefinition, + soClient, + clusterClient.asSecondaryAuthUser + ); }); it('should reinstall when outdated version', async () => { @@ -329,7 +334,8 @@ describe('install_entity_definition', () => { ...mockEntityDefinition, version: semver.inc(mockEntityDefinition.version, 'major') ?? '0.0.0', }; - const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; + const clusterClient = elasticsearchClientMock.createScopedClusterClient(); + const esClient = clusterClient.asInternalUser; esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); const soClient = savedObjectsClientMock.create(); @@ -359,14 +365,14 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, definitions: [updatedDefinition], logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, esClient); - assertHasUpgradedDefinition(updatedDefinition, soClient, esClient); + assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); + assertHasUpgradedDefinition(updatedDefinition, soClient, clusterClient.asSecondaryAuthUser); }); it('should reinstall when stale upgrade', async () => { @@ -374,7 +380,8 @@ describe('install_entity_definition', () => { ...mockEntityDefinition, version: semver.inc(mockEntityDefinition.version, 'major') ?? '0.0.0', }; - const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; + const clusterClient = elasticsearchClientMock.createScopedClusterClient(); + const esClient = clusterClient.asInternalUser; esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); const soClient = savedObjectsClientMock.create(); @@ -406,18 +413,19 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, definitions: [updatedDefinition], logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, esClient); - assertHasUpgradedDefinition(updatedDefinition, soClient, esClient); + assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); + assertHasUpgradedDefinition(updatedDefinition, soClient, clusterClient.asSecondaryAuthUser); }); it('should reinstall when failed installation', async () => { - const esClient = elasticsearchClientMock.createScopedClusterClient().asCurrentUser; + const clusterClient = elasticsearchClientMock.createScopedClusterClient(); + const esClient = clusterClient.asInternalUser; esClient.transform.getTransformStats.mockResolvedValue({ transforms: [], count: 0 }); const soClient = savedObjectsClientMock.create(); @@ -448,14 +456,18 @@ describe('install_entity_definition', () => { }); await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, definitions: [mockEntityDefinition], logger: loggerMock.create(), }); - assertHasDeletedTransforms(mockEntityDefinition, esClient); - assertHasUpgradedDefinition(mockEntityDefinition, soClient, esClient); + assertHasDeletedTransforms(mockEntityDefinition, clusterClient.asSecondaryAuthUser); + assertHasUpgradedDefinition( + mockEntityDefinition, + soClient, + clusterClient.asSecondaryAuthUser + ); }); }); }); diff --git a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts index b4adedaf10374..bfc37ac52e2c1 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/install_entity_definition.ts @@ -6,7 +6,7 @@ */ import semver from 'semver'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient, IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { EntityDefinition, EntityDefinitionUpdate } from '@kbn/entities-schema'; import { Logger } from '@kbn/logging'; @@ -29,6 +29,7 @@ import { mergeEntityDefinitionUpdate } from './helpers/merge_definition_update'; import { EntityDefinitionWithState } from './types'; import { stopLatestTransform, stopTransforms } from './stop_transforms'; import { deleteLatestTransform, deleteTransforms } from './delete_transforms'; +import { deleteIndices } from './delete_index'; export interface InstallDefinitionParams { esClient: ElasticsearchClient; @@ -49,10 +50,7 @@ export async function installEntityDefinition({ validateDefinitionCanCreateValidTransformIds(definition); if (await entityDefinitionExists(soClient, definition.id)) { - throw new EntityIdConflict( - `Entity definition with [${definition.id}] already exists.`, - definition - ); + throw new EntityIdConflict(`Entity definition [${definition.id}] already exists.`, definition); } try { @@ -65,7 +63,7 @@ export async function installEntityDefinition({ return await install({ esClient, soClient, logger, definition: entityDefinition }); } catch (e) { - logger.error(`Failed to install entity definition ${definition.id}: ${e}`); + logger.error(`Failed to install entity definition [${definition.id}]: ${e}`); await stopLatestTransform(esClient, definition, logger); await deleteLatestTransform(esClient, definition, logger); @@ -90,28 +88,32 @@ export async function installEntityDefinition({ } export async function installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, logger, definitions, -}: Omit & { +}: Omit & { + clusterClient: IScopedClusterClient; definitions: EntityDefinition[]; }): Promise { if (definitions.length === 0) return []; - logger.debug(`Starting installation of ${definitions.length} built-in definitions`); + logger.info(`Checking installation of ${definitions.length} built-in definitions`); const installPromises = definitions.map(async (builtInDefinition) => { const installedDefinition = await findEntityDefinitionById({ - esClient, soClient, + esClient: clusterClient.asInternalUser, id: builtInDefinition.id, includeState: true, }); if (!installedDefinition) { + // clean data from previous installation + await deleteIndices(clusterClient.asCurrentUser, builtInDefinition, logger); + return await installEntityDefinition({ definition: builtInDefinition, - esClient, + esClient: clusterClient.asSecondaryAuthUser, soClient, logger, }); @@ -127,15 +129,16 @@ export async function installBuiltInEntityDefinitions({ return installedDefinition; } - logger.debug( + logger.info( `Detected failed or outdated installation of definition [${installedDefinition.id}] v${installedDefinition.version}, installing v${builtInDefinition.version}` ); return await reinstallEntityDefinition({ soClient, - esClient, + clusterClient, logger, definition: installedDefinition, definitionUpdate: builtInDefinition, + deleteData: true, }); }); @@ -150,22 +153,16 @@ async function install({ definition, logger, }: InstallDefinitionParams): Promise { - logger.debug( - () => - `Installing definition ${definition.id} v${definition.version}\n${JSON.stringify( - definition, - null, - 2 - )}` - ); + logger.info(`Installing definition [${definition.id}] v${definition.version}`); + logger.debug(() => JSON.stringify(definition, null, 2)); - logger.debug(`Installing index templates for definition ${definition.id}`); + logger.debug(`Installing index templates for definition [${definition.id}]`); const templates = await createAndInstallTemplates(esClient, definition, logger); - logger.debug(`Installing ingest pipelines for definition ${definition.id}`); + logger.debug(`Installing ingest pipelines for definition [${definition.id}]`); const pipelines = await createAndInstallIngestPipelines(esClient, definition, logger); - logger.debug(`Installing transforms for definition ${definition.id}`); + logger.debug(`Installing transforms for definition [${definition.id}]`); const transforms = await createAndInstallTransforms(esClient, definition, logger); const updatedProps = await updateEntityDefinition(soClient, definition.id, { @@ -177,20 +174,23 @@ async function install({ // stop and delete the current transforms and reinstall all the components export async function reinstallEntityDefinition({ - esClient, + clusterClient, soClient, definition, definitionUpdate, logger, -}: InstallDefinitionParams & { + deleteData = false, +}: Omit & { + clusterClient: IScopedClusterClient; definitionUpdate: EntityDefinitionUpdate; + deleteData?: boolean; }): Promise { try { const updatedDefinition = mergeEntityDefinitionUpdate(definition, definitionUpdate); logger.debug( () => - `Reinstalling definition ${definition.id} from v${definition.version} to v${ + `Reinstalling definition [${definition.id}] from v${definition.version} to v${ definitionUpdate.version }\n${JSON.stringify(updatedDefinition, null, 2)}` ); @@ -201,13 +201,17 @@ export async function reinstallEntityDefinition({ installStartedAt: new Date().toISOString(), }); - logger.debug(`Deleting transforms for definition ${definition.id} v${definition.version}`); - await stopAndDeleteTransforms(esClient, definition, logger); + logger.debug(`Deleting transforms for definition [${definition.id}] v${definition.version}`); + await stopAndDeleteTransforms(clusterClient.asSecondaryAuthUser, definition, logger); + + if (deleteData) { + await deleteIndices(clusterClient.asCurrentUser, definition, logger); + } return await install({ - esClient, soClient, logger, + esClient: clusterClient.asSecondaryAuthUser, definition: updatedDefinition, }); } catch (err) { diff --git a/x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap b/x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap index 94303584c45dc..86978b1b4df95 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap +++ b/x-pack/plugins/entity_manager/server/lib/entities/transform/__snapshots__/generate_latest_transform.test.ts.snap @@ -3,7 +3,7 @@ exports[`generateLatestTransform(definition) should generate a valid latest transform 1`] = ` Object { "_meta": Object { - "definitionVersion": "1.0.0", + "definition_version": "1.0.0", "managed": false, }, "defer_validation": true, @@ -42,7 +42,7 @@ Object { }, }, }, - "entity.lastSeenTimestamp": Object { + "entity.last_seen_timestamp": Object { "max": Object { "field": "@timestamp", }, diff --git a/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.ts b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.ts index c273469e3d3e3..c9d8cd9deef9b 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/transform/generate_latest_transform.ts @@ -69,7 +69,7 @@ const generateTransformPutRequest = ({ return { transform_id: transformId, _meta: { - definitionVersion: definition.version, + definition_version: definition.version, managed: definition.managed, }, defer_validation: true, @@ -113,7 +113,7 @@ const generateTransformPutRequest = ({ aggs: { ...generateLatestMetricAggregations(definition), ...generateLatestMetadataAggregations(definition), - 'entity.lastSeenTimestamp': { + 'entity.last_seen_timestamp': { max: { field: definition.latest.timestampField, }, diff --git a/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts b/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts index a4d44cd45ee17..66bc6da96619b 100644 --- a/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts +++ b/x-pack/plugins/entity_manager/server/lib/entities/upgrade_entity_definition.ts @@ -35,18 +35,20 @@ export async function upgradeBuiltInEntityDefinitions({ ); } - const { esClient, soClient } = getClientsFromAPIKey({ apiKey, server }); + const { clusterClient, soClient } = getClientsFromAPIKey({ apiKey, server }); logger.debug(`Starting built-in definitions upgrade`); const upgradedDefinitions = await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, definitions, logger, }); await Promise.all( - upgradedDefinitions.map((definition) => startTransforms(esClient, definition, logger)) + upgradedDefinitions.map((definition) => + startTransforms(clusterClient.asSecondaryAuthUser, definition, logger) + ) ); return { success: true, definitions: upgradedDefinitions }; diff --git a/x-pack/plugins/entity_manager/server/lib/entity_client.ts b/x-pack/plugins/entity_manager/server/lib/entity_client.ts index 7e930d29bba73..babcbf35197e8 100644 --- a/x-pack/plugins/entity_manager/server/lib/entity_client.ts +++ b/x-pack/plugins/entity_manager/server/lib/entity_client.ts @@ -40,7 +40,11 @@ export class EntityClient { definition: EntityDefinition; installOnly?: boolean; }) { + this.options.logger.info( + `Creating definition [${definition.id}] v${definition.version} (installOnly=${installOnly})` + ); const secondaryAuthClient = this.options.clusterClient.asSecondaryAuthUser; + const installedDefinition = await installEntityDefinition({ definition, esClient: secondaryAuthClient, @@ -62,16 +66,15 @@ export class EntityClient { id: string; definitionUpdate: EntityDefinitionUpdate; }) { - const secondaryAuthClient = this.options.clusterClient.asSecondaryAuthUser; const definition = await findEntityDefinitionById({ id, soClient: this.options.soClient, - esClient: secondaryAuthClient, + esClient: this.options.clusterClient.asInternalUser, includeState: true, }); if (!definition) { - const message = `Unable to find entity definition with [${id}]`; + const message = `Unable to find entity definition [${id}]`; this.options.logger.error(message); throw new EntityDefinitionNotFound(message); } @@ -86,25 +89,31 @@ export class EntityClient { definition as EntityDefinitionWithState ).state.components.transforms.some((transform) => transform.running); + this.options.logger.info( + `Updating definition [${definition.id}] from v${definition.version} to v${definitionUpdate.version}` + ); const updatedDefinition = await reinstallEntityDefinition({ definition, definitionUpdate, soClient: this.options.soClient, - esClient: secondaryAuthClient, + clusterClient: this.options.clusterClient, logger: this.options.logger, }); if (shouldRestartTransforms) { - await startTransforms(secondaryAuthClient, updatedDefinition, this.options.logger); + await startTransforms( + this.options.clusterClient.asSecondaryAuthUser, + updatedDefinition, + this.options.logger + ); } return updatedDefinition; } async deleteEntityDefinition({ id, deleteData = false }: { id: string; deleteData?: boolean }) { - const secondaryAuthClient = this.options.clusterClient.asSecondaryAuthUser; const definition = await findEntityDefinitionById({ id, - esClient: secondaryAuthClient, + esClient: this.options.clusterClient.asInternalUser, soClient: this.options.soClient, }); @@ -112,9 +121,12 @@ export class EntityClient { throw new EntityDefinitionNotFound(`Unable to find entity definition with [${id}]`); } + this.options.logger.info( + `Uninstalling definition [${definition.id}] v${definition.version} (deleteData=${deleteData})` + ); await uninstallEntityDefinition({ definition, - esClient: secondaryAuthClient, + esClient: this.options.clusterClient.asSecondaryAuthUser, soClient: this.options.soClient, logger: this.options.logger, }); @@ -146,7 +158,7 @@ export class EntityClient { builtIn?: boolean; }) { const definitions = await findEntityDefinitions({ - esClient: this.options.clusterClient.asSecondaryAuthUser, + esClient: this.options.clusterClient.asInternalUser, soClient: this.options.soClient, page, perPage, @@ -160,6 +172,7 @@ export class EntityClient { } async startEntityDefinition(definition: EntityDefinition) { + this.options.logger.info(`Starting transforms for definition [${definition.id}]`); return startTransforms( this.options.clusterClient.asSecondaryAuthUser, definition, @@ -168,6 +181,7 @@ export class EntityClient { } async stopEntityDefinition(definition: EntityDefinition) { + this.options.logger.info(`Stopping transforms for definition [${definition.id}]`); return stopTransforms( this.options.clusterClient.asSecondaryAuthUser, definition, diff --git a/x-pack/plugins/entity_manager/server/lib/manage_index_templates.ts b/x-pack/plugins/entity_manager/server/lib/manage_index_templates.ts index ffa58cd9c0145..704c240e0c424 100644 --- a/x-pack/plugins/entity_manager/server/lib/manage_index_templates.ts +++ b/x-pack/plugins/entity_manager/server/lib/manage_index_templates.ts @@ -11,7 +11,6 @@ import { IndicesPutIndexTemplateRequest, } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { entitiesHistoryBaseComponentTemplateConfig } from '../templates/components/base_history'; import { entitiesLatestBaseComponentTemplateConfig } from '../templates/components/base_latest'; import { entitiesEntityComponentTemplateConfig } from '../templates/components/entity'; import { entitiesEventComponentTemplateConfig } from '../templates/components/event'; @@ -38,11 +37,6 @@ export const installEntityManagerTemplates = async ({ logger: Logger; }) => { await Promise.all([ - upsertComponent({ - esClient, - logger, - component: entitiesHistoryBaseComponentTemplateConfig, - }), upsertComponent({ esClient, logger, diff --git a/x-pack/plugins/entity_manager/server/lib/utils.ts b/x-pack/plugins/entity_manager/server/lib/utils.ts index aec8ffa940437..dfe14dcf20abd 100644 --- a/x-pack/plugins/entity_manager/server/lib/utils.ts +++ b/x-pack/plugins/entity_manager/server/lib/utils.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { getFakeKibanaRequest } from '@kbn/security-plugin/server/authentication/api_keys/fake_kibana_request'; import { EntityManagerServerSetup } from '../types'; @@ -17,9 +17,9 @@ export const getClientsFromAPIKey = ({ }: { apiKey: EntityDiscoveryAPIKey; server: EntityManagerServerSetup; -}): { esClient: ElasticsearchClient; soClient: SavedObjectsClientContract } => { +}): { clusterClient: IScopedClusterClient; soClient: SavedObjectsClientContract } => { const fakeRequest = getFakeKibanaRequest({ id: apiKey.id, api_key: apiKey.apiKey }); - const esClient = server.core.elasticsearch.client.asScoped(fakeRequest).asSecondaryAuthUser; + const clusterClient = server.core.elasticsearch.client.asScoped(fakeRequest); const soClient = server.core.savedObjects.getScopedClient(fakeRequest); - return { esClient, soClient }; + return { clusterClient, soClient }; }; diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/check.ts b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts index d0e2a572cb6f5..1c67643c5c902 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/check.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/check.ts @@ -61,13 +61,13 @@ export const checkEntityDiscoveryEnabledRoute = createEntityManagerServerRoute({ return response.ok({ body: { enabled: false, reason: ERROR_API_KEY_NOT_VALID } }); } - const { esClient, soClient } = getClientsFromAPIKey({ apiKey, server }); + const { clusterClient, soClient } = getClientsFromAPIKey({ apiKey, server }); const entityDiscoveryState = await Promise.all( builtInDefinitions.map(async (builtInDefinition) => { const definitions = await findEntityDefinitions({ - esClient, soClient, + esClient: clusterClient.asSecondaryAuthUser, id: builtInDefinition.id, includeState: true, }); diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts index f8629fe46497b..01208fe19d7a0 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/disable.ts @@ -67,12 +67,13 @@ export const disableEntityDiscoveryRoute = createEntityManagerServerRoute({ includedHiddenTypes: [EntityDiscoveryApiKeyType.name], }); + logger.info('Disabling managed entity discovery'); await uninstallBuiltInEntityDefinitions({ entityClient, deleteData: params.query.deleteData, }); - server.logger.debug('reading entity discovery API key from saved object'); + logger.debug('reading entity discovery API key from saved object'); const apiKey = await readEntityDiscoveryAPIKey(server); // api key could be deleted outside of the apis, it does not affect the // disablement flow @@ -82,6 +83,7 @@ export const disableEntityDiscoveryRoute = createEntityManagerServerRoute({ ids: [apiKey.id], }); } + logger.info('Managed entity discovery is disabled'); return response.ok({ body: { success: true } }); } catch (err) { diff --git a/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts index 1002c1e716df2..9a851e08b5673 100644 --- a/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts +++ b/x-pack/plugins/entity_manager/server/routes/enablement/enable.ts @@ -93,6 +93,7 @@ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ }); } + logger.info(`Enabling managed entity discovery (installOnly=${params.query.installOnly})`); const soClient = core.savedObjects.getClient({ includedHiddenTypes: [EntityDiscoveryApiKeyType.name], }); @@ -119,9 +120,9 @@ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ await saveEntityDiscoveryAPIKey(soClient, apiKey); - const esClient = core.elasticsearch.client.asSecondaryAuthUser; + const clusterClient = core.elasticsearch.client; const installedDefinitions = await installBuiltInEntityDefinitions({ - esClient, + clusterClient, soClient, logger, definitions: builtInDefinitions, @@ -130,10 +131,11 @@ export const enableEntityDiscoveryRoute = createEntityManagerServerRoute({ if (!params.query.installOnly) { await Promise.all( installedDefinitions.map((installedDefinition) => - startTransforms(esClient, installedDefinition, logger) + startTransforms(clusterClient.asSecondaryAuthUser, installedDefinition, logger) ) ); } + logger.info('Managed entity discovery is enabled'); return response.ok({ body: { success: true } }); } catch (err) { diff --git a/x-pack/plugins/entity_manager/server/routes/entities/update.ts b/x-pack/plugins/entity_manager/server/routes/entities/update.ts index 9cf72a9298d42..3b7ea8a03830d 100644 --- a/x-pack/plugins/entity_manager/server/routes/entities/update.ts +++ b/x-pack/plugins/entity_manager/server/routes/entities/update.ts @@ -5,21 +5,13 @@ * 2.0. */ -import { - createEntityDefinitionQuerySchema, - entityDefinitionUpdateSchema, -} from '@kbn/entities-schema'; +import { entityDefinitionUpdateSchema } from '@kbn/entities-schema'; import { z } from '@kbn/zod'; import { EntitySecurityException } from '../../lib/entities/errors/entity_security_exception'; import { InvalidTransformError } from '../../lib/entities/errors/invalid_transform_error'; -import { findEntityDefinitionById } from '../../lib/entities/find_entity_definition'; -import { startTransforms } from '../../lib/entities/start_transforms'; -import { - installationInProgress, - reinstallEntityDefinition, -} from '../../lib/entities/install_entity_definition'; - import { createEntityManagerServerRoute } from '../create_entity_manager_server_route'; +import { EntityDefinitionNotFound } from '../../lib/entities/errors/entity_not_found'; +import { EntityDefinitionUpdateConflict } from '../../lib/entities/errors/entity_definition_update_conflict'; /** * @openapi @@ -29,13 +21,12 @@ import { createEntityManagerServerRoute } from '../create_entity_manager_server_ * tags: * - definitions * parameters: - * - in: query - * name: installOnly - * description: If true, the definition transforms will not be started - * required: false + * - in: path + * name: id + * description: The entity definition ID * schema: - * type: boolean - * default: false + * type: string + * required: true * requestBody: * description: The definition properties to update * required: true @@ -63,58 +54,37 @@ export const updateEntityDefinitionRoute = createEntityManagerServerRoute({ endpoint: 'PATCH /internal/entities/definition/{id}', params: z.object({ path: z.object({ id: z.string() }), - query: createEntityDefinitionQuerySchema, body: entityDefinitionUpdateSchema, }), - handler: async ({ context, response, params, logger }) => { - const core = await context.core; - const soClient = core.savedObjects.client; - const esClient = core.elasticsearch.client.asCurrentUser; + handler: async ({ request, response, params, logger, getScopedClient }) => { + const entityClient = await getScopedClient({ request }); try { - const installedDefinition = await findEntityDefinitionById({ - soClient, - esClient, + const updatedDefinition = await entityClient.updateEntityDefinition({ id: params.path.id, + definitionUpdate: params.body, }); - if (!installedDefinition) { + return response.ok({ body: updatedDefinition }); + } catch (e) { + logger.error(e); + + if (e instanceof EntityDefinitionNotFound) { return response.notFound({ body: { message: `Entity definition [${params.path.id}] not found` }, }); } - if (installedDefinition.managed) { - return response.forbidden({ - body: { message: `Managed definition cannot be modified` }, - }); - } - - if (installationInProgress(installedDefinition)) { + if (e instanceof EntityDefinitionUpdateConflict) { return response.conflict({ body: { message: `Entity definition [${params.path.id}] has changes in progress` }, }); } - const updatedDefinition = await reinstallEntityDefinition({ - soClient, - esClient, - logger, - definition: installedDefinition, - definitionUpdate: params.body, - }); - - if (!params.query.installOnly) { - await startTransforms(esClient, updatedDefinition, logger); - } - - return response.ok({ body: updatedDefinition }); - } catch (e) { - logger.error(e); - if (e instanceof EntitySecurityException || e instanceof InvalidTransformError) { return response.customError({ body: e, statusCode: 400 }); } + return response.customError({ body: e, statusCode: 500 }); } }, diff --git a/x-pack/plugins/entity_manager/server/templates/components/base_history.ts b/x-pack/plugins/entity_manager/server/templates/components/base_history.ts deleted file mode 100644 index d0bdaa76152ec..0000000000000 --- a/x-pack/plugins/entity_manager/server/templates/components/base_history.ts +++ /dev/null @@ -1,36 +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 { ClusterPutComponentTemplateRequest } from '@elastic/elasticsearch/lib/api/types'; -import { ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1 } from '../../../common/constants_entities'; - -export const entitiesHistoryBaseComponentTemplateConfig: ClusterPutComponentTemplateRequest = { - name: ENTITY_HISTORY_BASE_COMPONENT_TEMPLATE_V1, - _meta: { - description: - "Component template for the ECS fields used in the Elastic Entity Model's entity discovery framework's history data set", - documentation: 'https://www.elastic.co/guide/en/ecs/current/ecs-base.html', - ecs_version: '8.0.0', - managed: true, - }, - template: { - mappings: { - properties: { - '@timestamp': { - type: 'date', - }, - labels: { - type: 'object', - }, - tags: { - ignore_above: 1024, - type: 'keyword', - }, - }, - }, - }, -}; diff --git a/x-pack/plugins/entity_manager/server/templates/components/base_latest.ts b/x-pack/plugins/entity_manager/server/templates/components/base_latest.ts index 6ee48b37bff1d..ef97a6fda3a3b 100644 --- a/x-pack/plugins/entity_manager/server/templates/components/base_latest.ts +++ b/x-pack/plugins/entity_manager/server/templates/components/base_latest.ts @@ -22,7 +22,7 @@ export const entitiesLatestBaseComponentTemplateConfig: ClusterPutComponentTempl properties: { entity: { properties: { - displayName: { + display_name: { type: 'text', fields: { keyword: { @@ -31,9 +31,6 @@ export const entitiesLatestBaseComponentTemplateConfig: ClusterPutComponentTempl }, }, }, - firstSeenTimestamp: { - type: 'date', - }, }, }, labels: { diff --git a/x-pack/plugins/entity_manager/server/templates/components/entity.ts b/x-pack/plugins/entity_manager/server/templates/components/entity.ts index db93d0db77abf..fbd57ba66fc97 100644 --- a/x-pack/plugins/entity_manager/server/templates/components/entity.ts +++ b/x-pack/plugins/entity_manager/server/templates/components/entity.ts @@ -29,22 +29,22 @@ export const entitiesEntityComponentTemplateConfig: ClusterPutComponentTemplateR ignore_above: 1024, type: 'keyword', }, - definitionId: { + definition_id: { ignore_above: 1024, type: 'keyword', }, - definitionVersion: { + definition_version: { ignore_above: 1024, type: 'keyword', }, - schemaVersion: { + schema_version: { ignore_above: 1024, type: 'keyword', }, - lastSeenTimestamp: { + last_seen_timestamp: { type: 'date', }, - identityFields: { + identity_fields: { type: 'keyword', }, }, diff --git a/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts b/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts index 7fcc1e71e1808..51d1882084d3e 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts +++ b/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts @@ -25,6 +25,24 @@ function getInvalidJobIds(jobs: MlJobWithTimeRange[], ids: string[]) { }); } +// This is useful when redirecting from dashboards where groupIds are treated as jobIds +const getJobIdsFromGroups = (jobIds: string[], jobs: MlJobWithTimeRange[]) => { + const result = new Set(); + + jobIds.forEach((id) => { + const jobsInGroup = jobs.filter((job) => job.groups?.includes(id)); + + if (jobsInGroup.length > 0) { + jobsInGroup.forEach((job) => result.add(job.job_id)); + } else { + // If it's not a group ID, keep it (regardless of whether it's valid or not) + result.add(id); + } + }); + + return Array.from(result); +}; + export interface JobSelection { jobIds: string[]; selectedGroups: string[]; @@ -37,9 +55,9 @@ export const useJobSelection = (jobs: MlJobWithTimeRange[]) => { const getJobSelection = useJobSelectionFlyout(); const tmpIds = useMemo(() => { - const ids = globalState?.ml?.jobIds || []; + const ids = getJobIdsFromGroups(globalState?.ml?.jobIds || [], jobs); return (typeof ids === 'string' ? [ids] : ids).map((id: string) => String(id)); - }, [globalState?.ml?.jobIds]); + }, [globalState?.ml?.jobIds, jobs]); const invalidIds = useMemo(() => { return getInvalidJobIds(jobs, tmpIds); diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 9dbdf6069aff6..a717995d4ee14 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -957,6 +957,14 @@ export const ModelsList: FC = ({ } }); + setItemIdToExpandedRowMap((prev) => { + const newMap = { ...prev }; + modelsToDelete.forEach((model) => { + delete newMap[model.model_id]; + }); + return newMap; + }); + setModelsToDelete([]); if (refreshList) { diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts index 5713c0a2b67fb..833e565ec00ef 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/types.ts @@ -26,7 +26,6 @@ export interface EntityLatestServiceRaw { interface Entity { id: string; - lastSeenTimestamp: string; - firstSeenTimestamp: string; - identityFields: string[]; + last_seen_timestamp: string; + identity_fields: string[]; } diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts index bb5c4f48b4125..e3dd0ef5e0d4e 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.test.ts @@ -20,9 +20,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['metrics', 'logs'] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, @@ -49,9 +48,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['foo'] }, entity: { - firstSeenTimestamp: '2024-03-05T10:34:40.810Z', - lastSeenTimestamp: '2024-03-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-03-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-1:env-service-1', }, }, @@ -63,9 +61,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['bar'] }, entity: { - firstSeenTimestamp: '2024-03-05T10:34:40.810Z', - lastSeenTimestamp: '2024-03-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-03-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'apm-only-1:synthtrace-env-2', }, }, @@ -77,9 +74,8 @@ describe('mergeEntities', () => { agent: { name: ['java'] }, source_data_stream: { type: ['baz'] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', }, }, @@ -91,9 +87,8 @@ describe('mergeEntities', () => { agent: { name: ['java'] }, source_data_stream: { type: ['baz'] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-2:env-service-3', }, }, @@ -127,9 +122,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['metrics', 'logs'] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, @@ -141,9 +135,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['metrics', 'logs'] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, @@ -155,9 +148,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['foo'] }, entity: { - firstSeenTimestamp: '2024-23-05T10:34:40.810Z', - lastSeenTimestamp: '2024-23-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-23-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-1:prod', }, }, @@ -183,9 +175,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: [] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name'], id: 'service-1:test', }, }, @@ -209,9 +200,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: [] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name'], id: 'service-1:test', }, }, @@ -222,9 +212,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: [] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name'], id: 'service-1:test', }, }, @@ -250,9 +239,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: [] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name'], id: 'service-1:test', }, }, @@ -276,9 +264,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: [] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name'], id: 'service-1:test', }, }, @@ -289,9 +276,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: [] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name'], id: 'service-1:test', }, }, @@ -318,9 +304,8 @@ describe('mergeEntities', () => { agent: { name: ['nodejs'] }, source_data_stream: { type: ['metrics'] }, entity: { - firstSeenTimestamp: '2024-06-05T10:34:40.810Z', - lastSeenTimestamp: '2024-06-05T10:34:40.810Z', - identityFields: ['service.name', 'service.environment'], + last_seen_timestamp: '2024-06-05T10:34:40.810Z', + identity_fields: ['service.name', 'service.environment'], id: 'service-1:test', }, }, diff --git a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts index e2ccc270b2a86..2f33c4728bd1a 100644 --- a/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts +++ b/x-pack/plugins/observability_solution/apm/server/routes/entities/utils/merge_entities.ts @@ -40,7 +40,7 @@ function mergeFunc(entity: EntityLatestServiceRaw, existingEntity?: MergedServic const commonEntityFields = { serviceName: entity.service.name, agentName: entity.agent.name[0], - lastSeenTimestamp: entity.entity.lastSeenTimestamp, + lastSeenTimestamp: entity.entity.last_seen_timestamp, }; if (!existingEntity) { diff --git a/x-pack/plugins/observability_solution/infra/public/apps/logs_app.tsx b/x-pack/plugins/observability_solution/infra/public/apps/logs_app.tsx index 329e059288e3e..9d5583b0ecf4c 100644 --- a/x-pack/plugins/observability_solution/infra/public/apps/logs_app.tsx +++ b/x-pack/plugins/observability_solution/infra/public/apps/logs_app.tsx @@ -6,13 +6,19 @@ */ import { History } from 'history'; -import { CoreStart } from '@kbn/core/public'; -import React from 'react'; +import { AppStatus, CoreStart } from '@kbn/core/public'; +import React, { useMemo } from 'react'; import ReactDOM from 'react-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import { AppMountParameters } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import { AllDatasetsLocatorParams, ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability'; +import { + AllDatasetsLocatorParams, + ALL_DATASETS_LOCATOR_ID, + OBSERVABILITY_LOGS_EXPLORER_APP_ID, +} from '@kbn/deeplinks-observability'; +import useObservable from 'react-use/lib/useObservable'; +import { map } from 'rxjs'; import { LinkToLogsPage } from '../pages/link_to/link_to_logs'; import { LogsPage } from '../pages/logs'; import { InfraClientStartDeps, InfraClientStartExports } from '../types'; @@ -57,7 +63,22 @@ const LogsApp: React.FC<{ storage: Storage; theme$: AppMountParameters['theme$']; }> = ({ core, history, pluginStart, plugins, setHeaderActionMenu, storage, theme$ }) => { - const { logs, discover, fleet } = core.application.capabilities; + const { logs } = core.application.capabilities; + + const isLogsExplorerAppAccessible = useObservable( + useMemo( + () => + core.application.applications$.pipe( + map( + (apps) => + (apps.get(OBSERVABILITY_LOGS_EXPLORER_APP_ID)?.status ?? AppStatus.inaccessible) === + AppStatus.accessible + ) + ), + [core.application.applications$] + ), + false + ); return ( @@ -74,7 +95,7 @@ const LogsApp: React.FC<{ toastsService={core.notifications.toasts} > - {Boolean(discover?.show && fleet?.read) && ( + {isLogsExplorerAppAccessible && ( { const { - services: { share }, + services: { share, application }, } = useKibanaContextForPlugin(); + const isLogsExplorerAppAccessible = useObservable( + useMemo( + () => + application.applications$.pipe( + map( + (apps) => + (apps.get(OBSERVABILITY_LOGS_EXPLORER_APP_ID)?.status ?? AppStatus.inaccessible) === + AppStatus.accessible + ) + ), + [application.applications$] + ), + false + ); + const { dismissalStorageKey, message } = pageConfigurations[page]; const [isDismissed, setDismissed] = useLocalStorage(dismissalStorageKey, false); - if (isDismissed) { + if (isDismissed || !isLogsExplorerAppAccessible) { return null; } + const allDatasetLocator = + share.url.locators.get(ALL_DATASETS_LOCATOR_ID); + return ( fill data-test-subj="infraLogsDeprecationCalloutTryLogsExplorerButton" color="warning" - {...getLogsExplorerLinkProps(share)} + {...getLogsExplorerLinkProps(allDatasetLocator!)} > {i18n.translate('xpack.infra.logsDeprecationCallout.tryLogsExplorerButtonLabel', { defaultMessage: 'Try Logs Explorer', @@ -81,9 +107,7 @@ export const LogsDeprecationCallout = ({ page }: LogsDeprecationCalloutProps) => ); }; -const getLogsExplorerLinkProps = (share: SharePublicStart) => { - const locator = share.url.locators.get(ALL_DATASETS_LOCATOR_ID)!; - +const getLogsExplorerLinkProps = (locator: LocatorPublic) => { return getRouterLinkProps({ href: locator.getRedirectUrl({}), onClick: () => locator.navigate({}), diff --git a/x-pack/plugins/observability_solution/infra/public/plugin.ts b/x-pack/plugins/observability_solution/infra/public/plugin.ts index daaa3510e1660..7b32df012a192 100644 --- a/x-pack/plugins/observability_solution/infra/public/plugin.ts +++ b/x-pack/plugins/observability_solution/infra/public/plugin.ts @@ -13,6 +13,8 @@ import { DEFAULT_APP_CATEGORIES, PluginInitializerContext, AppDeepLinkLocations, + AppStatus, + ApplicationStart, } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { enableInfrastructureHostsView } from '@kbn/observability-plugin/public'; @@ -21,7 +23,7 @@ import { MetricsExplorerLocatorParams, ObservabilityTriggerId, } from '@kbn/observability-shared-plugin/common'; -import { BehaviorSubject, combineLatest, from } from 'rxjs'; +import { BehaviorSubject, combineLatest, distinctUntilChanged, from, of, switchMap } from 'rxjs'; import { map } from 'rxjs'; import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; import { apiCanAddNewPanel } from '@kbn/presentation-containers'; @@ -35,6 +37,7 @@ import { } from '@kbn/observability-shared-plugin/common'; import { OBSERVABILITY_ENABLE_LOGS_STREAM } from '@kbn/management-settings-ids'; import { NavigationEntry } from '@kbn/observability-shared-plugin/public'; +import { OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability/constants'; import type { InfraPublicConfig } from '../common/plugin_config_types'; import { createInventoryMetricRuleType } from './alerting/inventory'; import { createLogThresholdRuleType } from './alerting/log_threshold'; @@ -131,76 +134,75 @@ export class Plugin implements InfraClientPluginClass { messageFields: this.config.sources?.default?.fields?.message, }); - const startDep$AndHostViewFlag$ = combineLatest([ + const startDep$AndAccessibleFlag$ = combineLatest([ from(core.getStartServices()), core.settings.client.get$(enableInfrastructureHostsView), - ]); + ]).pipe( + switchMap(([[{ application }], isInfrastructureHostsViewEnabled]) => + combineLatest([ + of(application), + of(isInfrastructureHostsViewEnabled), + getLogsExplorerAccessible$(application), + ]) + ) + ); const logRoutes = getLogsAppRoutes({ isLogsStreamEnabled }); /** !! Need to be kept in sync with the deepLinks in x-pack/plugins/observability_solution/infra/public/plugin.ts */ pluginsSetup.observabilityShared.navigation.registerSections( - startDep$AndHostViewFlag$.pipe( - map( - ([ - [ - { - application: { capabilities }, - }, - ], - isInfrastructureHostsViewEnabled, - ]) => { - const { infrastructure, logs } = capabilities; - return [ - ...(logs.show - ? [ - { - label: logsTitle, - sortKey: 200, - entries: getLogsNavigationEntries({ - capabilities, - config: this.config, - routes: logRoutes, - }), - }, - ] - : []), - ...(infrastructure.show - ? [ - { - label: metricsTitle, - sortKey: 300, - entries: [ - { - label: inventoryTitle, - app: 'metrics', - path: '/inventory', - }, - ...(this.config.featureFlags.metricsExplorerEnabled - ? [ - { - label: metricsExplorerTitle, - app: 'metrics', - path: '/explorer', - }, - ] - : []), - ...(isInfrastructureHostsViewEnabled - ? [ - { - label: hostsTitle, - app: 'metrics', - path: '/hosts', - }, - ] - : []), - ], - }, - ] - : []), - ]; - } - ) + startDep$AndAccessibleFlag$.pipe( + map(([application, isInfrastructureHostsViewEnabled, isLogsExplorerAccessible]) => { + const { infrastructure, logs } = application.capabilities; + return [ + ...(logs.show + ? [ + { + label: logsTitle, + sortKey: 200, + entries: getLogsNavigationEntries({ + isLogsExplorerAccessible, + config: this.config, + routes: logRoutes, + }), + }, + ] + : []), + ...(infrastructure.show + ? [ + { + label: metricsTitle, + sortKey: 300, + entries: [ + { + label: inventoryTitle, + app: 'metrics', + path: '/inventory', + }, + ...(this.config.featureFlags.metricsExplorerEnabled + ? [ + { + label: metricsExplorerTitle, + app: 'metrics', + path: '/explorer', + }, + ] + : []), + ...(isInfrastructureHostsViewEnabled + ? [ + { + label: hostsTitle, + app: 'metrics', + path: '/hosts', + }, + ] + : []), + ], + }, + ] + : []), + ]; + }) ) ); @@ -333,9 +335,10 @@ export class Plugin implements InfraClientPluginClass { }, }); - startDep$AndHostViewFlag$.subscribe( - ([_startServices, isInfrastructureHostsViewEnabled]: [ - [CoreStart, InfraClientStartDeps, InfraClientStartExports], + startDep$AndAccessibleFlag$.subscribe( + ([_startServices, isInfrastructureHostsViewEnabled, _isLogsExplorerAccessible]: [ + ApplicationStart, + boolean, boolean ]) => { this.appUpdater$.next(() => ({ @@ -408,11 +411,11 @@ export class Plugin implements InfraClientPluginClass { } const getLogsNavigationEntries = ({ - capabilities, + isLogsExplorerAccessible, config, routes, }: { - capabilities: CoreStart['application']['capabilities']; + isLogsExplorerAccessible: boolean; config: InfraPublicConfig; routes: LogsAppRoutes; }) => { @@ -420,7 +423,7 @@ const getLogsNavigationEntries = ({ if (!config.featureFlags.logsUIEnabled) return entries; - if (capabilities.discover?.show && capabilities.fleet?.read) { + if (isLogsExplorerAccessible) { entries.push({ label: 'Explorer', app: 'observability-logs-explorer', @@ -440,6 +443,18 @@ const getLogsNavigationEntries = ({ return entries; }; +const getLogsExplorerAccessible$ = (application: CoreStart['application']) => { + const { applications$ } = application; + return applications$.pipe( + map( + (apps) => + (apps.get(OBSERVABILITY_LOGS_EXPLORER_APP_ID)?.status ?? AppStatus.inaccessible) === + AppStatus.accessible + ), + distinctUntilChanged() + ); +}; + const createNavEntryFromRoute = ({ path, title }: LogsRoute): NavigationEntry => ({ app: 'logs', label: title, diff --git a/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts index c4b48410456f8..b8d6219e6cd46 100644 --- a/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts +++ b/x-pack/plugins/observability_solution/inventory/common/utils/parse_identity_field_values_to_kql.test.ts @@ -26,7 +26,7 @@ describe('parseIdentityFieldValuesToKql', () => { it('should return the value when identityFields is a single string', () => { const entity: ServiceEntity = { 'agent.name': 'node', - 'entity.identityFields': 'service.name', + 'entity.identity_fields': 'service.name', 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, @@ -39,7 +39,7 @@ describe('parseIdentityFieldValuesToKql', () => { it('should return values when identityFields is an array of strings', () => { const entity: ServiceEntity = { 'agent.name': 'node', - 'entity.identityFields': ['service.name', 'service.environment'], + 'entity.identity_fields': ['service.name', 'service.environment'], 'service.name': 'my-service', 'entity.type': 'service', 'service.environment': 'staging', @@ -53,7 +53,7 @@ describe('parseIdentityFieldValuesToKql', () => { it('should return an empty string if identityFields is empty string', () => { const entity: ServiceEntity = { 'agent.name': 'node', - 'entity.identityFields': '', + 'entity.identity_fields': '', 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, @@ -65,7 +65,7 @@ describe('parseIdentityFieldValuesToKql', () => { it('should return an empty array if identityFields is empty array', () => { const entity: ServiceEntity = { 'agent.name': 'node', - 'entity.identityFields': [], + 'entity.identity_fields': [], 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, @@ -77,7 +77,7 @@ describe('parseIdentityFieldValuesToKql', () => { it('should ignore fields that are not present in the entity', () => { const entity: HostEntity = { - 'entity.identityFields': ['host.name', 'foo.bar'], + 'entity.identity_fields': ['host.name', 'foo.bar'], 'host.name': 'my-host', 'entity.type': 'host', 'cloud.provider': null, diff --git a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx index c60490c8a12b1..fc73e490d4d05 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/alerts_badge/alerts_badge.test.tsx @@ -28,13 +28,13 @@ describe('AlertsBadge', () => { it('render alerts badge for a host entity', () => { const entity: HostEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'host', - 'entity.displayName': 'foo', - 'entity.identityFields': 'host.name', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'host.name', 'host.name': 'foo', - 'entity.definitionId': 'host', + 'entity.definition_id': 'host', 'cloud.provider': null, alertsCount: 1, }; @@ -46,14 +46,14 @@ describe('AlertsBadge', () => { }); it('render alerts badge for a service entity', () => { const entity: ServiceEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'agent.name': 'node', 'entity.id': '1', 'entity.type': 'service', - 'entity.displayName': 'foo', - 'entity.identityFields': 'service.name', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'service.name', 'service.name': 'bar', - 'entity.definitionId': 'host', + 'entity.definition_id': 'host', 'cloud.provider': null, alertsCount: 5, }; @@ -65,15 +65,15 @@ describe('AlertsBadge', () => { }); it('render alerts badge for a service entity with multiple identity fields', () => { const entity: ServiceEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'agent.name': 'node', 'entity.id': '1', 'entity.type': 'service', - 'entity.displayName': 'foo', - 'entity.identityFields': ['service.name', 'service.environment'], + 'entity.display_name': 'foo', + 'entity.identity_fields': ['service.name', 'service.environment'], 'service.name': 'bar', 'service.environment': 'prod', - 'entity.definitionId': 'host', + 'entity.definition_id': 'host', 'cloud.provider': null, alertsCount: 2, }; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx index 36aad3d8e3a97..865e185eaa945 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/entity_name.test.tsx @@ -41,13 +41,13 @@ describe('EntityName', () => { it('returns host link', () => { const entity: HostEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'host', - 'entity.displayName': 'foo', - 'entity.identityFields': 'host.name', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'host.name', 'host.name': 'foo', - 'entity.definitionId': 'host', + 'entity.definition_id': 'host', 'cloud.provider': null, }; render(); @@ -59,13 +59,13 @@ describe('EntityName', () => { it('returns container link', () => { const entity: ContainerEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'container', - 'entity.displayName': 'foo', - 'entity.identityFields': 'container.id', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'container.id', 'container.id': 'foo', - 'entity.definitionId': 'container', + 'entity.definition_id': 'container', 'cloud.provider': null, }; render(); @@ -77,13 +77,13 @@ describe('EntityName', () => { it('returns service link without environment', () => { const entity: ServiceEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', - 'entity.displayName': 'foo', - 'entity.identityFields': 'service.name', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'service.name', 'service.name': 'foo', - 'entity.definitionId': 'service', + 'entity.definition_id': 'service', 'agent.name': 'bar', }; render(); @@ -95,13 +95,13 @@ describe('EntityName', () => { it('returns service link with environment', () => { const entity: ServiceEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', - 'entity.displayName': 'foo', - 'entity.identityFields': 'service.name', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'service.name', 'service.name': 'foo', - 'entity.definitionId': 'service', + 'entity.definition_id': 'service', 'agent.name': 'bar', 'service.environment': 'baz', }; @@ -114,13 +114,13 @@ describe('EntityName', () => { it('returns service link with first environment when it is an array', () => { const entity: ServiceEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', - 'entity.displayName': 'foo', - 'entity.identityFields': 'service.name', + 'entity.display_name': 'foo', + 'entity.identity_fields': 'service.name', 'service.name': 'foo', - 'entity.definitionId': 'service', + 'entity.definition_id': 'service', 'agent.name': 'bar', 'service.environment': ['baz', 'bar', 'foo'], }; @@ -133,13 +133,13 @@ describe('EntityName', () => { it('returns service link identity fields is an array', () => { const entity: ServiceEntity = { - 'entity.lastSeenTimestamp': 'foo', + 'entity.last_seen_timestamp': 'foo', 'entity.id': '1', 'entity.type': 'service', - 'entity.displayName': 'foo', - 'entity.identityFields': ['service.name', 'service.environment'], + 'entity.display_name': 'foo', + 'entity.identity_fields': ['service.name', 'service.environment'], 'service.name': 'foo', - 'entity.definitionId': 'service', + 'entity.definition_id': 'service', 'agent.name': 'bar', 'service.environment': 'baz', }; diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts index 90bf2967b894d..0e6c663a00890 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_identify_fields.test.ts @@ -29,14 +29,14 @@ describe('getIdentityFields', () => { it('should return a Map with unique entity types and their respective identity fields', () => { const serviceEntity: ServiceEntity = { 'agent.name': 'node', - 'entity.identityFields': ['service.name', 'service.environment'], + 'entity.identity_fields': ['service.name', 'service.environment'], 'service.name': 'my-service', 'entity.type': 'service', ...commonEntityFields, }; const hostEntity: HostEntity = { - 'entity.identityFields': ['host.name'], + 'entity.identity_fields': ['host.name'], 'host.name': 'my-host', 'entity.type': 'host', 'cloud.provider': null, @@ -44,7 +44,7 @@ describe('getIdentityFields', () => { }; const containerEntity: ContainerEntity = { - 'entity.identityFields': 'container.id', + 'entity.identity_fields': 'container.id', 'host.name': 'my-host', 'entity.type': 'container', 'cloud.provider': null, diff --git a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/assistant_hypothesis/assistant_hypothesis.tsx b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/assistant_hypothesis/assistant_hypothesis.tsx index 2dc76d49c282f..f63cbb9c01618 100644 --- a/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/assistant_hypothesis/assistant_hypothesis.tsx +++ b/x-pack/plugins/observability_solution/investigate_app/public/pages/details/components/assistant_hypothesis/assistant_hypothesis.tsx @@ -114,7 +114,7 @@ const formatEntityMetrics = (entity: EntityWithSource): string => { .join(', '); const entitySources = entity.sources.map((source) => source.dataStream).join(', '); return dedent(` - Entity name: ${entity.displayName}; + Entity name: ${entity.display_name}; Entity type: ${entity.type}; Entity metrics: ${entityMetrics}; Entity data streams: ${entitySources} diff --git a/x-pack/plugins/observability_solution/investigate_app/server/services/get_entities.ts b/x-pack/plugins/observability_solution/investigate_app/server/services/get_entities.ts index 00151f2029d21..0aa5d674702e3 100644 --- a/x-pack/plugins/observability_solution/investigate_app/server/services/get_entities.ts +++ b/x-pack/plugins/observability_solution/investigate_app/server/services/get_entities.ts @@ -22,7 +22,7 @@ import { } from '../clients/create_entities_es_client'; // the official types do not explicitly define sourceIndex in the schema, but it is present in the data at the time of writing this -type EntitiesLatest = z.infer & { sourceIndex: string[] }; +type EntitiesLatest = z.infer & { source_index: string[] }; export async function getEntitiesWithSource({ serviceEnvironment, @@ -51,21 +51,21 @@ export async function getEntitiesWithSource({ for (const response of entityResponses) { const processedEntities = await Promise.all( response.map(async (entity: EntitiesLatest) => { - const sourceIndex = entity?.sourceIndex; + const sourceIndex = entity?.source_index; if (!sourceIndex || !sourceIndex.length) return null; const indices = await esClient.indices.get({ index: sourceIndex }); const sources = await fetchSources(indices); return { - identityFields: entity?.entity.identityFields, + identity_fields: entity?.entity.identity_fields, id: entity?.entity.id, - definitionId: entity?.entity.definitionId, - lastSeenTimestamp: entity?.entity.lastSeenTimestamp, - displayName: entity?.entity.displayName, + definition_id: entity?.entity.definition_id, + last_seen_timestamp: entity?.entity.last_seen_timestamp, + display_name: entity?.entity.display_name, metrics: entity?.entity.metrics, - schemaVersion: entity?.entity.schemaVersion, - definitionVersion: entity?.entity.definitionVersion, + schema_version: entity?.entity.schema_version, + definition_version: entity?.entity.definition_version, type: entity?.entity.type, sources, }; @@ -104,7 +104,7 @@ const getFetchEntitiesPromises = ({ hostName?: string; containerId?: string; serviceEnvironment?: string; -}): Array>> => { +}): Array>> => { const shouldFilterForServiceEnvironment = serviceEnvironment && serviceName && @@ -139,7 +139,7 @@ const getFetchEntitiesPromises = ({ return [containersPromise, hostsPromise, servicesPromise].filter( (promise) => promise !== null - ) as Array>>; + ) as Array>>; }; const getFetchEntityPromise = ({ @@ -152,10 +152,10 @@ const getFetchEntityPromise = ({ shouldFetch: boolean; shouldMatch: QueryDslQueryContainer[]; entitiesEsClient: EntitiesESClient; -}): Promise> | null => { +}): Promise> | null => { return shouldFetch ? entitiesEsClient - .search<{ sourceIndex: string[]; entity: EntitiesLatest['entity'] }>(index, { + .search<{ source_index: string[]; entity: EntitiesLatest['entity'] }>(index, { body: { query: { bool: { @@ -167,7 +167,7 @@ const getFetchEntityPromise = ({ }) .then((response) => { return response.hits.hits.map((hit) => { - return { sourceIndex: hit?._source.sourceIndex, entity: hit._source.entity }; + return { source_index: hit?._source.source_index, entity: hit._source.entity }; }); }) : null; diff --git a/x-pack/plugins/observability_solution/observability_shared/common/field_names/elasticsearch.ts b/x-pack/plugins/observability_solution/observability_shared/common/field_names/elasticsearch.ts index 5652671a87281..e703cd487259c 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/field_names/elasticsearch.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/field_names/elasticsearch.ts @@ -149,9 +149,9 @@ export const PROFILE_INUSE_SPACE = 'profile.inuse_space.bytes'; export const ENTITY = 'entity'; export const ENTITY_ID = 'entity.id'; export const ENTITY_TYPE = 'entity.type'; -export const ENTITY_LAST_SEEN = 'entity.lastSeenTimestamp'; -export const ENTITY_FIRST_SEEN = 'entity.firstSeenTimestamp'; -export const ENTITY_DISPLAY_NAME = 'entity.displayName'; -export const ENTITY_DEFINITION_ID = 'entity.definitionId'; -export const ENTITY_IDENTITY_FIELDS = 'entity.identityFields'; +export const ENTITY_LAST_SEEN = 'entity.last_seen_timestamp'; +export const ENTITY_FIRST_SEEN = 'entity.first_seen_timestamp'; +export const ENTITY_DISPLAY_NAME = 'entity.display_name'; +export const ENTITY_DEFINITION_ID = 'entity.definition_id'; +export const ENTITY_IDENTITY_FIELDS = 'entity.identity_fields'; export const SOURCE_DATA_STREAM_TYPE = 'source_data_stream.type'; diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts index b332951b1a444..2eaf015f23220 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { waitForAlertsToPopulate } from '@kbn/test-suites-xpack/security_solution_cypress/cypress/tasks/create_new_rule'; import { disableNewFeaturesTours } from '../../tasks/navigation'; import { initializeDataViews } from '../../tasks/login'; import { checkResults, clickRuleName, submitQuery } from '../../tasks/live_query'; @@ -31,9 +32,8 @@ describe('Alert Test', { tags: ['@ess'] }, () => { onBeforeLoad: (win) => disableNewFeaturesTours(win), }); clickRuleName(ruleName); - cy.getBySel('expand-event').first().click({ force: true }); - - cy.wait(500); + waitForAlertsToPopulate(); + cy.getBySel('expand-event').first().click(); cy.getBySel('securitySolutionFlyoutInvestigationGuideButton').click(); cy.contains('Get processes').click(); }); diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts index c198a11556539..f4d2b848b726f 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts @@ -67,7 +67,7 @@ const buildIngestPipeline = ({ { set: { field: '@timestamp', - value: '{{entity.lastSeenTimestamp}}', + value: '{{entity.last_seen_timestamp}}', }, }, { diff --git a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/remove_entity_definition_fields_step.ts b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/remove_entity_definition_fields_step.ts index ad6a459f7ae80..96677a9c8a9b2 100644 --- a/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/remove_entity_definition_fields_step.ts +++ b/x-pack/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/remove_entity_definition_fields_step.ts @@ -15,13 +15,12 @@ export const removeEntityDefinitionFieldsStep = (): IngestProcessorContainer => remove: { ignore_failure: true, field: [ - 'entity.lastSeenTimestamp', - 'entity.schemaVersion', - 'entity.definitionVersion', - 'entity.identityFields', - 'entity.definitionId', - 'entity.displayName', - 'entity.firstSeenTimestamp', + 'entity.last_seen_timestamp', + 'entity.schema_version', + 'entity.definition_version', + 'entity.identity_fields', + 'entity.definition_id', + 'entity.display_name', ], }, }); diff --git a/x-pack/test/api_integration/apis/entity_manager/definitions.ts b/x-pack/test/api_integration/apis/entity_manager/definitions.ts index 468e53767b4e8..1a68169f6f386 100644 --- a/x-pack/test/api_integration/apis/entity_manager/definitions.ts +++ b/x-pack/test/api_integration/apis/entity_manager/definitions.ts @@ -101,26 +101,6 @@ export default function ({ getService }: FtrProviderContext) { await uninstallDefinition(supertest, { id: mockDefinition.id }); }); - - it('rejects updates to managed definitions', async () => { - await installDefinition(supertest, { - definition: { ...mockDefinition, managed: true }, - installOnly: true, - }); - - await updateDefinition(supertest, { - id: mockDefinition.id, - update: { - version: '1.0.0', - latest: { - timestampField: '@updatedTimestampField', - }, - }, - expectedCode: 403, - }); - - await uninstallDefinition(supertest, { id: mockDefinition.id }); - }); }); describe('entity data', () => { diff --git a/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts b/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts index c828e8da1f3af..7977f17bf5f65 100644 --- a/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts +++ b/x-pack/test/functional/apps/ml/short_tests/model_management/model_list.ts @@ -416,7 +416,8 @@ export default function ({ getService }: FtrProviderContext) { it('displays a model without an ingest pipeline and model can be deleted', async () => { await ml.testExecution.logTestStep('should display the model in the table'); - await ml.trainedModelsTable.filterWithSearchString(modelWithoutPipelineData.modelId, 1); + await ml.testExecution.logTestStep('expands the row to show the model details'); + await ml.trainedModelsTable.ensureRowIsExpanded(modelWithoutPipelineData.modelId); await ml.testExecution.logTestStep( 'displays expected row values for the model in the table' diff --git a/x-pack/test/functional/es_archives/security_solution/entity_store/mappings.json b/x-pack/test/functional/es_archives/security_solution/entity_store/mappings.json index 364ced91dc0b6..acbfceb9b55dd 100644 --- a/x-pack/test/functional/es_archives/security_solution/entity_store/mappings.json +++ b/x-pack/test/functional/es_archives/security_solution/entity_store/mappings.json @@ -45,15 +45,15 @@ }, "entity": { "properties": { - "definitionId": { + "definition_id": { "type": "keyword", "ignore_above": 1024 }, - "definitionVersion": { + "definition_version": { "type": "keyword", "ignore_above": 1024 }, - "displayName": { + "display_name": { "type": "text", "fields": { "keyword": { @@ -62,17 +62,14 @@ } } }, - "firstSeenTimestamp": { - "type": "date" - }, "id": { "type": "keyword", "ignore_above": 1024 }, - "identityFields": { + "identity_fields": { "type": "keyword" }, - "lastSeenTimestamp": { + "last_seen_timestamp": { "type": "date" }, "name": { @@ -210,15 +207,15 @@ }, "entity": { "properties": { - "definitionId": { + "definition_id": { "type": "keyword", "ignore_above": 1024 }, - "definitionVersion": { + "definition_version": { "type": "keyword", "ignore_above": 1024 }, - "displayName": { + "display_name": { "type": "text", "fields": { "keyword": { @@ -227,17 +224,14 @@ } } }, - "firstSeenTimestamp": { - "type": "date" - }, "id": { "type": "keyword", "ignore_above": 1024 }, - "identityFields": { + "identity_fields": { "type": "keyword" }, - "lastSeenTimestamp": { + "last_seen_timestamp": { "type": "date" }, "name": { diff --git a/yarn.lock b/yarn.lock index 379a5c9261271..46f953a50a7dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1753,10 +1753,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@97.0.0-backport.0": - version "97.0.0-backport.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-97.0.0-backport.0.tgz#cababac6e5937b14ce0e836240fc4817e2e41920" - integrity sha512-gefYh5ZgjFraGWOTy8f8RO5DAfOJB3S/PAlM9dvi6mNlNJ5T1CelNafBwdrD8Hdut//BxmexQD7aBZ+36GTaOg== +"@elastic/eui@97.0.0-backport.1": + version "97.0.0-backport.1" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-97.0.0-backport.1.tgz#2f4c9e6edca3501b0178d2e98a7f6a8b07dc0d81" + integrity sha512-fv/R7c+CGIMeebQxMxScxiWBfOF/2PGgsdFwuj4zvPsLjGwGLFKzojOtykQDnSzGuIx4mxIJ+KVasWg8zl2Uig== dependencies: "@hello-pangea/dnd" "^16.6.0" "@types/lodash" "^4.14.202" @@ -29683,7 +29683,7 @@ string-replace-loader@^2.2.0: loader-utils "^1.2.3" schema-utils "^1.0.0" -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -29701,15 +29701,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -29820,7 +29811,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -29834,13 +29825,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -32758,7 +32742,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -32784,15 +32768,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" @@ -32905,7 +32880,7 @@ xpath@^0.0.33: resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07" integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA== -"xstate5@npm:xstate@^5.18.1": +"xstate5@npm:xstate@^5.18.1", xstate@^5.18.1: version "5.18.1" resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.18.1.tgz#c4d43ceaba6e6c31705d36bd96e285de4be4f7f4" integrity sha512-m02IqcCQbaE/kBQLunwub/5i8epvkD2mFutnL17Oeg1eXTShe1sRF4D5mhv1dlaFO4vbW5gRGRhraeAD5c938g== @@ -32915,11 +32890,6 @@ xstate@^4.38.2: resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.38.2.tgz#1b74544fc9c8c6c713ba77f81c6017e65aa89804" integrity sha512-Fba/DwEPDLneHT3tbJ9F3zafbQXszOlyCJyQqqdzmtlY/cwE2th462KK48yaANf98jHlP6lJvxfNtN0LFKXPQg== -xstate@^5.18.1: - version "5.18.1" - resolved "https://registry.yarnpkg.com/xstate/-/xstate-5.18.1.tgz#c4d43ceaba6e6c31705d36bd96e285de4be4f7f4" - integrity sha512-m02IqcCQbaE/kBQLunwub/5i8epvkD2mFutnL17Oeg1eXTShe1sRF4D5mhv1dlaFO4vbW5gRGRhraeAD5c938g== - "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"