From 52cebed323ce056d0d8c472482d1b5648d429421 Mon Sep 17 00:00:00 2001 From: Sonia Sanz Vivas Date: Fri, 3 Jan 2025 15:25:19 +0100 Subject: [PATCH 01/43] [Ingest pipelines] Change copy text from copy to field in Set processor (#205471) Part of https://github.com/elastic/kibana/issues/193186 ## Summary I got some copy change request from docs team after submitting this [PR](https://github.com/elastic/kibana/pull/204336) and I forgot to add those changes. Adding this PR as a follow-up. --- .../components/processor_form/processors/set.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx index 72849c57e8e0c..778e8ae8d82dd 100644 --- a/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx +++ b/x-pack/platform/plugins/shared/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/processors/set.tsx @@ -176,7 +176,7 @@ const fieldsConfig: FieldsConfig = { helpText: ( {'Field'}, value: {'Value'}, From d26ba8a9f3333c2a40edcc7ad8204a6cba31594a Mon Sep 17 00:00:00 2001 From: Sonia Sanz Vivas Date: Fri, 3 Jan 2025 15:25:35 +0100 Subject: [PATCH 02/43] [Remote Clusters] Modify folder structure (#205473) Part of https://github.com/elastic/kibana/issues/199664 ## Summary As part of the work of the `Remote clusters: UX improvements` project I've changed a bit the folder structure and moved some files that are going to be used by more than one step. Since the PR will be large, I decided to create a PR only for the structure changes to avoid much noise in the main PR. The new structure will be as follows: ``` |-- x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/ |-- remote_cluster_config_steps [new folder] |-- components [will be used for the common components for all the steps] |-- request_flyout.tsx [existing but moved here] |-- remote_cluster_form |-- remote_cluster_setup_trust ``` --- .../edit/remote_clusters_edit.test.tsx | 2 +- .../public/application/sections/components/index.js | 4 ++-- .../components}/request_flyout.tsx | 2 +- .../remote_cluster_form/components/connection_mode.tsx | 4 ++-- .../components/connection_mode_cloud.tsx | 2 +- .../remote_cluster_form/components/index.ts | 0 .../components/proxy_connection.tsx | 2 +- .../components/sniff_connection.tsx | 2 +- .../remote_cluster_form/index.ts | 0 .../remote_cluster_form/remote_cluster_form.tsx | 10 +++++----- .../__snapshots__/validate_name.test.ts.snap | 0 .../validate_node_connections.test.tsx.snap | 0 .../__snapshots__/validate_proxy.test.ts.snap | 0 .../__snapshots__/validate_seeds.test.ts.snap | 0 .../remote_cluster_form/validators/index.ts | 0 .../validators/validate_address.test.ts | 0 .../remote_cluster_form/validators/validate_address.ts | 0 .../validators/validate_cloud_url.test.ts | 0 .../validators/validate_cloud_url.tsx | 2 +- .../validators/validate_cluster.tsx | 2 +- .../validators/validate_name.test.ts | 0 .../remote_cluster_form/validators/validate_name.tsx | 0 .../validators/validate_node_connections.test.tsx | 2 +- .../validators/validate_node_connections.tsx | 2 +- .../validators/validate_proxy.test.ts | 0 .../remote_cluster_form/validators/validate_proxy.tsx | 0 .../validators/validate_seed.test.ts | 0 .../remote_cluster_form/validators/validate_seed.tsx | 0 .../validators/validate_seeds.test.ts | 0 .../remote_cluster_form/validators/validate_seeds.tsx | 0 .../remote_cluster_setup_trust/confirm_modal.tsx | 0 .../remote_cluster_setup_trust/index.ts | 0 .../remote_cluster_setup_trust.tsx | 4 ++-- 33 files changed, 20 insertions(+), 20 deletions(-) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{remote_cluster_form => remote_cluster_config_steps/components}/request_flyout.tsx (99%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/components/connection_mode.tsx (94%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/components/connection_mode_cloud.tsx (98%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/components/index.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/components/proxy_connection.tsx (98%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/components/sniff_connection.tsx (98%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/index.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/remote_cluster_form.tsx (97%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/__snapshots__/validate_node_connections.test.tsx.snap (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/index.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_address.test.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_address.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_cloud_url.test.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_cloud_url.tsx (97%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_cluster.tsx (94%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_name.test.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_name.tsx (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_node_connections.test.tsx (90%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_node_connections.tsx (90%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_proxy.test.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_proxy.tsx (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_seed.test.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_seed.tsx (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_seeds.test.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_form/validators/validate_seeds.tsx (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_setup_trust/confirm_modal.tsx (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_setup_trust/index.ts (100%) rename x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/{ => remote_cluster_config_steps}/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx (98%) diff --git a/x-pack/platform/plugins/private/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx b/x-pack/platform/plugins/private/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx index c4c656bb646d5..1025c7f50fa7a 100644 --- a/x-pack/platform/plugins/private/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.tsx @@ -8,7 +8,7 @@ import { act } from 'react-dom/test-utils'; import { TestBed } from '@kbn/test-jest-helpers'; -import { RemoteClusterForm } from '../../../public/application/sections/components/remote_cluster_form'; +import { RemoteClusterForm } from '../../../public/application/sections/components/remote_cluster_config_steps/remote_cluster_form'; import { RemoteClustersActions, setupEnvironment } from '../helpers'; import { setup as setupRemoteClustersAdd } from '../add/remote_clusters_add.helpers'; import { diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/index.js b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/index.js index 6fe583f0c5d79..e6801a6b3bbb1 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/index.js +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/index.js @@ -5,7 +5,7 @@ * 2.0. */ -export { RemoteClusterSetupTrust } from './remote_cluster_setup_trust'; -export { RemoteClusterForm } from './remote_cluster_form'; +export { RemoteClusterSetupTrust } from './remote_cluster_config_steps/remote_cluster_setup_trust'; +export { RemoteClusterForm } from './remote_cluster_config_steps/remote_cluster_form'; export { RemoteClusterPageTitle } from './remote_cluster_page_title'; export { ConfiguredByNodeWarning } from './configured_by_node_warning'; diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/components/request_flyout.tsx similarity index 99% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/components/request_flyout.tsx index a729315357f04..cf46689137b07 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/request_flyout.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/components/request_flyout.tsx @@ -20,7 +20,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { ClusterPayload, serializeCluster } from '../../../../../common/lib'; +import { ClusterPayload, serializeCluster } from '../../../../../../common/lib'; interface Props { close: () => void; diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/connection_mode.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/connection_mode.tsx similarity index 94% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/connection_mode.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/connection_mode.tsx index b51d389b73014..8cc45fd2b3473 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/connection_mode.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/connection_mode.tsx @@ -10,8 +10,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiDescribedFormGroup, EuiTitle, EuiFormRow, EuiSwitch } from '@elastic/eui'; -import { SNIFF_MODE, PROXY_MODE } from '../../../../../../common/constants'; -import { useAppContext } from '../../../../app_context'; +import { SNIFF_MODE, PROXY_MODE } from '../../../../../../../common/constants'; +import { useAppContext } from '../../../../../app_context'; import { ClusterErrors } from '../validators'; import { ConnectionModeCloud } from './connection_mode_cloud'; diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/connection_mode_cloud.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/connection_mode_cloud.tsx similarity index 98% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/connection_mode_cloud.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/connection_mode_cloud.tsx index b5d27b15de8e2..f9a13f86b8fc3 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/connection_mode_cloud.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/connection_mode_cloud.tsx @@ -21,7 +21,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { proxySettingsUrl } from '../../../../services/documentation'; +import { proxySettingsUrl } from '../../../../../services/documentation'; import { ClusterErrors } from '../validators'; import { FormFields } from '../remote_cluster_form'; diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/index.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/index.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/index.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/index.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/proxy_connection.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/proxy_connection.tsx similarity index 98% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/proxy_connection.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/proxy_connection.tsx index f322aebee9d7e..3d579da219106 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/proxy_connection.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/proxy_connection.tsx @@ -9,7 +9,7 @@ import React, { FunctionComponent } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiFieldNumber, EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; -import { proxySettingsUrl } from '../../../../services/documentation'; +import { proxySettingsUrl } from '../../../../../services/documentation'; import { Props } from './connection_mode'; export const ProxyConnection: FunctionComponent = (props) => { diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/sniff_connection.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/sniff_connection.tsx similarity index 98% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/sniff_connection.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/sniff_connection.tsx index c29a1ce5c2169..b4dc347673cdb 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/components/sniff_connection.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/components/sniff_connection.tsx @@ -16,7 +16,7 @@ import { EuiLink, } from '@elastic/eui'; -import { transportPortUrl } from '../../../../services/documentation'; +import { transportPortUrl } from '../../../../../services/documentation'; import { validateSeed } from '../validators'; import { Props } from './connection_mode'; diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/index.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/index.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/index.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/index.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/remote_cluster_form.tsx similarity index 97% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/remote_cluster_form.tsx index 083f7b8f06c93..4eadc0acd25c6 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/remote_cluster_form.tsx @@ -31,14 +31,14 @@ import { EuiSwitchEvent, } from '@elastic/eui'; -import { Cluster, ClusterPayload } from '../../../../../common/lib'; -import { SNIFF_MODE, PROXY_MODE } from '../../../../../common/constants'; +import { Cluster, ClusterPayload } from '../../../../../../common/lib'; +import { SNIFF_MODE, PROXY_MODE } from '../../../../../../common/constants'; -import { AppContext, Context } from '../../../app_context'; +import { AppContext, Context } from '../../../../app_context'; -import { skippingDisconnectedClustersUrl } from '../../../services/documentation'; +import { skippingDisconnectedClustersUrl } from '../../../../services/documentation'; -import { RequestFlyout } from './request_flyout'; +import { RequestFlyout } from '../components/request_flyout'; import { ConnectionMode } from './components'; import { ClusterErrors, diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_name.test.ts.snap diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_node_connections.test.tsx.snap b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_node_connections.test.tsx.snap similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_node_connections.test.tsx.snap rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_node_connections.test.tsx.snap diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_proxy.test.ts.snap diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/__snapshots__/validate_seeds.test.ts.snap diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/index.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/index.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/index.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.test.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_address.test.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.test.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_address.test.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_address.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_address.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_address.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cloud_url.test.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cloud_url.test.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cloud_url.test.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cloud_url.test.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cloud_url.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cloud_url.tsx similarity index 97% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cloud_url.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cloud_url.tsx index 54f050a789b9f..247a996182884 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cloud_url.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cloud_url.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { Cluster } from '../../../../../../common/lib'; +import { Cluster } from '../../../../../../../common/lib'; import { isAddressValid } from './validate_address'; export const i18nTexts = { diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cluster.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cluster.tsx similarity index 94% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cluster.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cluster.tsx index 6d3f9e31c74b6..15686b50d3728 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_cluster.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_cluster.tsx @@ -6,7 +6,7 @@ */ import { validateName } from './validate_name'; -import { PROXY_MODE, SNIFF_MODE } from '../../../../../../common/constants'; +import { PROXY_MODE, SNIFF_MODE } from '../../../../../../../common/constants'; import { validateSeeds } from './validate_seeds'; import { validateProxy } from './validate_proxy'; import { validateCloudRemoteAddress } from './validate_cloud_url'; diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_name.test.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.test.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_name.test.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_name.tsx similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_name.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_name.tsx diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_node_connections.test.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_node_connections.test.tsx similarity index 90% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_node_connections.test.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_node_connections.test.tsx index 20f39395692b7..4d9fedc205e60 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_node_connections.test.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_node_connections.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { MAX_NODE_CONNECTIONS } from '../../../../../../common/constants'; +import { MAX_NODE_CONNECTIONS } from '../../../../../../../common/constants'; import { validateNodeConnections } from './validate_node_connections'; describe('validateNodeConnections', () => { diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_node_connections.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_node_connections.tsx similarity index 90% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_node_connections.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_node_connections.tsx index 4adadb6fc1d6d..2ed76a968cf98 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_node_connections.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_node_connections.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -import { MAX_NODE_CONNECTIONS } from '../../../../../../common/constants'; +import { MAX_NODE_CONNECTIONS } from '../../../../../../../common/constants'; export function validateNodeConnections(connections?: number | null): null | JSX.Element { if (connections && connections > MAX_NODE_CONNECTIONS) { diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_proxy.test.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_proxy.test.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_proxy.test.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_proxy.test.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_proxy.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_proxy.tsx similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_proxy.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_proxy.tsx diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seed.test.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seed.test.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seed.test.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seed.test.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seed.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seed.tsx similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seed.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seed.tsx diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.test.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seeds.test.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.test.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seeds.test.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seeds.tsx similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_form/validators/validate_seeds.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_form/validators/validate_seeds.tsx diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/confirm_modal.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/confirm_modal.tsx similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/confirm_modal.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/confirm_modal.tsx diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/index.ts b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/index.ts similarity index 100% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/index.ts rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/index.ts diff --git a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx similarity index 98% rename from x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx rename to x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx index 399e49fc8fc3f..12aa1787d6792 100644 --- a/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx +++ b/x-pack/platform/plugins/private/remote_clusters/public/application/sections/components/remote_cluster_config_steps/remote_cluster_setup_trust/remote_cluster_setup_trust.tsx @@ -18,8 +18,8 @@ import { EuiButtonEmpty, } from '@elastic/eui'; -import * as docs from '../../../services/documentation'; -import { AppContext } from '../../../app_context'; +import * as docs from '../../../../services/documentation'; +import { AppContext } from '../../../../app_context'; import { ConfirmTrustSetupModal } from './confirm_modal'; const MIN_ALLOWED_VERSION_API_KEYS_METHOD = '8.10'; From b2344549fbe5a57506c9bb7284623749a124bc06 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 3 Jan 2025 08:27:54 -0600 Subject: [PATCH 03/43] [renovate] matchBaseBranches for a few more groups (#205451) Follow up to https://github.com/elastic/kibana/pull/205285 This uses renovate instead of backport tooling on a few more of operation's groups. These were selected because they have not required any code changes to date. --- renovate.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/renovate.json b/renovate.json index c1d7b8b53ba76..7e528ca255379 100644 --- a/renovate.json +++ b/renovate.json @@ -67,11 +67,12 @@ "team:kibana-operations" ], "matchBaseBranches": [ - "main" + "*" ], "labels": [ "Team:Operations", - "release_note:skip" + "release_note:skip", + "backport:skip" ], "enabled": true }, @@ -93,11 +94,11 @@ "team:kibana-operations" ], "matchBaseBranches": [ - "main" + "*" ], "labels": [ "Team:Operations", - "backport:all-open", + "backport:skip", "release_note:skip" ], "enabled": true From 8d1845208e66e9ae8a5a69ef0d0f5c8ce712929c Mon Sep 17 00:00:00 2001 From: Sonia Sanz Vivas Date: Fri, 3 Jan 2025 15:55:11 +0100 Subject: [PATCH 04/43] Add remote clusters doc links (#205477) Part of https://github.com/elastic/kibana/issues/199664 ## Summary As part of the work of the `Remote clusters: UX improvements` project, adding some links. The corresponding PR will be quite large so I've decided to add the doc links in a separated PR to avoid noise and simplify the review process. The added links are the following: | Name | Target link | Code link | |-----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| | management.apiKeys| https://www.elastic.co/guide/en/kibana/8.16/api-keys.html | `${KIBANA_DOCS}api-keys.html` | | elasticsearch.remoteClustersCreateCloudClusterApiKey | https://www.elastic.co/guide/en/elasticsearch/reference/8.16/security-api-create-cross-cluster-api-key.html| `${ELASTICSEARCH_DOCS}security-api-create-cross-cluster-api-key.html`| | elasticsearch.remoteClustersOnPremPrerequisitesApiKey | https://www.elastic.co/guide/en/elasticsearch/reference/8.16/remote-clusters-api-key.html#remote-clusters-prerequisites-api-key | `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html#remote-clusters-prerequisites-api-key` | | elasticsearch.remoteClustersOnPremSecurityApiKey | https://www.elastic.co/guide/en/elasticsearch/reference/8.16/remote-clusters-api-key.html#remote-clusters-security-api-key| `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html#remote-clusters-security-api-key` | | elasticsearch.remoteClustersOnPremPrerequisitesCert | https://www.elastic.co/guide/en/elasticsearch/reference/8.16/remote-clusters-cert.html#remote-clusters-prerequisites-cert| `${ELASTICSEARCH_DOCS}remote-clusters-cert.html#remote-clusters-prerequisites-cert`| | elasticsearch.remoteClustersOnPremSecurityCert| https://www.elastic.co/guide/en/elasticsearch/reference/8.16/remote-clusters-cert.html#remote-clusters-security-cert| `${ELASTICSEARCH_DOCS}remote-clusters-cert.html#remote-clusters-security-cert`| --- .../packages/shared/kbn-doc-links/src/get_doc_links.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts index f391114a85f26..dc747a63cdc96 100644 --- a/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts +++ b/src/platform/packages/shared/kbn-doc-links/src/get_doc_links.ts @@ -445,6 +445,11 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D remoteClustersOnPremSetupTrustWithCert: `${ELASTICSEARCH_DOCS}remote-clusters-cert.html`, remoteClustersOnPremSetupTrustWithApiKey: `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html`, remoteClustersCloudSetupTrust: `${ELASTIC_WEBSITE_URL}guide/en/cloud/current/ec-enable-ccs.html`, + remoteClustersCreateCloudClusterApiKey: `${ELASTICSEARCH_DOCS}security-api-create-cross-cluster-api-key.html`, + remoteClustersOnPremPrerequisitesApiKey: `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html#remote-clusters-prerequisites-api-key`, + remoteClustersOnPremSecurityApiKey: `${ELASTICSEARCH_DOCS}remote-clusters-api-key.html#remote-clusters-security-api-key`, + remoteClustersOnPremPrerequisitesCert: `${ELASTICSEARCH_DOCS}remote-clusters-cert.html#remote-clusters-prerequisites-cert`, + remoteClustersOnPremSecurityCert: `${ELASTICSEARCH_DOCS}remote-clusters-cert.html#remote-clusters-security-cert`, rollupMigratingToDownsampling: `${ELASTICSEARCH_DOCS}rollup-migrating-to-downsampling.html`, rrf: `${ELASTICSEARCH_DOCS}rrf.html`, scriptParameters: `${ELASTICSEARCH_DOCS}modules-scripting-using.html#prefer-params`, @@ -539,6 +544,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D timelionSettings: `${KIBANA_DOCS}advanced-options.html#kibana-timelion-settings`, generalSettings: `${KIBANA_DOCS}advanced-options.html#kibana-general-settings`, savedObjectsApiList: `${KIBANA_DOCS}saved-objects-api.html#saved-objects-api`, + apiKeys: `${KIBANA_DOCS}api-keys.html`, }, ml: { guide: `${MACHINE_LEARNING_DOCS}index.html`, From d25dcc11ebdd18324111fc7dc403014b08fbf786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Fri, 3 Jan 2025 16:04:17 +0100 Subject: [PATCH 05/43] [Infra][Hosts] Reset SearchBar refresh state to fully disable auto-refresh (#205416) ## Summary A bug with the search bar configuration allows for a page that does not have auto-refresh enabled to inherit the auto-refresh state from another page that had previously enabled it. This PR fixes that by fully resetting the state for the page's search bar to ensure auto-refresh is not active for Hosts page (as it should be). Closes #205414 ## How to test - Go to Alerts page - Click on the Date Quick Selector, enable the auto-refresh feature. Confirm the refresh icon is on the datepicker now. - Navigate to Hosts page - The auto-refresh icon on the Hosts page search bar datepicker should no longer be visible, and opening the date quick selector should not show any auto-refresh feature/toggle. --- .../metrics/hosts/components/search_bar/unified_search_bar.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx index 426cc275d52d0..8d522cebb5102 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/search_bar/unified_search_bar.tsx @@ -60,6 +60,7 @@ export const UnifiedSearchBar = () => { showQueryMenu useDefaultBehaviors isAutoRefreshDisabled + isRefreshPaused /> From 1fb16c59521a9bbbbc71d151f4de5fa57323c7e2 Mon Sep 17 00:00:00 2001 From: Ilya Nikokoshev Date: Fri, 3 Jan 2025 16:31:46 +0100 Subject: [PATCH 06/43] [Automatic Import] Safely access non-identifier fields in Painless if context (#205220) Closes https://github.com/elastic/kibana/issues/205024 We add utility functions to access nested fields in Painless in a safe way and modify the existing ECS generation logic to use them. This access happens using the `object?.get("field")` syntax for complex cases, while falling back to the familiar `ctx.field` for the cases where `field` is a valid Painless identifier and `ctx` is known to be non-nullable. This takes care of the compile-time correctness of field accesses. Note that it is still possible for generated pipelines to fail in runtime on unexpected input, e.g. accessing a nested field `a.b` fails for the document of the form `{"a": "string"}`. See the PR for more details, release note and test results. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../server/graphs/ecs/pipeline.test.ts | 2 +- .../server/graphs/ecs/pipeline.ts | 60 ++++++--- .../server/templates/pipeline.yml.njk | 3 +- .../server/util/fields.test.ts | 20 +++ .../server/util/fields.ts | 25 ++++ .../server/util/painless.test.ts | 88 +++++++++++++ .../server/util/painless.ts | 124 ++++++++++++++++++ 7 files changed, 301 insertions(+), 21 deletions(-) create mode 100644 x-pack/platform/plugins/shared/integration_assistant/server/util/fields.test.ts create mode 100644 x-pack/platform/plugins/shared/integration_assistant/server/util/fields.ts create mode 100644 x-pack/platform/plugins/shared/integration_assistant/server/util/painless.test.ts create mode 100644 x-pack/platform/plugins/shared/integration_assistant/server/util/painless.ts diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.test.ts b/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.test.ts index 3dda9208c3094..0ad142390b865 100644 --- a/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.test.ts +++ b/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.test.ts @@ -79,7 +79,7 @@ describe('Testing pipeline templates', () => { target_field: '@timestamp', formats: ["yyyy-MM-dd'T'HH:mm:ss.SSSSSSSSS'Z'", 'ISO8601'], tag: 'date_processor_xdfsfs.ds.@timestamp', - if: 'ctx.xdfsfs?.ds?.@timestamp != null', + if: 'ctx.xdfsfs?.ds?.get("@timestamp") != null', }, }, { diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.ts b/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.ts index dda48c97bdf98..d729df1edde3d 100644 --- a/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.ts +++ b/x-pack/platform/plugins/shared/integration_assistant/server/graphs/ecs/pipeline.ts @@ -12,10 +12,8 @@ import { Pipeline, ESProcessorItem } from '../../../common'; import type { EcsMappingState } from '../../types'; import { ECS_TYPES } from './constants'; import { deepCopy } from '../../util/util'; - -interface IngestPipeline { - [key: string]: unknown; -} +import { type FieldPath, fieldPathToProcessorString } from '../../util/fields'; +import { fieldPathToPainlessExpression, SafePainlessExpression } from '../../util/painless'; interface ECSField { target: string; @@ -24,16 +22,36 @@ interface ECSField { type: string; } +const KNOWN_ES_TYPES = ['long', 'float', 'scaled_float', 'ip', 'boolean', 'keyword']; +type KnownESType = (typeof KNOWN_ES_TYPES)[number]; + +/** + * Clarifies the types of specific fields in pipeline processors. + * + * This includes safety requirements for Painless script fields. + * Restricted to the processors that we generate in this file. + */ +interface SafeESProcessorItem extends ESProcessorItem { + [k: string]: { + field?: string; + if?: SafePainlessExpression; + ignore_missing?: boolean; + target_field?: string; + type?: KnownESType; + formats?: string[]; + }; +} + function generateProcessor( - currentPath: string, + currentPath: FieldPath, ecsField: ECSField, expectedEcsType: string, sampleValue: unknown -): object { +): SafeESProcessorItem { if (needsTypeConversion(sampleValue, expectedEcsType)) { return { convert: { - field: currentPath, + field: fieldPathToProcessorString(currentPath), target_field: ecsField.target, type: getConvertProcessorType(expectedEcsType), ignore_missing: true, @@ -44,17 +62,17 @@ function generateProcessor( if (ecsField.type === 'date') { return { date: { - field: currentPath, + field: fieldPathToProcessorString(currentPath), target_field: ecsField.target, formats: convertIfIsoDate(ecsField.date_formats), - if: currentPath.replace(/\./g, '?.'), + if: fieldPathToPainlessExpression(currentPath), }, }; } return { rename: { - field: currentPath, + field: fieldPathToProcessorString(currentPath), target_field: ecsField.target, ignore_missing: true, }, @@ -74,10 +92,9 @@ function convertIfIsoDate(date: string[]): string[] { return date; } -function getSampleValue(key: string, samples: Record): unknown { - const keyList = key.split('.'); +function getSampleValue(fieldPath: FieldPath, samples: Record): unknown { let value: any = samples; - for (const k of keyList) { + for (const k of fieldPath) { if (value === undefined || value === null) { return null; } @@ -91,7 +108,7 @@ function getEcsType(ecsField: ECSField, ecsTypes: Record): strin return ecsTypes[ecsTarget]; } -function getConvertProcessorType(expectedEcsType: string): string { +function getConvertProcessorType(expectedEcsType: KnownESType): KnownESType { if (expectedEcsType === 'long') { return 'long'; } @@ -107,7 +124,7 @@ function getConvertProcessorType(expectedEcsType: string): string { return 'string'; } -function needsTypeConversion(sample: unknown, expected: string): boolean { +function needsTypeConversion(sample: unknown, expected: KnownESType): boolean { if (sample === null || sample === undefined) { return false; } @@ -136,16 +153,20 @@ function needsTypeConversion(sample: unknown, expected: string): boolean { return false; } -function generateProcessors(ecsMapping: object, samples: object, basePath: string = ''): object[] { +function generateProcessors( + ecsMapping: object, + samples: object, + basePath: FieldPath = [] +): SafeESProcessorItem[] { if (Object.keys(ecsMapping).length === 0) { return []; } const ecsTypes = ECS_TYPES; const valueFieldKeys = new Set(['target', 'confidence', 'date_formats', 'type']); - const results: object[] = []; + const results: SafeESProcessorItem[] = []; for (const [key, value] of Object.entries(ecsMapping)) { - const currentPath = basePath ? `${basePath}.${key}` : key; + const currentPath = [...basePath, key]; if (value !== null && typeof value === 'object' && value?.target !== null) { const valueKeys = new Set(Object.keys(value)); @@ -162,10 +183,11 @@ function generateProcessors(ecsMapping: object, samples: object, basePath: strin } } } + return results; } -export function createPipeline(state: EcsMappingState): IngestPipeline { +export function createPipeline(state: EcsMappingState): Pipeline { const samples = JSON.parse(state.combinedSamples); const processors = generateProcessors(state.finalMapping, samples); diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/templates/pipeline.yml.njk b/x-pack/platform/plugins/shared/integration_assistant/server/templates/pipeline.yml.njk index 116d5cc66719f..1af205fb5e549 100644 --- a/x-pack/platform/plugins/shared/integration_assistant/server/templates/pipeline.yml.njk +++ b/x-pack/platform/plugins/shared/integration_assistant/server/templates/pipeline.yml.njk @@ -53,7 +53,8 @@ processors: - {{ format }} {% endfor %} tag: date_processor_{{ value.field}} - if: "ctx.{{ value.if }} != null"{% endif %} + if: |- + {{ value.if }} != null{% endif %} {% if key == 'convert' %} - {{ key }}: field: {{ value.field }} diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/util/fields.test.ts b/x-pack/platform/plugins/shared/integration_assistant/server/util/fields.test.ts new file mode 100644 index 0000000000000..efda66df2116a --- /dev/null +++ b/x-pack/platform/plugins/shared/integration_assistant/server/util/fields.test.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { fieldPathToProcessorString } from './fields'; + +describe('fieldPathToProcessorString', () => { + it('should join an array of strings with dots', () => { + const result = fieldPathToProcessorString(['foo', 'bar', 'baz']); + expect(result).toBe('foo.bar.baz'); + }); + + it('should return an empty string if array is empty', () => { + const result = fieldPathToProcessorString([]); + expect(result).toBe(''); + }); +}); diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/util/fields.ts b/x-pack/platform/plugins/shared/integration_assistant/server/util/fields.ts new file mode 100644 index 0000000000000..810754b23e150 --- /dev/null +++ b/x-pack/platform/plugins/shared/integration_assistant/server/util/fields.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Represents a path to a field as an array of string segments. + * Each element in the array represents a level in the field hierarchy. + * + * A segment might contain a character that is invalid in some contexts. + * @example ['person', 'address', 'street-level'] + */ +export type FieldPath = string[]; + +/** + * Converts a FieldPath array into a string useable as the field in the ingest pipeline. + * + * @param fieldPath - The array of field names representing the path. + * @returns The processor string created by joining the field names with a dot. + */ +export function fieldPathToProcessorString(fieldPath: FieldPath): string { + return fieldPath.join('.'); +} diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/util/painless.test.ts b/x-pack/platform/plugins/shared/integration_assistant/server/util/painless.test.ts new file mode 100644 index 0000000000000..e2ec2963f2ee3 --- /dev/null +++ b/x-pack/platform/plugins/shared/integration_assistant/server/util/painless.test.ts @@ -0,0 +1,88 @@ +/* + * 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 { + isPainlessIdentifier, + painlessStringRepresentation, + addPainlessFieldAccess, + fieldPathToPainlessExpression, + type SafePainlessExpression, +} from './painless'; + +describe('isPainlessIdentifier', () => { + it('should return true for valid identifiers', () => { + expect(isPainlessIdentifier('_validIdentifier123')).toBe(true); + expect(isPainlessIdentifier('valid')).toBe(true); + }); + + it('should return false for invalid identifiers', () => { + expect(isPainlessIdentifier('123start')).toBe(false); // Identifiers cannot start with a number + expect(isPainlessIdentifier('new')).toBe(true); // Reserved words are valid identifiers + expect(isPainlessIdentifier('_source')).toBe(true); // Underscore-prefixed identifiers are valid + expect(isPainlessIdentifier('invalid-char!')).toBe(false); // Identifiers cannot contain special characters + }); +}); + +describe('painlessFieldEscape', () => { + it('should return a quoted and escaped string', () => { + expect(painlessStringRepresentation('simple')).toBe('"simple"'); + expect(painlessStringRepresentation('"quote"')).toBe('"\\"quote\\""'); + expect(painlessStringRepresentation('back\\slash')).toBe('"back\\\\slash"'); + }); +}); + +describe('addPainlessFieldAccess', () => { + it('should add a dot-access for valid identifiers', () => { + const expr = 'root' as SafePainlessExpression; + const result = addPainlessFieldAccess('foo', expr, false); + expect(result).toBe('root.foo'); + }); + + it('should add a nullable dot-access for valid identifiers', () => { + const expr = 'root' as SafePainlessExpression; + const result = addPainlessFieldAccess('foo', expr); + expect(result).toBe('root?.foo'); + }); + + it('should add a get-access for invalid identifiers', () => { + const expr = 'root' as SafePainlessExpression; + const result = addPainlessFieldAccess('foo-bar', expr, false); + expect(result).toContain('"foo-bar"'); + expect(result).toBe('root.get("foo-bar")'); + }); + + it('should add a nullable get-access for invalid identifiers in the chain', () => { + const expr = 'root' as SafePainlessExpression; + const result = addPainlessFieldAccess('foo-bar', expr, true); + expect(result).toContain('"foo-bar"'); + expect(result).toBe('root?.get("foo-bar")'); + }); +}); + +describe('fieldPathToPainlessExpression', () => { + it('should build a nested expression from a simple field path', () => { + const result = fieldPathToPainlessExpression(['source', 'ip']); + expect(result).toBe('ctx.source?.ip'); + }); + + it('should quote invalid identifiers', () => { + const result = fieldPathToPainlessExpression(['ip-address']); + expect(result).toContain('"ip-address"'); + expect(result).toBe('ctx.get("ip-address")'); + }); + + it('should use nullable get access for nested invalid identifiers', () => { + const result = fieldPathToPainlessExpression(['field', 'ip-address']); + expect(result).toContain('"ip-address"'); + expect(result).toBe('ctx.field?.get("ip-address")'); + }); + + it('should return just "ctx" if the path is empty', () => { + const result = fieldPathToPainlessExpression([]); + expect(result).toBe('ctx'); + }); +}); diff --git a/x-pack/platform/plugins/shared/integration_assistant/server/util/painless.ts b/x-pack/platform/plugins/shared/integration_assistant/server/util/painless.ts new file mode 100644 index 0000000000000..797a14422f7b2 --- /dev/null +++ b/x-pack/platform/plugins/shared/integration_assistant/server/util/painless.ts @@ -0,0 +1,124 @@ +/* + * 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 { FieldPath } from './fields'; + +/** + * Branded type respresenting a string that is a safe Painless expression. + * + * Painless is a scripting language used in Elasticsearch, here we are using it to + * generate the ingest pipeline with Automatic Import. + * + * This type is used to ensure that a string has been validated + * and is considered safe to be used as a Painless expression. + * The `__isSafePainlessExpression` property is a type brand + * to distinguish this type from a regular string. + */ +export type SafePainlessExpression = string & { __isSafePainlessExpression: true }; + +export type SafeNonNullablePainlessExpression = SafePainlessExpression & { + __isNonNullablePainlessExpression: true; +}; + +/** + * A constant representing the context variable used in Elasticsearch ingest pipeline painless scripts. + * This is typed as a safe painless expression to ensure type safety when used in pipeline definitions. + * + * @link https://www.elastic.co/guide/en/elasticsearch/painless/8.17/painless-contexts.html + * @constant {SafePainlessExpression} + */ +const INGEST_PIPELINE_PAINLESS_CONTEXT = 'ctx' as const as SafePainlessExpression; + +/** + * A regular expression that matches valid Painless script identifiers. + * + * Identifiers in Painless + * must start with an underscore or a letter (a-z, A-Z), followed by any combination + * of underscores, letters, or digits. + * + * This regular expression ensures that the identifier conforms to these rules: + * - The first character must be an underscore or a letter. + * - Subsequent characters can be underscores, letters, or digits. + * + * This is the ID and DOTID regexp in the Painless grammar under the following link: + * @link packages/kbn-monaco/src/painless/antlr/painless_parser.g4 + */ +const PAINLESS_IDENTIFIER_REGEXP = /^[_a-zA-Z][_a-zA-Z0-9]*$/; + +/** + * Checks if a given string is a valid Painless identifier (though possibly a reserved word). + * + * @link https://www.elastic.co/guide/en/elasticsearch/painless/8.17/painless-identifiers.html + * @param s - The string to check. + * @returns `true` if the string is a valid Painless identifier, `false` otherwise. + */ +export function isPainlessIdentifier(s: string): boolean { + return PAINLESS_IDENTIFIER_REGEXP.test(s); +} + +/** + * Creates a string literal for use in Painless scripts. + * + * Quoting rules: + * - Use a \" token to include a double-quote as part of a double-quoted string literal. + * - Use a \\ token to include a backslash as part of any string literal. + * + * @link https://www.elastic.co/guide/en/elasticsearch/painless/8.17/painless-literals.html#string-literals + * @param s - The string to escape. + * @returns The escaped string. + */ +export function painlessStringRepresentation(s: string): SafePainlessExpression { + return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` as SafePainlessExpression; +} + +/** + * Adds a field access to a Painless expression. + * + * This function is used to add a field access to a Painless expression. + * It ensures that the field access is properly escaped and that the expression remains safe. + * It is still possible to access fields that are not valid Painless identifiers by using a map access. + * + * @param expr - The Painless expression to add the field access to. + * @param fieldName - The subfield to access. + * @returns The new Painless expression with the added field access; safe but possibly null. + */ +export function addPainlessFieldAccess( + fieldName: string, + expr: SafePainlessExpression, + exprNullable: boolean = true +): SafePainlessExpression { + const nonNullableExpr = exprNullable ? (`${expr}?` as SafePainlessExpression) : expr; + const isValidIdentifier = isPainlessIdentifier(fieldName); + + if (isValidIdentifier) { + return `${nonNullableExpr}.${fieldName}` as SafePainlessExpression; + } + + const representedName = painlessStringRepresentation(fieldName); + return `${nonNullableExpr}.get(${representedName})` as SafePainlessExpression; +} + +/** + * Converts a field path to a Painless script expression. + * + * This function takes a `FieldPath` (an array of strings representing the path to a field) + * and converts it into a `SafePainlessExpression` by reducing the array and adding Painless + * field access for each subfield. + * + * We assume that all field paths accesses except the context itself can result in nullable fields, + * so we always add a null check before accessing the subfields. + * + * @param fieldPath - The path to the field as an array of strings. + * @returns A `SafePainlessExpression` representing the field path in Painless script syntax. + */ +export function fieldPathToPainlessExpression(fieldPath: FieldPath): SafePainlessExpression { + return fieldPath.reduce( + (expr: SafePainlessExpression, subfield: string) => + addPainlessFieldAccess(subfield, expr, expr !== INGEST_PIPELINE_PAINLESS_CONTEXT), + INGEST_PIPELINE_PAINLESS_CONTEXT + ); +} From cf3c7bd7b9a14fcfa9706ca52b46255b21506222 Mon Sep 17 00:00:00 2001 From: Kevin Delemme Date: Fri, 3 Jan 2025 10:48:08 -0500 Subject: [PATCH 07/43] chore(synthetics): Borealis support (#204488) --- .../plugins/synthetics/kibana.jsonc | 3 +- .../monitor_status/monitor_status_data.ts | 2 +- .../availability_sparklines.tsx | 13 ++-- .../monitor_summary/duration_sparklines.tsx | 14 ++--- .../monitor_summary/monitor_alerts.tsx | 24 +++++--- .../monitor_error_sparklines.tsx | 7 ++- .../monitor_summary/monitor_errors_count.tsx | 11 ++-- .../monitor_list_table/monitor_locations.tsx | 15 +++-- .../monitor_stats/monitor_test_runs.tsx | 18 +++--- .../monitor_test_runs_sparkline.tsx | 19 +++--- .../overview/overview/metric_item.tsx | 60 ++++++++++--------- .../overview/monitor_detail_flyout.tsx | 26 ++++---- .../overview/overview/overview_alerts.tsx | 34 +++++++---- .../overview_errors/overview_errors_count.tsx | 10 +++- .../overview_errors_sparklines.tsx | 7 ++- .../step_objects/color_palette.tsx | 14 ++--- .../breakdown_legend.tsx | 19 ++---- .../network_timings_donut.tsx | 14 ++--- .../waterfall_marker/waterfall_markers.tsx | 33 +++++----- .../contexts/synthetics_shared_context.tsx | 1 + .../hooks/use_status_by_location.tsx | 22 +++---- .../plugins/synthetics/public/plugin.ts | 2 + .../plugins/synthetics/tsconfig.json | 1 + 23 files changed, 198 insertions(+), 171 deletions(-) diff --git a/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc b/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc index eab5fa622d47a..15ab331ea58c8 100644 --- a/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/synthetics/kibana.jsonc @@ -40,7 +40,8 @@ "usageCollection", "uiActions", "unifiedSearch", - "presentationUtil" + "presentationUtil", + "charts" ], "optionalPlugins": [ "cloud", diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_data.ts b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_data.ts index 0a76badc574ab..314316a348055 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_data.ts +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_status/monitor_status_data.ts @@ -83,7 +83,7 @@ export function getSkippedVizColor(euiTheme: EuiThemeComputed) { } export function getErrorVizColor(euiTheme: EuiThemeComputed) { - return euiTheme.colors.dangerText; + return euiTheme.colors.textDanger; } export function getXAxisLabelFormatter(interval: number) { diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx index 36ee60ef02677..9413f7738f06e 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/availability_sparklines.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; -import { AVAILABILITY_LABEL } from './availability_panel'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; import { ClientPluginsStart } from '../../../../../plugin'; import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; +import { AVAILABILITY_LABEL } from './availability_panel'; interface AvailabilitySparklinesProps { from: string; @@ -26,8 +26,7 @@ export const AvailabilitySparklines = (props: AvailabilitySparklinesProps) => { }, } = useKibana(); const { queryIdFilter, locationFilter } = useMonitorQueryFilters(); - - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); if (!queryIdFilter) { return null; @@ -50,7 +49,7 @@ export const AvailabilitySparklines = (props: AvailabilitySparklinesProps) => { selectedMetricField: 'monitor_availability', reportDefinitions: queryIdFilter, filters: locationFilter, - color: theme.eui.euiColorVis1, + color: euiTheme.colors.vis.euiColorVis1, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx index ab5e3e038c89d..776905fcaf0b7 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/duration_sparklines.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import React from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; -import { MEDIAN_DURATION_LABEL } from './duration_panel'; -import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; import { ClientPluginsStart } from '../../../../../plugin'; +import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; +import { MEDIAN_DURATION_LABEL } from './duration_panel'; interface DurationSparklinesProps { from: string; @@ -26,7 +26,7 @@ export const DurationSparklines = (props: DurationSparklinesProps) => { }, } = useKibana(); const { queryIdFilter, locationFilter } = useMonitorQueryFilters(); - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); if (!queryIdFilter) { return null; @@ -49,7 +49,7 @@ export const DurationSparklines = (props: DurationSparklinesProps) => { selectedMetricField: 'monitor.duration.us', reportDefinitions: queryIdFilter, filters: locationFilter, - color: theme.eui.euiColorVis1, + color: euiTheme.colors.vis.euiColorVis1, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx index 03878ae70f060..40bcaa3697b30 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_alerts.tsx @@ -5,23 +5,24 @@ * 2.0. */ -import React from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiSkeletonText, EuiPanel, + EuiSkeletonText, EuiText, EuiTitle, + euiPaletteColorBlindBehindText, + useEuiTheme, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { RECORDS_FIELD } from '@kbn/exploratory-view-plugin/public'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useSelectedLocation } from '../hooks/use_selected_location'; +import React from 'react'; +import { ClientPluginsStart } from '../../../../../plugin'; import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; +import { useSelectedLocation } from '../hooks/use_selected_location'; import { AlertActions } from './alert_actions'; -import { ClientPluginsStart } from '../../../../../plugin'; const MONITOR_STATUS_RULE = { 'kibana.alert.rule.category': ['Synthetics monitor status'], @@ -40,7 +41,8 @@ export const MonitorAlerts = ({ exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana().services; - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const { queryIdFilter, locationFilter } = useMonitorQueryFilters(); const selectedLocation = useSelectedLocation(); @@ -145,7 +147,9 @@ export const MonitorAlerts = ({ { field: 'kibana.alert.status', values: ['active'] }, ...(locationFilter ?? []), ], - color: theme.eui.euiColorVis7_behindText, + color: isAmsterdam + ? euiPaletteColorBlindBehindText()[7] + : euiTheme.colors.vis.euiColorVis7, }, ]} /> @@ -198,7 +202,9 @@ export const MonitorAlerts = ({ { field: 'kibana.alert.status', values: ['recovered'] }, ...(locationFilter ?? []), ], - color: theme.eui.euiColorVis0_behindText, + color: isAmsterdam + ? euiPaletteColorBlindBehindText()[0] + : euiTheme.colors.vis.euiColorVis0, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx index 033ffdaf92443..05f17b277d8a1 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_error_sparklines.tsx @@ -7,7 +7,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useMemo } from 'react'; -import { useEuiTheme } from '@elastic/eui'; +import { euiPaletteColorBlindBehindText, useEuiTheme } from '@elastic/eui'; import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; import { ERRORS_LABEL } from './monitor_errors_count'; import { ClientPluginsStart } from '../../../../../plugin'; @@ -23,6 +23,7 @@ export const MonitorErrorSparklines = ({ from, to, id }: Props) => { } = useKibana().services; const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const { queryIdFilter, locationFilter } = useMonitorQueryFilters(); const time = useMemo(() => ({ from, to }), [from, to]); @@ -47,8 +48,10 @@ export const MonitorErrorSparklines = ({ from, to, id }: Props) => { dataType: 'synthetics', selectedMetricField: 'monitor_errors', name: ERRORS_LABEL, - color: euiTheme.colors.danger, operationType: 'unique_count', + color: isAmsterdam + ? euiPaletteColorBlindBehindText()[1] + : euiTheme.colors.vis.euiColorVis6, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx index 9d439557e83aa..84f03f8d944fc 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_errors_count.tsx @@ -5,12 +5,13 @@ * 2.0. */ -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import React, { useMemo } from 'react'; +import { useEuiTheme } from '@elastic/eui'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; import { i18n } from '@kbn/i18n'; -import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React, { useMemo } from 'react'; import { ClientPluginsStart } from '../../../../../plugin'; +import { useMonitorQueryFilters } from '../hooks/use_monitor_query_filters'; interface MonitorErrorsCountProps { from: string; @@ -22,7 +23,8 @@ export const MonitorErrorsCount = ({ from, to, id }: MonitorErrorsCountProps) => const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana().services; - + const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const { queryIdFilter, locationFilter } = useMonitorQueryFilters(); const time = useMemo(() => ({ from, to }), [from, to]); @@ -45,6 +47,7 @@ export const MonitorErrorsCount = ({ from, to, id }: MonitorErrorsCountProps) => selectedMetricField: 'monitor_errors', name: ERRORS_LABEL, filters: locationFilter, + color: isAmsterdam ? euiTheme.colors.vis.euiColorVis1 : euiTheme.colors.vis.euiColorVis6, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_locations.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_locations.tsx index 40ef9d6ca75d7..6c7c9f51299a2 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_locations.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/monitor_locations.tsx @@ -5,10 +5,10 @@ * 2.0. */ +import { useEuiTheme } from '@elastic/eui'; import React from 'react'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { OverviewStatusState, ServiceLocations } from '../../../../../../../common/runtime_types'; import { LocationStatusBadges } from '../../../common/components/location_status_badges'; -import { ServiceLocations, OverviewStatusState } from '../../../../../../../common/runtime_types'; interface Props { locations: ServiceLocations; @@ -17,22 +17,21 @@ interface Props { } export const MonitorLocations = ({ locations, monitorId, overviewStatus }: Props) => { - const { - eui: { euiColorVis9, euiColorVis0, euiColorDisabled }, - } = useTheme(); + const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const locationsToDisplay = locations.map((loc) => { const locById = `${monitorId}-${loc.id}`; let status: string = 'unknown'; - let color = euiColorDisabled; + let color = euiTheme.colors.disabled; if (overviewStatus?.downConfigs[locById]) { status = 'down'; - color = euiColorVis9; + color = isAmsterdam ? euiTheme.colors.vis.euiColorVis9 : euiTheme.colors.danger; } else if (overviewStatus?.upConfigs[locById]) { status = 'up'; - color = euiColorVis0; + color = isAmsterdam ? euiTheme.colors.vis.euiColorVis0 : euiTheme.colors.success; } return { diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx index 18eaa74b4cdcd..cdd92fbd9913c 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs.tsx @@ -5,23 +5,21 @@ * 2.0. */ -import React from 'react'; - -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; - -import { useMonitorFilters } from '../../hooks/use_monitor_filters'; -import { useRefreshedRange } from '../../../../hooks'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React from 'react'; import { ClientPluginsStart } from '../../../../../../plugin'; -import * as labels from '../labels'; +import { useRefreshedRange } from '../../../../hooks'; +import { useMonitorFilters } from '../../hooks/use_monitor_filters'; import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; +import * as labels from '../labels'; export const MonitorTestRunsCount = () => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana().services; - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); const { from, to } = useRefreshedRange(30, 'days'); const filters = useMonitorFilters({}); @@ -42,7 +40,7 @@ export const MonitorTestRunsCount = () => { dataType: 'synthetics', selectedMetricField: 'monitor_total_runs', name: labels.TEST_RUNS_LABEL, - color: theme.eui.euiColorVis1, + color: euiTheme.colors.vis.euiColorVis1, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx index 8713dfb77769e..9cebe452458ca 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_stats/monitor_test_runs_sparkline.tsx @@ -5,23 +5,20 @@ * 2.0. */ -import React, { useMemo } from 'react'; - +import { useEuiTheme } from '@elastic/eui'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; - -import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; -import { useMonitorFilters } from '../../hooks/use_monitor_filters'; -import { useRefreshedRange } from '../../../../hooks'; +import React, { useMemo } from 'react'; import { ClientPluginsStart } from '../../../../../../plugin'; +import { useRefreshedRange } from '../../../../hooks'; +import { useMonitorFilters } from '../../hooks/use_monitor_filters'; +import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; import * as labels from '../labels'; export const MonitorTestRunsSparkline = () => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana().services; - - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); const { from, to } = useRefreshedRange(30, 'days'); const filters = useMonitorFilters({}); @@ -39,12 +36,12 @@ export const MonitorTestRunsSparkline = () => { selectedMetricField: 'total_test_runs', filters, name: labels.TEST_RUNS_LABEL, - color: theme.eui.euiColorVis1, + color: euiTheme.colors.vis.euiColorVis1, operationType: 'count', }, ]; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [from, theme.eui.euiColorVis1, to]); + }, [from, euiTheme.colors.vis.euiColorVis1, to]); return ( , - isEnabled: boolean, - status?: string -) => { +export const getColor = (euiTheme: EuiThemeComputed, isEnabled: boolean, status?: string) => { if (!isEnabled) { - return theme.eui.euiColorLightestShade; + return euiTheme.colors.lightestShade; } + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; + switch (status) { case 'down': - return theme.eui.euiColorVis9_behindText; + return isAmsterdam ? euiPaletteColorBlindBehindText()[9] : euiTheme.colors.danger; case 'up': - return theme.eui.euiColorVis0_behindText; + return isAmsterdam ? euiPaletteColorBlindBehindText()[0] : euiTheme.colors.success; case 'unknown': - return theme.eui.euiColorGhost; + return euiTheme.colors.ghost; default: - return theme.eui.euiColorVis0_behindText; + return isAmsterdam ? euiPaletteColorBlindBehindText()[0] : euiTheme.colors.success; } }; @@ -65,6 +68,7 @@ export const MetricItem = ({ style?: React.CSSProperties; onClick: (params: FlyoutParamProps) => void; }) => { + const { euiTheme } = useEuiTheme(); const trendData = useSelector(selectOverviewTrends)[monitor.configId + monitor.locationId]; const [isPopoverOpen, setIsPopoverOpen] = useState(false); const isErrorPopoverOpen = useSelector(selectErrorPopoverState); @@ -73,8 +77,8 @@ export const MetricItem = ({ configId: monitor.configId, locationId: monitor.locationId, }); - const theme = useTheme(); + const { charts } = useKibana().services; const testInProgress = useSelector(manualTestRunInProgressSelector(monitor.configId)); const dispatch = useDispatch(); @@ -132,7 +136,7 @@ export const MetricItem = ({ }); } }} - baseTheme={useElasticChartsTheme()} + baseTheme={charts.theme.useChartsBaseTheme()} locale={i18n.getLocale()} /> ) : undefined, valueFormatter: (d: number) => formatDuration(d), - color: getColor(theme, monitor.isEnabled, status), + color: getColor(euiTheme, monitor.isEnabled, status), body: , }, ], diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx index 876105ffe7c73..ec3f8c1c6201a 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx @@ -23,21 +23,18 @@ import { EuiPanel, EuiSpacer, EuiTitle, + useEuiTheme, useIsWithinMaxBreakpoint, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; -import { FlyoutParamProps } from './types'; import { useKibanaSpace } from '../../../../../../hooks/use_kibana_space'; -import { useOverviewStatus } from '../../hooks/use_overview_status'; -import { MonitorDetailsPanel } from '../../../common/components/monitor_details_panel'; import { ClientPluginsStart } from '../../../../../../plugin'; +import { useMonitorDetail } from '../../../../hooks/use_monitor_detail'; +import { useMonitorDetailLocator } from '../../../../hooks/use_monitor_detail_locator'; import { LocationsStatus, useStatusByLocation } from '../../../../hooks/use_status_by_location'; -import { MonitorEnabled } from '../../management/monitor_list_table/monitor_enabled'; -import { ActionsPopover } from './actions_popover'; import { getMonitorAction, selectMonitorUpsertStatus, @@ -47,11 +44,14 @@ import { selectSyntheticsMonitorLoading, setFlyoutConfig, } from '../../../../state'; -import { useMonitorDetail } from '../../../../hooks/use_monitor_detail'; -import { ConfigKey, EncryptedSyntheticsMonitor, OverviewStatusMetaData } from '../types'; -import { useMonitorDetailLocator } from '../../../../hooks/use_monitor_detail_locator'; -import { MonitorStatus } from '../../../common/components/monitor_status'; +import { MonitorDetailsPanel } from '../../../common/components/monitor_details_panel'; import { MonitorLocationSelect } from '../../../common/components/monitor_location_select'; +import { MonitorStatus } from '../../../common/components/monitor_status'; +import { useOverviewStatus } from '../../hooks/use_overview_status'; +import { MonitorEnabled } from '../../management/monitor_list_table/monitor_enabled'; +import { ConfigKey, EncryptedSyntheticsMonitor, OverviewStatusMetaData } from '../types'; +import { ActionsPopover } from './actions_popover'; +import { FlyoutParamProps } from './types'; interface Props { configId: string; @@ -89,7 +89,7 @@ function DetailFlyoutDurationChart({ | 'previousDurationChartFrom' | 'previousDurationChartTo' >) { - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); const { exploratoryView: { ExploratoryViewEmbeddable }, @@ -108,7 +108,7 @@ function DetailFlyoutDurationChart({ attributes={[ { seriesType: 'area', - color: theme?.eui?.euiColorVis1, + color: euiTheme.colors.vis.euiColorVis1, time: { from: currentDurationChartFrom ?? DEFAULT_DURATION_CHART_FROM, to: currentDurationChartTo ?? DEFAULT_CURRENT_DURATION_CHART_TO, @@ -130,7 +130,7 @@ function DetailFlyoutDurationChart({ }, { seriesType: 'line', - color: theme?.eui?.euiColorVis7, + color: euiTheme.colors.vis.euiColorVis7, time: { from: previousDurationChartFrom ?? DEFAULT_PREVIOUS_DURATION_CHART_FROM, to: previousDurationChartTo ?? DEFAULT_PREVIOUS_DURATION_CHART_TO, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx index 8174b7fb63f73..fd58f6898edd4 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_alerts.tsx @@ -5,23 +5,30 @@ * 2.0. */ -import React, { useMemo } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiSpacer, + EuiTitle, + euiPaletteColorBlindBehindText, + useEuiTheme, +} from '@elastic/eui'; +import { RECORDS_FIELD } from '@kbn/exploratory-view-plugin/public'; import { i18n } from '@kbn/i18n'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; +import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; -import { RECORDS_FIELD } from '@kbn/exploratory-view-plugin/public'; -import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; import { SYNTHETICS_STATUS_RULE, SYNTHETICS_TLS_RULE, } from '../../../../../../../common/constants/synthetics_alerts'; -import { useMonitorFilters } from '../../hooks/use_monitor_filters'; +import { ClientPluginsStart } from '../../../../../../plugin'; +import { useGetUrlParams, useRefreshedRange } from '../../../../hooks'; import { selectOverviewStatus } from '../../../../state/overview_status'; import { AlertsLink } from '../../../common/links/view_alerts'; -import { useRefreshedRange, useGetUrlParams } from '../../../../hooks'; -import { ClientPluginsStart } from '../../../../../../plugin'; +import { useMonitorFilters } from '../../hooks/use_monitor_filters'; +import { useMonitorQueryFilters } from '../../hooks/use_monitor_query_filters'; export const useMonitorQueryIds = () => { const { status } = useSelector(selectOverviewStatus); @@ -62,7 +69,8 @@ export const OverviewAlerts = () => { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana().services; - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const filters = useMonitorFilters({ forAlerts: true }); const { locations } = useGetUrlParams(); @@ -99,7 +107,9 @@ export const OverviewAlerts = () => { { field: 'kibana.alert.status', values: ['active', 'recovered'] }, ...filters, ], - color: theme.eui.euiColorVis1, + color: isAmsterdam + ? euiTheme.colors.vis.euiColorVis1 + : euiTheme.colors.vis.euiColorVis6, }, ]} /> @@ -129,7 +139,9 @@ export const OverviewAlerts = () => { { field: 'kibana.alert.status', values: ['active', 'recovered'] }, ...filters, ], - color: theme.eui.euiColorVis1_behindText, + color: isAmsterdam + ? euiPaletteColorBlindBehindText()[1] + : euiTheme.colors.vis.euiColorVis6, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx index e7365ccc7520c..4def3c4b80073 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_count.tsx @@ -5,12 +5,13 @@ * 2.0. */ +import { useEuiTheme } from '@elastic/eui'; +import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useMemo } from 'react'; -import { ReportTypes } from '@kbn/exploratory-view-plugin/public'; -import { useMonitorFilters } from '../../../hooks/use_monitor_filters'; -import { ERRORS_LABEL } from '../../../../monitor_details/monitor_summary/monitor_errors_count'; import { ClientPluginsStart } from '../../../../../../../plugin'; +import { ERRORS_LABEL } from '../../../../monitor_details/monitor_summary/monitor_errors_count'; +import { useMonitorFilters } from '../../../hooks/use_monitor_filters'; import { useMonitorQueryFilters } from '../../../hooks/use_monitor_query_filters'; interface MonitorErrorsCountProps { @@ -22,6 +23,8 @@ export const OverviewErrorsCount = ({ from, to }: MonitorErrorsCountProps) => { const { exploratoryView: { ExploratoryViewEmbeddable }, } = useKibana().services; + const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const filters = useMonitorFilters({}); @@ -44,6 +47,7 @@ export const OverviewErrorsCount = ({ from, to }: MonitorErrorsCountProps) => { selectedMetricField: 'monitor_errors', name: ERRORS_LABEL, filters, + color: isAmsterdam ? euiTheme.colors.vis.euiColorVis1 : euiTheme.colors.vis.euiColorVis6, }, ]} /> diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx index 41d6a5bc34d7d..b6956abc02d81 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/overview_errors/overview_errors_sparklines.tsx @@ -7,7 +7,7 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import React, { useMemo } from 'react'; -import { useEuiTheme } from '@elastic/eui'; +import { euiPaletteColorBlindBehindText, useEuiTheme } from '@elastic/eui'; import { ERRORS_LABEL } from '../../../../monitor_details/monitor_summary/monitor_errors_count'; import { ClientPluginsStart } from '../../../../../../../plugin'; import { useMonitorFilters } from '../../../hooks/use_monitor_filters'; @@ -24,6 +24,7 @@ export const OverviewErrorsSparklines = ({ from, to }: Props) => { const filters = useMonitorFilters({}); const { euiTheme } = useEuiTheme(); + const isAmsterdam = euiTheme.flags.hasVisColorAdjustment; const time = useMemo(() => ({ from, to }), [from, to]); @@ -45,7 +46,9 @@ export const OverviewErrorsSparklines = ({ from, to }: Props) => { dataType: 'synthetics', selectedMetricField: 'monitor_errors', name: ERRORS_LABEL, - color: euiTheme.colors.danger, + color: isAmsterdam + ? euiPaletteColorBlindBehindText()[1] + : euiTheme.colors.vis.euiColorVis6, operationType: 'unique_count', filters, }, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx index 76a413cd6bcae..8e6e902652a3a 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_objects/color_palette.tsx @@ -5,17 +5,17 @@ * 2.0. */ -import React, { useState, useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, - EuiText, - EuiSkeletonRectangle, EuiIcon, + EuiSkeletonRectangle, + EuiText, EuiToolTip, + useEuiTheme, } from '@elastic/eui'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; import { i18n } from '@kbn/i18n'; +import React, { useEffect, useState } from 'react'; import { colourPalette } from '../common/network_data/data_formatting'; export const ColorPalette = ({ @@ -116,7 +116,7 @@ export const ColorPaletteFlexItem = ({ percent: number; loading: boolean; }) => { - const { eui } = useTheme(); + const { euiTheme } = useEuiTheme(); const [value, setVal] = useState(0); @@ -142,11 +142,11 @@ export const ColorPaletteFlexItem = ({ - + )[mimeType], diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/breakdown_legend.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/breakdown_legend.tsx index 16d3c05cc319a..f8bdff084b467 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/breakdown_legend.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_timing_breakdown/breakdown_legend.tsx @@ -5,21 +5,18 @@ * 2.0. */ +import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiSpacer, useEuiTheme } from '@elastic/eui'; import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiSpacer } from '@elastic/eui'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; - import { ThresholdIndicator } from '../../common/components/thershold_indicator'; -import { useNetworkTimingsPrevious24Hours } from '../hooks/use_network_timings_prev'; import { formatMillisecond } from '../common/network_data/data_formatting'; import { useNetworkTimings } from '../hooks/use_network_timings'; +import { useNetworkTimingsPrevious24Hours } from '../hooks/use_network_timings_prev'; export const BreakdownLegend = () => { const networkTimings = useNetworkTimings(); const { timingsWithLabels: prevTimingsWithLabels, loading } = useNetworkTimingsPrevious24Hours(); - - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); return ( <> @@ -28,17 +25,13 @@ export const BreakdownLegend = () => { {networkTimings.timingsWithLabels.map(({ label, value }, index) => { const prevValueItem = prevTimingsWithLabels?.find((prev) => prev.label === label); const prevValue = prevValueItem?.value; + // @ts-ignore + const color = euiTheme.colors.vis[`euiColorVis${index + 1}`]; return ( - )[`euiColorVis${index + 1}`] - } - > - {label} - + {label} { const networkTimings = useNetworkTimings(); - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); if (!networkTimings) { return ; @@ -87,9 +87,9 @@ export const NetworkTimingsDonut = () => { nodeLabel: (d: Datum) => d, shape: { fillColor: (dataName, index) => { - return (theme.eui as unknown as Record)[ - `euiColorVis${index + 1}` - ]; + // @ts-ignore + const color = euiTheme.colors.vis[`euiColorVis${index + 1}`]; + return color; }, }, }, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_markers.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_markers.tsx index e3f2877a159bd..e1acf22f9bd73 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_markers.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/components/step_details_page/step_waterfall_chart/waterfall/waterfall_marker/waterfall_markers.tsx @@ -5,15 +5,14 @@ * 2.0. */ -import React, { useMemo } from 'react'; import { AnnotationDomainType, LineAnnotation } from '@elastic/charts'; +import { EuiThemeComputed, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useTheme } from '@kbn/observability-shared-plugin/public'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; - +import React, { useMemo } from 'react'; +import { formatMillisecond } from '../../../common/network_data/data_formatting'; import { MarkerItems, useWaterfallContext } from '../context/waterfall_context'; import { WaterfallMarkerIcon } from './waterfall_marker_icon'; -import { formatMillisecond } from '../../../common/network_data/data_formatting'; export const FIELD_SYNTHETICS_LCP = 'browser.experience.lcp.us'; export const FIELD_SYNTHETICS_FCP = 'browser.experience.fcp.us'; @@ -22,7 +21,7 @@ export const FIELD_SYNTHETICS_DCL = 'browser.experience.dcl.us'; export const LAYOUT_SHIFT = 'layoutShift'; export function WaterfallChartMarkers() { - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); const { markerItems, showCustomMarks } = useWaterfallContext(); const markerItemsByOffset = useMemo( @@ -41,7 +40,7 @@ export function WaterfallChartMarkers() { // Remove unrecognized marks e.g. custom marks if `showCustomMarks` is false const vitalMarkers = showCustomMarks ? items - : items.filter(({ id }) => getMarkersInfo(id, theme) !== undefined); + : items.filter(({ id }) => getMarkersInfo(id, euiTheme) !== undefined); const hasMultipleMarksAtOffset = vitalMarkers.some(({ id }) => id !== LAYOUT_SHIFT); const isLastOffsetTooClose = lastOffset && Math.abs(offset - lastOffset) < 100; // 100ms @@ -67,21 +66,21 @@ export function WaterfallChartMarkers() { .map(({ id }) => id) .filter((id, index, arr) => arr.indexOf(id) === index); - const label = uniqueIds.map((id) => getMarkersInfo(id, theme)?.label ?? id).join(' / '); + const label = uniqueIds.map((id) => getMarkersInfo(id, euiTheme)?.label ?? id).join(' / '); const id = uniqueIds[0]; - const markersInfo = getMarkersInfo(id, theme); + const markersInfo = getMarkersInfo(id, euiTheme); return { id, offset, label, field: markersInfo?.field ?? '', - color: markersInfo?.color ?? theme.eui.euiColorMediumShade, + color: markersInfo?.color ?? euiTheme.colors.mediumShade, strokeWidth: markersInfo?.strokeWidth ?? 1, dash: markersInfo?.dash, }; }); - }, [markerItemsByOffset, showCustomMarks, theme]); + }, [markerItemsByOffset, showCustomMarks, euiTheme]); if (!markerItems) { return null; @@ -113,7 +112,7 @@ export function WaterfallChartMarkers() { dash, }, }} - zIndex={theme.eui.euiZLevel0} + zIndex={Number(euiTheme.levels.content)} /> ); })} @@ -121,12 +120,12 @@ export function WaterfallChartMarkers() { ); } -function getMarkersInfo(id: string, theme: ReturnType) { +function getMarkersInfo(id: string, euiTheme: EuiThemeComputed) { switch (id) { case 'domContentLoaded': return { label: DOCUMENT_CONTENT_LOADED_LABEL, - color: theme.eui.euiColorMediumShade, + color: euiTheme.colors.mediumShade, field: FIELD_SYNTHETICS_DCL, strokeWidth: 1, dash: undefined, @@ -134,7 +133,7 @@ function getMarkersInfo(id: string, theme: ReturnType) { case 'firstContentfulPaint': return { label: FCP_LABEL, - color: theme.eui.euiColorMediumShade, + color: euiTheme.colors.mediumShade, field: FIELD_SYNTHETICS_FCP, strokeWidth: 1, dash: undefined, @@ -142,7 +141,7 @@ function getMarkersInfo(id: string, theme: ReturnType) { case 'largestContentfulPaint': return { label: LCP_LABEL, - color: theme.eui.euiColorMediumShade, + color: euiTheme.colors.mediumShade, field: FIELD_SYNTHETICS_LCP, strokeWidth: 1, dash: undefined, @@ -150,7 +149,7 @@ function getMarkersInfo(id: string, theme: ReturnType) { case 'layoutShift': return { label: LAYOUT_SHIFT_LABEL, - color: theme.eui.euiColorMediumShade, + color: euiTheme.colors.mediumShade, field: '', strokeWidth: 1, dash: [5, 5], @@ -158,7 +157,7 @@ function getMarkersInfo(id: string, theme: ReturnType) { case 'loadEvent': return { label: LOAD_EVENT_LABEL, - color: theme.eui.euiColorMediumShade, + color: euiTheme.colors.mediumShade, field: FIELD_SYNTHETICS_DOCUMENT_ONLOAD, strokeWidth: 1, dash: undefined, diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx index 5db1500878a0e..57389ce4f4d45 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/contexts/synthetics_shared_context.tsx @@ -41,6 +41,7 @@ export const SyntheticsSharedContext: React.FC< embeddable: startPlugins.embeddable, slo: startPlugins.slo, serverless: startPlugins.serverless, + charts: startPlugins.charts, }} > diff --git a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location.tsx b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location.tsx index 7d06f04130c05..a4706794ad26d 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location.tsx +++ b/x-pack/solutions/observability/plugins/synthetics/public/apps/synthetics/hooks/use_status_by_location.tsx @@ -5,16 +5,17 @@ * 2.0. */ -import { useEsSearch, useTheme } from '@kbn/observability-shared-plugin/public'; +import { useEuiTheme } from '@elastic/eui'; +import { useEsSearch } from '@kbn/observability-shared-plugin/public'; import { useMemo } from 'react'; -import { useLocations } from './use_locations'; -import { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../common/runtime_types'; +import { SYNTHETICS_INDEX_PATTERN, UNNAMED_LOCATION } from '../../../../common/constants'; import { EXCLUDE_RUN_ONCE_FILTER, FINAL_SUMMARY_FILTER, } from '../../../../common/constants/client_defaults'; -import { SYNTHETICS_INDEX_PATTERN, UNNAMED_LOCATION } from '../../../../common/constants'; +import { EncryptedSyntheticsSavedMonitor, Ping } from '../../../../common/runtime_types'; import { useSyntheticsRefreshContext } from '../contexts'; +import { useLocations } from './use_locations'; export type LocationsStatus = Array<{ status: string; id: string; label: string; color: string }>; @@ -25,7 +26,7 @@ export function useStatusByLocation({ configId: string; monitorLocations?: EncryptedSyntheticsSavedMonitor['locations']; }) { - const theme = useTheme(); + const { euiTheme } = useEuiTheme(); const { lastRefresh } = useSyntheticsRefreshContext(); @@ -76,11 +77,11 @@ export function useStatusByLocation({ const getColor = (status: string) => { switch (status) { case 'up': - return theme.eui.euiColorVis0; + return euiTheme.colors.success; case 'down': - return theme.eui.euiColorVis9; + return euiTheme.colors.vis.euiColorVis6; default: - return 'subdued'; + return euiTheme.colors.backgroundBaseSubdued; } }; @@ -112,7 +113,8 @@ export function useStatusByLocation({ data?.aggregations?.locations.buckets, loading, monitorLocations, - theme.eui.euiColorVis0, - theme.eui.euiColorVis9, + euiTheme.colors.success, + euiTheme.colors.vis.euiColorVis6, + euiTheme.colors.backgroundBaseSubdued, ]); } diff --git a/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts b/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts index e8fbedfa0ecb1..c12d9161cf0b1 100644 --- a/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/synthetics/public/plugin.ts @@ -61,6 +61,7 @@ import type { UiActionsSetup } from '@kbn/ui-actions-plugin/public'; import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public'; import { DashboardStart, DashboardSetup } from '@kbn/dashboard-plugin/public'; import { SLOPublicStart } from '@kbn/slo-plugin/public'; +import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import { registerSyntheticsEmbeddables } from './apps/embeddables/register_embeddables'; import { kibanaService } from './utils/kibana_service'; import { PLUGIN } from '../common/constants/plugin'; @@ -114,6 +115,7 @@ export interface ClientPluginsStart { slo?: SLOPublicStart; presentationUtil: PresentationUtilPluginStart; dashboard: DashboardStart; + charts: ChartsPluginStart; } export interface SyntheticsPluginServices extends Partial { diff --git a/x-pack/solutions/observability/plugins/synthetics/tsconfig.json b/x-pack/solutions/observability/plugins/synthetics/tsconfig.json index fef7efd29865d..727a72bb2f450 100644 --- a/x-pack/solutions/observability/plugins/synthetics/tsconfig.json +++ b/x-pack/solutions/observability/plugins/synthetics/tsconfig.json @@ -108,6 +108,7 @@ "@kbn/core-http-server-utils", "@kbn/apm-data-access-plugin", "@kbn/charts-theme", + "@kbn/charts-plugin", "@kbn/response-ops-rule-params" ], "exclude": ["target/**/*"] From c2d20d3b4e4cfafb5216fc36484b2446ba591255 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Fri, 3 Jan 2025 16:57:12 +0100 Subject: [PATCH 08/43] [ES|QL] Updates functions metadata (#205472) ## Summary Manual update of the function metadata as the script failed on Monday. --- .../generated/aggregation_functions.ts | 59 +++ .../definitions/generated/scalar_functions.ts | 489 +++++++++++++++++- .../validation.functions.full_text.test.ts | 11 - 3 files changed, 543 insertions(+), 16 deletions(-) diff --git a/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts b/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts index 4bfdbb6a16395..22af81a95bc5c 100644 --- a/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts +++ b/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/aggregation_functions.ts @@ -1341,6 +1341,64 @@ const stCentroidAggDefinition: FunctionDefinition = { examples: ['FROM airports\n| STATS centroid=ST_CENTROID_AGG(location)'], }; +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const stExtentAggDefinition: FunctionDefinition = { + type: 'agg', + name: 'st_extent_agg', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.st_extent_agg', { + defaultMessage: + 'Calculate the spatial extent over a field with geometry type. Returns a bounding box for all values of the field.', + }), + preview: false, + alias: undefined, + signatures: [ + { + params: [ + { + name: 'field', + type: 'cartesian_point', + optional: false, + }, + ], + returnType: 'cartesian_shape', + }, + { + params: [ + { + name: 'field', + type: 'cartesian_shape', + optional: false, + }, + ], + returnType: 'cartesian_shape', + }, + { + params: [ + { + name: 'field', + type: 'geo_point', + optional: false, + }, + ], + returnType: 'geo_shape', + }, + { + params: [ + { + name: 'field', + type: 'geo_shape', + optional: false, + }, + ], + returnType: 'geo_shape', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics'], + supportedOptions: undefined, + validate: undefined, + examples: ['FROM airports\n| WHERE country == "India"\n| STATS extent = ST_EXTENT_AGG(location)'], +}; + // Do not edit this manually... generated by scripts/generate_function_definitions.ts const stdDevDefinition: FunctionDefinition = { type: 'agg', @@ -1921,6 +1979,7 @@ export const aggregationFunctionDefinitions = [ minDefinition, percentileDefinition, stCentroidAggDefinition, + stExtentAggDefinition, stdDevDefinition, sumDefinition, topDefinition, diff --git a/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts b/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts index 20e3fe964722b..4837dfbc0413c 100644 --- a/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts +++ b/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/definitions/generated/scalar_functions.ts @@ -2177,6 +2177,84 @@ const greatestDefinition: FunctionDefinition = { examples: ['ROW a = 10, b = 20\n| EVAL g = GREATEST(a, b)'], }; +// Do not edit this manually... generated by scripts/generate_function_definitions.ts +const hashDefinition: FunctionDefinition = { + type: 'eval', + name: 'hash', + description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.hash', { + defaultMessage: + 'Computes the hash of the input using various algorithms such as MD5, SHA, SHA-224, SHA-256, SHA-384, SHA-512.', + }), + preview: false, + alias: undefined, + signatures: [ + { + params: [ + { + name: 'algorithm', + type: 'keyword', + optional: false, + }, + { + name: 'input', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'algorithm', + type: 'keyword', + optional: false, + }, + { + name: 'input', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'algorithm', + type: 'text', + optional: false, + }, + { + name: 'input', + type: 'keyword', + optional: false, + }, + ], + returnType: 'keyword', + }, + { + params: [ + { + name: 'algorithm', + type: 'text', + optional: false, + }, + { + name: 'input', + type: 'text', + optional: false, + }, + ], + returnType: 'keyword', + }, + ], + supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], + supportedOptions: ['by'], + validate: undefined, + examples: [], +}; + // Do not edit this manually... generated by scripts/generate_function_definitions.ts const hypotDefinition: FunctionDefinition = { type: 'eval', @@ -3446,7 +3524,7 @@ const matchDefinition: FunctionDefinition = { name: 'match', description: i18n.translate('kbn-esql-validation-autocomplete.esql.definitions.match', { defaultMessage: - 'Performs a match query on the specified field. Returns true if the provided query matches the row.', + 'Use `MATCH` to perform a match query on the specified field.\nUsing `MATCH` is equivalent to using the `match` query in the Elasticsearch Query DSL.\n\nMatch can be used on text fields, as well as other field types like boolean, dates, and numeric types.\n\nFor a simplified syntax, you can use the match operator `:` operator instead of `MATCH`.\n\n`MATCH` returns true if the provided query matches the row.', }), preview: true, alias: undefined, @@ -3455,8 +3533,88 @@ const matchDefinition: FunctionDefinition = { params: [ { name: 'field', + type: 'boolean', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'boolean', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'boolean', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'date', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'date', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'date', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', type: 'keyword', optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'date_nanos', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'date_nanos', + optional: false, fieldsOnly: true, }, { @@ -3471,13 +3629,61 @@ const matchDefinition: FunctionDefinition = { params: [ { name: 'field', + type: 'double', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'double', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'integer', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', type: 'keyword', optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'double', + optional: false, fieldsOnly: true, }, { name: 'query', - type: 'text', + type: 'long', optional: false, }, ], @@ -3487,7 +3693,39 @@ const matchDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'text', + type: 'integer', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'double', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'integer', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'integer', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'integer', optional: false, fieldsOnly: true, }, @@ -3503,14 +3741,254 @@ const matchDefinition: FunctionDefinition = { params: [ { name: 'field', - type: 'text', + type: 'integer', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'long', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'ip', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'ip', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'ip', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'keyword', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'double', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'long', optional: false, fieldsOnly: true, }, { name: 'query', + type: 'integer', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'long', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', type: 'text', optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'double', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'integer', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'long', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'unsigned_long', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'unsigned_long', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'version', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'keyword', + optional: false, + }, + ], + returnType: 'boolean', + }, + { + params: [ + { + name: 'field', + type: 'version', + optional: false, + fieldsOnly: true, + }, + { + name: 'query', + type: 'version', + optional: false, }, ], returnType: 'boolean', @@ -8370,7 +8848,7 @@ const termDefinition: FunctionDefinition = { supportedCommands: ['stats', 'inlinestats', 'metrics', 'eval', 'where', 'row', 'sort'], supportedOptions: ['by'], validate: undefined, - examples: ['from books \n| where term(author, "gabriel") \n| keep book_no, title\n| limit 3;'], + examples: ['FROM books \n| WHERE TERM(author, "gabriel") \n| KEEP book_no, title\n| LIMIT 3;'], }; // Do not edit this manually... generated by scripts/generate_function_definitions.ts @@ -10029,6 +10507,7 @@ export const scalarFunctionDefinitions = [ floorDefinition, fromBase64Definition, greatestDefinition, + hashDefinition, hypotDefinition, ipPrefixDefinition, kqlDefinition, diff --git a/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.functions.full_text.test.ts b/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.functions.full_text.test.ts index b7d962ffb4a42..e8d378d7c8935 100644 --- a/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.functions.full_text.test.ts +++ b/src/platform/packages/shared/kbn-esql-validation-autocomplete/src/validation/__tests__/validation.functions.full_text.test.ts @@ -31,17 +31,6 @@ describe('validation', () => { '[MATCH] function is only supported in WHERE commands', ]); }); - - it('shows errors if argument is not an index field ', async () => { - const { expectErrors } = await setup(); - await expectErrors( - 'FROM index | LIMIT 10 | where MATCH(`kubernetes.something.something`, "value")', - [ - 'Argument of [match] must be [keyword], found value [kubernetes.something.something] type [double]', - '[MATCH] function cannot be used after LIMIT', - ] - ); - }); }); describe('QSRT function', () => { it('no error if valid', async () => { From b851db30137aa51c3390dee903870685feb754ae Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Fri, 3 Jan 2025 11:06:00 -0500 Subject: [PATCH 09/43] [Synthetics] chore - synthetics unskip test in mki (#204273) ## Summary Unskip test that was previously failing in MKI. These tests have been run against MKI. Background on the test -- The test confirms that synthetics integration policies auto update when the package version is updated. It attempts to install an older version of the package and then run fleet setup to install a newer version of the package and trigger auto upgrade Why it failed -- The test expected a lower version of the package to be installed, but a more recent version was already installed. Therefore, when we went to create synthetics integration policies they were installed with the most recent version. How we fixed it -- Before this test, we now uninstall the Synthetics integration to ensure we can force install an earlier version. --- .../create_monitor_private_location.ts | 222 ++++++++---------- .../services/synthetics_private_location.ts | 22 +- 2 files changed, 105 insertions(+), 139 deletions(-) diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/create_monitor_private_location.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/create_monitor_private_location.ts index a940a5aec756b..c140ba7319e25 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/create_monitor_private_location.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/synthetics/create_monitor_private_location.ts @@ -8,6 +8,7 @@ import moment from 'moment'; import semver from 'semver'; import { v4 as uuidv4 } from 'uuid'; import { RoleCredentials } from '@kbn/ftr-common-functional-services'; +import { formatKibanaNamespace } from '@kbn/synthetics-plugin/common/formatters'; import { ConfigKey, HTTPFields, @@ -22,21 +23,17 @@ import rawExpect from 'expect'; import { DeploymentAgnosticFtrProviderContext } from '../../../ftr_provider_context'; import { getFixtureJson } from './helpers/get_fixture_json'; import { comparePolicies, getTestSyntheticsPolicy } from './sample_data/test_policy'; -import { - INSTALLED_VERSION, - PrivateLocationTestService, -} from '../../../services/synthetics_private_location'; +import { PrivateLocationTestService } from '../../../services/synthetics_private_location'; import { addMonitorAPIHelper, keyToOmitList, omitMonitorKeys } from './create_monitor'; import { SyntheticsMonitorTestService } from '../../../services/synthetics_monitor'; export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { describe('PrivateLocationAddMonitor', function () { - // see details: https://github.com/elastic/kibana/issues/204204 - this.tags(['failsOnMKI']); const kibanaServer = getService('kibanaServer'); - const supertestAPI = getService('supertestWithoutAuth'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const supertestWithAuth = getService('supertest'); const samlAuth = getService('samlAuth'); + const retry = getService('retry'); let testFleetPolicyID: string; let editorUser: RoleCredentials; @@ -49,7 +46,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const testPrivateLocations = new PrivateLocationTestService(getService); const addMonitorAPI = async (monitor: any, statusCode = 200) => { - return addMonitorAPIHelper(supertestAPI, monitor, statusCode, editorUser, samlAuth); + return addMonitorAPIHelper(supertestWithoutAuth, monitor, statusCode, editorUser, samlAuth); }; const deleteMonitor = async ( @@ -83,7 +80,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { it('add a test private location', async () => { privateLocations = await testPrivateLocations.setTestLocations([testFleetPolicyID]); - const apiResponse = await supertestAPI + const apiResponse = await supertestWithoutAuth .get(SYNTHETICS_API_URLS.SERVICE_LOCATIONS) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -122,7 +119,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { newMonitor.name = invalidName; - const apiResponse = await supertestAPI + const apiResponse = await supertestWithoutAuth .post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -135,7 +132,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { message: `Invalid locations specified. Private Location(s) 'invalidLocation' not found. Available private locations are '${privateLocations[0].label}'`, }); - const apiGetResponse = await supertestAPI + const apiGetResponse = await supertestWithoutAuth .get(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + `?query="${invalidName}"`) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -203,7 +200,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }, }); - const apiResponse = await supertestAPI + const apiResponse = await supertestWithoutAuth .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -267,7 +264,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { ({ id }) => id !== testFleetPolicyID2 ); - await supertestAPI + await supertestWithoutAuth .put(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS + '/' + newMonitorId + '?internal=true') .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -317,100 +314,70 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(packagePolicy).eql(undefined); }); - // it('handles spaces', async () => { - // const username = 'admin'; - // const password = `${username}-password`; - // const roleName = 'uptime-role'; - // const SPACE_ID = `test-space-${uuidv4()}`; - // const SPACE_NAME = `test-space-name ${uuidv4()}`; - // let monitorId = ''; - // const monitor = { - // ...httpMonitorJson, - // name: `Test monitor ${uuidv4()}`, - // [ConfigKey.NAMESPACE]: 'default', - // locations: [ - // { - // id: testFleetPolicyID, - // agentPolicyId: testFleetPolicyID, - // label: 'Test private location 0', - // isServiceManaged: false, - // geo: { - // lat: 0, - // lon: 0, - // }, - // }, - // ], - // }; - - // try { - // await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); - // await security.role.create(roleName, { - // kibana: [ - // { - // feature: { - // uptime: ['all'], - // actions: ['all'], - // }, - // spaces: ['*'], - // }, - // ], - // }); - // await security.user.create(username, { - // password, - // roles: [roleName], - // full_name: 'a kibana user', - // }); - // const apiResponse = await supertestWithoutAuth - // .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`) - // .auth(username, password) - // .set('kbn-xsrf', 'true') - // .send(monitor) - // .expect(200); - - // const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body; - // expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); - - // expect(omit(apiResponse.body, keyToOmitList)).eql( - // omitMonitorKeys({ - // ...monitor, - // [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID), - // url: apiResponse.body.url, - // }) - // ); - // monitorId = apiResponse.body.id; - - // const policyResponse = await supertestWithAuth.get( - // '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - // ); - - // const packagePolicy = policyResponse.body.items.find( - // (pkgPolicy: PackagePolicy) => - // pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-${SPACE_ID}` - // ); - - // expect(packagePolicy.policy_id).eql(testFleetPolicyID); - // expect(packagePolicy.name).eql(`${monitor.name}-Test private location 0-${SPACE_ID}`); - // comparePolicies( - // packagePolicy, - // getTestSyntheticsPolicy({ - // name: monitor.name, - // id: monitorId, - // location: { id: testFleetPolicyID }, - // namespace: formatKibanaNamespace(SPACE_ID), - // spaceId: SPACE_ID, - // }) - // ); - // await supertestWithoutAuth - // .delete(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`) - // .auth(username, password) - // .set('kbn-xsrf', 'true') - // .send({ ids: [monitorId] }) - // .expect(200); - // } finally { - // await security.user.delete(username); - // await security.role.delete(roleName); - // } - // }); + it('handles spaces', async () => { + const SPACE_ID = `test-space-${uuidv4()}`; + const SPACE_NAME = `test-space-name ${uuidv4()}`; + const spaceScopedPrivateLocation = await testPrivateLocations.addTestPrivateLocation( + SPACE_ID + ); + let monitorId = ''; + const monitor = { + ...httpMonitorJson, + name: `Test monitor ${uuidv4()}`, + [ConfigKey.NAMESPACE]: 'default', + locations: [spaceScopedPrivateLocation], + }; + + await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME }); + const apiResponse = await supertestWithoutAuth + .post(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`) + .set(editorUser.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .set('kbn-xsrf', 'true') + .send(monitor); + + const { created_at: createdAt, updated_at: updatedAt } = apiResponse.body; + expect([createdAt, updatedAt].map((d) => moment(d).isValid())).eql([true, true]); + + expect(omit(apiResponse.body, keyToOmitList)).eql( + omitMonitorKeys({ + ...monitor, + [ConfigKey.NAMESPACE]: formatKibanaNamespace(SPACE_ID), + url: apiResponse.body.url, + }) + ); + monitorId = apiResponse.body.id; + + const policyResponse = await supertestWithAuth.get( + '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' + ); + + const packagePolicy = policyResponse.body.items.find( + (pkgPolicy: PackagePolicy) => + pkgPolicy.id === monitorId + '-' + spaceScopedPrivateLocation.id + `-${SPACE_ID}` + ); + + expect(packagePolicy.policy_id).eql(spaceScopedPrivateLocation.id); + expect(packagePolicy.name).eql( + `${monitor.name}-${spaceScopedPrivateLocation.label}-${SPACE_ID}` + ); + comparePolicies( + packagePolicy, + getTestSyntheticsPolicy({ + name: monitor.name, + id: monitorId, + location: { id: spaceScopedPrivateLocation.id, name: spaceScopedPrivateLocation.label }, + namespace: formatKibanaNamespace(SPACE_ID), + spaceId: SPACE_ID, + }) + ); + await supertestWithoutAuth + .delete(`/s/${SPACE_ID}${SYNTHETICS_API_URLS.SYNTHETICS_MONITORS}`) + .set(editorUser.apiKeyHeader) + .set(samlAuth.getInternalRequestHeader()) + .send({ ids: [monitorId] }) + .expect(200); + }); it('handles is_tls_enabled true', async () => { let monitorId = ''; @@ -430,7 +397,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }; try { - const apiResponse = await supertestAPI + const apiResponse = await supertestWithoutAuth .post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -479,7 +446,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }; try { - const apiResponse = await supertestAPI + const apiResponse = await supertestWithoutAuth .post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -510,23 +477,21 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { }); it('handles auto upgrading policies', async () => { + // force a lower version + const lowerVersion = '1.1.1'; + await testPrivateLocations.installSyntheticsPackage({ version: lowerVersion }); let monitorId = ''; + const privateLocation = await testPrivateLocations.addTestPrivateLocation(); const monitor = { ...httpMonitorJson, name: `Test monitor ${uuidv4()}`, [ConfigKey.NAMESPACE]: 'default', - locations: [ - { - id: testFleetPolicyID, - label: privateLocations[0].label, - isServiceManaged: false, - }, - ], + locations: [privateLocation], }; try { - const apiResponse = await supertestAPI + const apiResponse = await supertestWithoutAuth .post(SYNTHETICS_API_URLS.SYNTHETICS_MONITORS) .set(editorUser.apiKeyHeader) .set(samlAuth.getInternalRequestHeader()) @@ -540,20 +505,21 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { const packagePolicy = policyResponse.body.items.find( (pkgPolicy: PackagePolicy) => - pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default` + pkgPolicy.id === monitorId + '-' + privateLocation.id + `-default` ); - expect(packagePolicy.package.version).eql(INSTALLED_VERSION); - + expect(packagePolicy.package.version).eql(lowerVersion); await supertestWithAuth.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200); - const policyResponseAfterUpgrade = await supertestWithAuth.get( - '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' - ); - const packagePolicyAfterUpgrade = policyResponseAfterUpgrade.body.items.find( - (pkgPolicy: PackagePolicy) => - pkgPolicy.id === monitorId + '-' + testFleetPolicyID + `-default` - ); - expect(semver.gte(packagePolicyAfterUpgrade.package.version, INSTALLED_VERSION)).eql(true); + await retry.tryForTime(60 * 1000, async () => { + const policyResponseAfterUpgrade = await supertestWithAuth.get( + '/api/fleet/package_policies?page=1&perPage=2000&kuery=ingest-package-policies.package.name%3A%20synthetics' + ); + const packagePolicyAfterUpgrade = policyResponseAfterUpgrade.body.items.find( + (pkgPolicy: PackagePolicy) => + pkgPolicy.id === monitorId + '-' + privateLocation.id + `-default` + ); + expect(semver.gt(packagePolicyAfterUpgrade.package.version, lowerVersion)).eql(true); + }); } finally { await deleteMonitor(monitorId); } diff --git a/x-pack/test/api_integration/deployment_agnostic/services/synthetics_private_location.ts b/x-pack/test/api_integration/deployment_agnostic/services/synthetics_private_location.ts index 8b3c95e0b9489..515c65db05277 100644 --- a/x-pack/test/api_integration/deployment_agnostic/services/synthetics_private_location.ts +++ b/x-pack/test/api_integration/deployment_agnostic/services/synthetics_private_location.ts @@ -12,7 +12,7 @@ import { SyntheticsPrivateLocations } from '@kbn/synthetics-plugin/common/runtim import { KibanaSupertestProvider } from '@kbn/ftr-common-functional-services'; import { DeploymentAgnosticFtrProviderContext } from '../ftr_provider_context'; -export const INSTALLED_VERSION = '1.1.1'; +export const INSTALLED_VERSION = '1.2.4'; export class PrivateLocationTestService { private supertestWithAuth: ReturnType; @@ -23,23 +23,23 @@ export class PrivateLocationTestService { this.retry = getService('retry'); } - async installSyntheticsPackage() { + async installSyntheticsPackage( + { version }: { version: string } = { version: INSTALLED_VERSION } + ) { await this.supertestWithAuth .post('/api/fleet/setup') .set('kbn-xsrf', 'true') .send() .expect(200); - const response = await this.supertestWithAuth - .get(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`) + // attempt to delete any existing package so we can install specific version + await this.supertestWithAuth + .delete(`/api/fleet/epm/packages/synthetics`) + .set('kbn-xsrf', 'true'); + await this.supertestWithAuth + .post(`/api/fleet/epm/packages/synthetics/${version}`) .set('kbn-xsrf', 'true') + .send({ force: true }) .expect(200); - if (response.body.item.status !== 'installed') { - await this.supertestWithAuth - .post(`/api/fleet/epm/packages/synthetics/${INSTALLED_VERSION}`) - .set('kbn-xsrf', 'true') - .send({ force: true }) - .expect(200); - } } async addTestPrivateLocation(spaceId?: string) { From 7d76276d8b1988599916f59052f7ebd9e815a3c5 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Fri, 3 Jan 2025 11:17:43 -0500 Subject: [PATCH 10/43] [Obs AI Assistant] Add route privilege tests for Serverless (#205210) Closes https://github.com/elastic/kibana/issues/204884 ## Summary This PR adds security and route privilege tests to the serverless test suite. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../observability_ai_assistant_api_client.ts | 19 +++- .../ai_assistant/tests/chat/chat.spec.ts | 67 ++++--------- .../tests/complete/complete.spec.ts | 19 ++++ .../tests/connectors/connectors.spec.ts | 16 ++- .../tests/conversations/conversations.spec.ts | 98 +++++++++++++++++++ .../knowledge_base/knowledge_base.spec.ts | 41 ++++++++ .../knowledge_base_setup.spec.ts | 21 +++- .../knowledge_base_status.spec.ts | 19 +++- .../knowledge_base_user_instructions.spec.ts | 27 +++++ 9 files changed, 268 insertions(+), 59 deletions(-) diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts index 3ac941501ae7c..566d06702872f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/common/observability_ai_assistant_api_client.ts @@ -40,7 +40,11 @@ export function getObservabilityAIAssistantApiClient({ } } -type ObservabilityAIAssistantApiClientKey = 'slsAdmin' | 'slsEditor' | 'slsUser'; +type ObservabilityAIAssistantApiClientKey = + | 'slsAdmin' + | 'slsEditor' + | 'slsUser' + | 'slsUnauthorized'; export type ObservabilityAIAssistantApiClient = Record< ObservabilityAIAssistantApiClientKey, @@ -195,18 +199,27 @@ export async function getObservabilityAIAssistantApiClientService({ const svlSharedConfig = getService('config'); const roleScopedSupertest = getService('roleScopedSupertest'); + // admin user const supertestAdminWithCookieCredentials: SupertestWithRoleScope = await roleScopedSupertest.getSupertestWithRoleScope('admin', { useCookieHeader: true, withInternalHeaders: true, }); + // editor user const supertestEditorWithCookieCredentials: SupertestWithRoleScope = await roleScopedSupertest.getSupertestWithRoleScope('editor', { useCookieHeader: true, withInternalHeaders: true, }); + // unauthorized user + const supertestUnauthorizedWithCookieCredentials: SupertestWithRoleScope = + await roleScopedSupertest.getSupertestWithRoleScope('viewer', { + useCookieHeader: false, + withInternalHeaders: true, + }); + return { // defaults to elastic_admin user when used without auth slsUser: await getObservabilityAIAssistantApiClient({ @@ -222,5 +235,9 @@ export async function getObservabilityAIAssistantApiClientService({ svlSharedConfig, supertestUserWithCookieCredentials: supertestEditorWithCookieCredentials, }), + slsUnauthorized: await getObservabilityAIAssistantApiClient({ + svlSharedConfig, + supertestUserWithCookieCredentials: supertestUnauthorizedWithCookieCredentials, + }), }; } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts index 2a25a309e8174..40f3db279135e 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/chat/chat.spec.ts @@ -23,6 +23,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const svlCommonApi = getService('svlCommonApi'); const log = getService('log'); const roleScopedSupertest = getService('roleScopedSupertest'); + const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); let supertestEditorWithCookieCredentials: SupertestWithRoleScope; @@ -170,57 +171,23 @@ export default function ApiTest({ getService }: FtrProviderContext) { ]); }); - it.skip('returns a useful error if the request fails', async () => { - const interceptor = proxy.intercept('conversation', () => true); - - const passThrough = new PassThrough(); - - supertestWithoutAuth - .post(CHAT_API_URL) - .set(roleAuthc.apiKeyHeader) - .set(internalReqHeader) - .set('kbn-xsrf', 'foo') - .send({ - name: 'my_api_call', - messages, - connectorId, - functions: [], - scopes: ['all'], - }) - .expect(200) - .pipe(passThrough); - - let data: string = ''; - - passThrough.on('data', (chunk) => { - data += chunk.toString('utf-8'); + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: `POST ${CHAT_API_URL}`, + params: { + body: { + name: 'my_api_call', + messages, + connectorId, + functions: [], + scopes: ['all'], + }, + }, + }) + .expect(403); }); - - const simulator = await interceptor.waitForIntercept(); - - await simulator.status(400); - - await simulator.rawWrite( - JSON.stringify({ - error: { - code: 'context_length_exceeded', - message: - "This model's maximum context length is 8192 tokens. However, your messages resulted in 11036 tokens. Please reduce the length of the messages.", - param: 'messages', - type: 'invalid_request_error', - }, - }) - ); - - await simulator.rawEnd(); - - await new Promise((resolve) => passThrough.on('end', () => resolve())); - - const response = JSON.parse(data.trim()); - - expect(response.error.message).to.be( - `Token limit reached. Token limit is 8192, but the current conversation has 11036 tokens.` - ); }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts index 4d3e1baed3f9b..47aa5018f810a 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/complete.spec.ts @@ -547,5 +547,24 @@ export default function ApiTest({ getService }: FtrProviderContext) { // todo it.skip('executes a function', async () => {}); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'POST /internal/observability_ai_assistant/chat/complete', + params: { + body: { + messages, + connectorId, + persist: false, + screenContexts: [], + scopes: ['all'], + }, + }, + }) + .expect(403); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts index 2096abe74e2e8..2c112f85fc219 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/connectors/connectors.spec.ts @@ -47,14 +47,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('Returns a 2xx for enterprise license', async () => { await observabilityAIAssistantAPIClient .slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/connectors', + endpoint: `GET /internal/observability_ai_assistant/connectors`, }) .expect(200); }); it('returns an empty list of connectors', async () => { const res = await observabilityAIAssistantAPIClient.slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/connectors', + endpoint: `GET /internal/observability_ai_assistant/connectors`, }); expect(res.body.length).to.be(0); @@ -70,7 +70,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); const res = await observabilityAIAssistantAPIClient.slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/connectors', + endpoint: `GET /internal/observability_ai_assistant/connectors`, }); expect(res.body.length).to.be(1); @@ -83,6 +83,16 @@ export default function ApiTest({ getService }: FtrProviderContext) { roleAuthc, }); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: `GET /internal/observability_ai_assistant/connectors`, + }) + .expect(403); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts index 7033e0660f5c6..6656ea0407817 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/conversations/conversations.spec.ts @@ -253,5 +253,103 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); }); + + describe('security roles and access privileges', () => { + describe('should deny access for users without the ai_assistant privilege', () => { + let createResponse: Awaited< + SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'> + >; + before(async () => { + createResponse = await observabilityAIAssistantAPIClient + .slsEditor({ + endpoint: 'POST /internal/observability_ai_assistant/conversation', + params: { + body: { + conversation: conversationCreate, + }, + }, + }) + .expect(200); + }); + + after(async () => { + await observabilityAIAssistantAPIClient + .slsEditor({ + endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + }, + }) + .expect(200); + }); + + it('POST /internal/observability_ai_assistant/conversation', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'POST /internal/observability_ai_assistant/conversation', + params: { + body: { + conversation: conversationCreate, + }, + }, + }) + .expect(403); + }); + + it('POST /internal/observability_ai_assistant/conversations', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'POST /internal/observability_ai_assistant/conversations', + }) + .expect(403); + }); + + it('PUT /internal/observability_ai_assistant/conversation/{conversationId}', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + body: { + conversation: merge(omit(conversationUpdate, 'conversation.id'), { + conversation: { id: createResponse.body.conversation.id }, + }), + }, + }, + }) + .expect(403); + }); + + it('GET /internal/observability_ai_assistant/conversation/{conversationId}', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + }, + }) + .expect(403); + }); + + it('DELETE /internal/observability_ai_assistant/conversation/{conversationId}', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + }, + }) + .expect(403); + }); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts index f156ba7e583b5..f5413de3f3ff5 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base.spec.ts @@ -212,6 +212,47 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(entries[0].title).to.eql('My title b'); }); }); + + describe('security roles and access privileges', () => { + describe('should deny access for users without the ai_assistant privilege', () => { + it('POST /internal/observability_ai_assistant/kb/entries/save', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'POST /internal/observability_ai_assistant/kb/entries/save', + params: { + body: { + id: 'my-doc-id-1', + title: 'My title', + text: 'My content', + }, + }, + }) + .expect(403); + }); + + it('GET /internal/observability_ai_assistant/kb/entries', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { query: '', sortBy: 'title', sortDirection: 'asc' }, + }, + }) + .expect(403); + }); + + it('DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', + params: { + path: { entryId: 'my-doc-id-1' }, + }, + }) + .expect(403); + }); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts index 87ceec18f1985..6f99a841f4d9f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -15,6 +15,8 @@ import { import { FtrProviderContext } from '../../common/ftr_provider_context'; +export const KNOWLEDGE_BASE_SETUP_API_URL = '/internal/observability_ai_assistant/kb/setup'; + export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); @@ -33,7 +35,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await createKnowledgeBaseModel(ml); const res = await observabilityAIAssistantAPIClient .slsAdmin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -52,7 +54,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns bad request if model cannot be installed', async () => { const res = await observabilityAIAssistantAPIClient .slsAdmin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -66,5 +68,20 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'No known trained model with model_id [pt_tiny_elser]' ); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, + params: { + query: { + model_id: TINY_ELSER.id, + }, + }, + }) + .expect(403); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts index 207badc1b855a..458cff655d404 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_status.spec.ts @@ -14,6 +14,9 @@ import { } from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/tests/knowledge_base/helpers'; import { AI_ASSISTANT_KB_INFERENCE_ID } from '@kbn/observability-ai-assistant-plugin/server/service/inference_endpoint'; import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { KNOWLEDGE_BASE_SETUP_API_URL } from './knowledge_base_setup.spec'; + +const KNOWLEDGE_BASE_STATUS_API_URL = '/internal/observability_ai_assistant/kb/status'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -27,7 +30,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await createKnowledgeBaseModel(ml); await observabilityAIAssistantAPIClient .slsAdmin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -45,7 +48,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns correct status after knowledge base is setup', async () => { const res = await observabilityAIAssistantAPIClient .slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/status', + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }) .expect(200); @@ -59,12 +62,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { const res = await observabilityAIAssistantAPIClient .slsEditor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/status', + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }) .expect(200); expect(res.body.enabled).to.be(true); expect(res.body.ready).to.be(false); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, + }) + .expect(403); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index 0557d43830bc0..e6d954529d759 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -329,5 +329,32 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(conversation.messages.length).to.be(5); }); }); + + describe('security roles and access privileges', () => { + describe('should deny access for users without the ai_assistant privilege', () => { + it('PUT /internal/observability_ai_assistant/kb/user_instructions', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', + params: { + body: { + id: 'test-instruction', + text: 'Test user instruction', + public: true, + }, + }, + }) + .expect(403); + }); + + it('GET /internal/observability_ai_assistant/kb/user_instructions', async () => { + await observabilityAIAssistantAPIClient + .slsUnauthorized({ + endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', + }) + .expect(403); + }); + }); + }); }); } From e51b581e25118571226e119d08a0b867b1fa6e49 Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:34:00 +0100 Subject: [PATCH 11/43] [Fleet] fix response schema for cancel upgrade (#205493) Noticed when cancelling an upgrade action that the API gave an error response saying that `agents` field is expected to be an array, but got undefined. Fixing the schema so that `agents` is optional. Verified by scheduling an upgrade in the future and cancelling from UI. image --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- oas_docs/bundle.json | 6 ++---- oas_docs/bundle.serverless.json | 6 ++---- oas_docs/output/kibana.serverless.yaml | 2 -- oas_docs/output/kibana.yaml | 2 -- .../plugins/shared/fleet/server/types/rest_spec/agent.ts | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 3e3d47df01661..84ce538ee1311 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -16520,8 +16520,7 @@ "type", "data", "created_at", - "ack_data", - "agents" + "ack_data" ], "type": "object" } @@ -18740,8 +18739,7 @@ "type", "data", "created_at", - "ack_data", - "agents" + "ack_data" ], "type": "object" } diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index b188ae0999b0d..68f4c181fc541 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -16520,8 +16520,7 @@ "type", "data", "created_at", - "ack_data", - "agents" + "ack_data" ], "type": "object" } @@ -18740,8 +18739,7 @@ "type", "data", "created_at", - "ack_data", - "agents" + "ack_data" ], "type": "object" } diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 07661ab564a48..b017c2d52ac6f 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -17250,7 +17250,6 @@ paths: - data - created_at - ack_data - - agents required: - item '400': @@ -17779,7 +17778,6 @@ paths: - data - created_at - ack_data - - agents required: - item '400': diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 4b896524ebbdc..561996f60c8af 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -19374,7 +19374,6 @@ paths: - data - created_at - ack_data - - agents required: - item '400': @@ -19896,7 +19895,6 @@ paths: - data - created_at - ack_data - - agents required: - item '400': diff --git a/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts b/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts index 9df9b73b3f49b..64d3f79b2e9e6 100644 --- a/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts +++ b/x-pack/platform/plugins/shared/fleet/server/types/rest_spec/agent.ts @@ -253,7 +253,7 @@ export const PostNewAgentActionResponseSchema = schema.object({ sent_at: schema.maybe(schema.string()), created_at: schema.string(), ack_data: schema.maybe(schema.any()), - agents: schema.arrayOf(schema.string()), + agents: schema.maybe(schema.arrayOf(schema.string())), namespaces: schema.maybe(schema.arrayOf(schema.string())), expiration: schema.maybe(schema.string()), start_time: schema.maybe(schema.string()), From c6b0a31d8ec6d423a8071f50a22e55acedd0dee0 Mon Sep 17 00:00:00 2001 From: Tiago Vila Verde Date: Fri, 3 Jan 2025 16:43:16 +0000 Subject: [PATCH 12/43] [Entity Store] [Asset Inventory] Universal entity definition (#202888) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary This PR adds a universal entity definition. A universal entity uses `related.entity` as an identifier field and includes an extra processor step that parses the field `entities.keyword` and extracts all the entities in said field (whose original data comes from `related.entities`). See this [doc](https://docs.google.com/document/d/1D8xDtn3HHP65i1Y3eIButacD6ZizyjZZRJB7mxlXzQY/edit?tab=t.0#heading=h.9fz3qtlfzjg7) for more details. To accomplish this, we need to allow describing an entity along with extra entity store resources required for that entity's engine. This PR reworks the current entity store by introducing an `Entity Description`, which has all that required information. From it, we can build an `EntityEngineDescription` which adds all the needed data that must be computed (as opposed to hardcoded) and is then used to generate all the resources needed for that Entity's engine (entity definition, pipeline, enrich policy, index mappings, etc). EntityDescriptions This required a refactoring of the current `UnitedEntityDefinition`, which has now been removed in favour of more contextual functions for all the different parts. The intention is to decouple the Entity Description schema from the schemas required for field retention, entity manager and pipeline. We can then freely expand on our Entity Description as required, and simply alter the conversion functions when needed. ## How to test 1. On a fresh ES cluster, add some entity data * For hosts and user, use the [security documents generator](https://github.com/elastic/security-documents-generator) * For universal, there are a few more steps: 1. Create the `entity.keyword` builder pipeline 2. Add it to a index template 3. Post some docs to the corresponding index 2. Initialise the universal entity engine via: `POST kbn:/api/entity_store/engines/universal/init {}` * Note that using the UI does not work, as we've specifically removed the Universal engine from the normal Entity Store workflow 3. Check the status of the store is `running` via `GET kbn:/api/entity_store/status` 4. Once the transform runs, you can query `GET entities*/_search` to see the created entities Note that universal entities do not show up in the dashboard Entities List. ### Code to ingest data
Pipeline ```js PUT _ingest/pipeline/entities-keyword-builder { "description":"Serialize entities.metadata into a keyword field", "processors":[ { "script":{ "lang":"painless", "source":""" String jsonFromMap(Map map) { StringBuilder json = new StringBuilder("{"); boolean first = true; for (entry in map.entrySet()) { if (!first) { json.append(","); } first = false; String key = entry.getKey().replace("\"", "\\\""); Object value = entry.getValue(); json.append("\"").append(key).append("\":"); if (value instanceof String) { String escapedValue = ((String) value).replace("\"", "\\\"").replace("=", ":"); json.append("\"").append(escapedValue).append("\""); } else if (value instanceof Map) { json.append(jsonFromMap((Map) value)); } else if (value instanceof List) { json.append(jsonFromList((List) value)); } else if (value instanceof Boolean || value instanceof Number) { json.append(value.toString()); } else { // For other types, treat as string String escapedValue = value.toString().replace("\"", "\\\"").replace("=", ":"); json.append("\"").append(escapedValue).append("\""); } } json.append("}"); return json.toString(); } String jsonFromList(List list) { StringBuilder json = new StringBuilder("["); boolean first = true; for (item in list) { if (!first) { json.append(","); } first = false; if (item instanceof String) { String escapedItem = ((String) item).replace("\"", "\\\"").replace("=", ":"); json.append("\"").append(escapedItem).append("\""); } else if (item instanceof Map) { json.append(jsonFromMap((Map) item)); } else if (item instanceof List) { json.append(jsonFromList((List) item)); } else if (item instanceof Boolean || item instanceof Number) { json.append(item.toString()); } else { // For other types, treat as string String escapedItem = item.toString().replace("\"", "\\\"").replace("=", ":"); json.append("\"").append(escapedItem).append("\""); } } json.append("]"); return json.toString(); } def metadata = jsonFromMap(ctx['entities']['metadata']); ctx['entities']['keyword'] = metadata; """ } } ] } ```
Index template ```js PUT /_index_template/entity_store_index_template { "index_patterns":[ "logs-store" ], "template":{ "settings":{ "index":{ "default_pipeline":"entities-keyword-builder" } }, "mappings":{ "properties":{ "@timestamp":{ "type":"date" }, "message":{ "type":"text" }, "event":{ "properties":{ "action":{ "type":"keyword" }, "category":{ "type":"keyword" }, "type":{ "type":"keyword" }, "outcome":{ "type":"keyword" }, "provider":{ "type":"keyword" }, "ingested":{ "type": "date" } } }, "related":{ "properties":{ "entity":{ "type":"keyword" } } }, "entities":{ "properties":{ "metadata":{ "type":"flattened" }, "keyword":{ "type":"keyword" } } } } } } } ```
Example source doc ```js POST /logs-store/_doc/ { "@timestamp":"2024-11-29T10:01:00Z", "message":"Eddie", "event": { "type":[ "creation" ], "ingested": "2024-12-03T10:01:00Z" }, "related":{ "entity":[ "AKIAI44QH8DHBEXAMPLE" ] }, "entities":{ "metadata":{ "AKIAI44QH8DHBEXAMPLE":{ "entity":{ "id":"AKIAI44QH8DHBEXAMPLE", "category":"Access Management", "type":"AWS IAM Access Key" }, "cloud":{ "account":{ "id":"444455556666" } } } } } } ```
### To do - [x] Add/Improve [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) - [x] Feature flag ---- #### Update: Added `assetInventoryStoreEnabled` Feature Flag. It is disabled by default and even when enabled, the `/api/entity_store/enable` route does not initialize the Universal Entity Engine. `/api/entity_store/engines/universal/init` needs to be manually called to initialize it --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Rômulo Farias Co-authored-by: jaredburgettelastic Co-authored-by: Elastic Machine --- oas_docs/output/kibana.serverless.yaml | 2 + oas_docs/output/kibana.yaml | 2 + .../asset_criticality/common.gen.ts | 2 +- .../asset_criticality/common.schema.yaml | 1 + .../entity_store/common.gen.ts | 2 +- .../entity_store/common.schema.yaml | 1 + .../parse_asset_criticality_csv_row.test.ts | 4 +- .../entity_store/constants.ts | 1 + .../common/experimental_features.ts | 6 + ...alytics_api_2023_10_31.bundled.schema.yaml | 2 + ...alytics_api_2023_10_31.bundled.schema.yaml | 2 + .../asset_criticality/helpers.ts | 1 + .../component_template.ts | 21 +- .../elasticsearch_assets/enrich_policy.ts | 72 ++-- .../elasticsearch_assets/entity_index.ts | 51 +++ .../elasticsearch_assets/ingest_pipeline.ts | 84 ++-- .../ingest_processor_steps/index.ts | 1 - ...on_definition_to_ingest_processor_steps.ts | 26 -- .../entity_definitions/constants.ts | 27 ++ .../entity_descriptions/common.ts | 36 ++ .../entity_descriptions/field_utils.ts | 79 ++++ .../entity_descriptions/host.ts | 48 +++ .../entity_descriptions}/index.ts | 3 +- .../entity_descriptions/service.ts | 32 ++ .../entity_descriptions/universal.ts | 60 +++ .../entity_descriptions/user.ts | 39 ++ .../entity_manager_conversion.ts | 40 ++ .../entity_store/entity_definitions/types.ts | 27 ++ .../entity_store_data_client.test.ts | 32 +- .../entity_store/entity_store_data_client.ts | 103 ++--- .../collect_values.ts | 27 +- .../index.ts | 0 .../operator_to_ingest_processor.ts | 22 +- .../prefer_newest_value.ts | 16 +- .../prefer_oldest_value.ts | 14 +- .../entity_store/field_retention/types.ts | 18 + .../field_retention_definition/types.ts | 34 -- .../engine_description.test.ts} | 363 +++--------------- .../installation/engine_description.ts | 95 +++++ .../entity_store/installation/types.ts | 73 ++++ .../task/field_retention_enrichment_task.ts | 13 +- .../united_entity_definitions/constants.ts | 28 -- .../definition_utils.ts | 86 ----- .../entity_types/common.ts | 46 --- .../entity_types/host.ts | 44 --- .../entity_types/service.ts | 29 -- .../entity_types/user.ts | 35 -- .../get_united_definition.ts | 69 ---- .../united_entity_definitions/index.ts | 10 - .../united_entity_definitions/types.ts | 30 -- .../united_entity_definition.ts | 126 ------ .../entity_store/utils/entity_utils.ts | 5 - .../field_retention_operators.ts | 264 +++++++++---- .../asset_criticality_csv_upload.ts | 2 +- 54 files changed, 1128 insertions(+), 1128 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/retention_definition_to_ingest_processor_steps.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/constants.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/common.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/field_utils.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/host.ts rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{united_entity_definitions/entity_types => entity_definitions/entity_descriptions}/index.ts (79%) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/service.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/universal.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/user.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_manager_conversion.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/types.ts rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{field_retention_definition => field_retention}/collect_values.ts (77%) rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{field_retention_definition => field_retention}/index.ts (100%) rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{field_retention_definition => field_retention}/operator_to_ingest_processor.ts (54%) rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{field_retention_definition => field_retention}/prefer_newest_value.ts (68%) rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{field_retention_definition => field_retention}/prefer_oldest_value.ts (69%) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/types.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/types.ts rename x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/{united_entity_definitions/get_united_definition.test.ts => installation/engine_description.test.ts} (64%) create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/types.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/constants.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/definition_utils.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/common.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/host.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/user.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/index.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/types.ts delete mode 100644 x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/united_entity_definition.ts diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index b017c2d52ac6f..b9c0acda9e793 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -47250,6 +47250,7 @@ components: - user - host - service + - universal type: string Security_Entity_Analytics_API_HostEntity: type: object @@ -47320,6 +47321,7 @@ components: - host.name - user.name - service.name + - related.entity type: string Security_Entity_Analytics_API_IndexPattern: type: string diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 561996f60c8af..38cc5ab0e932f 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -54126,6 +54126,7 @@ components: - user - host - service + - universal type: string Security_Entity_Analytics_API_HostEntity: type: object @@ -54196,6 +54197,7 @@ components: - host.name - user.name - service.name + - related.entity type: string Security_Entity_Analytics_API_IndexPattern: type: string diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts index acae48846c82c..2c9cc0760f210 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.gen.ts @@ -17,7 +17,7 @@ import { z } from '@kbn/zod'; export type IdField = z.infer; -export const IdField = z.enum(['host.name', 'user.name', 'service.name']); +export const IdField = z.enum(['host.name', 'user.name', 'service.name', 'related.entity']); export type IdFieldEnum = typeof IdField.enum; export const IdFieldEnum = IdField.enum; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml index 2dd4f3f635599..1bd73eea6cef4 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/asset_criticality/common.schema.yaml @@ -29,6 +29,7 @@ components: - 'host.name' - 'user.name' - 'service.name' + - 'related.entity' AssetCriticalityRecordIdParts: type: object properties: diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts index 3511e1d166ede..8fd0b17161154 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.gen.ts @@ -17,7 +17,7 @@ import { z } from '@kbn/zod'; export type EntityType = z.infer; -export const EntityType = z.enum(['user', 'host', 'service']); +export const EntityType = z.enum(['user', 'host', 'service', 'universal']); export type EntityTypeEnum = typeof EntityType.enum; export const EntityTypeEnum = EntityType.enum; diff --git a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml index 0ddbf1c9b3fd0..68b6e6612735c 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/common/api/entity_analytics/entity_store/common.schema.yaml @@ -12,6 +12,7 @@ components: - user - host - service + - universal EngineDescriptor: type: object diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/asset_criticality/parse_asset_criticality_csv_row.test.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/asset_criticality/parse_asset_criticality_csv_row.test.ts index 21c0785db5c28..f4633ba87d385 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/asset_criticality/parse_asset_criticality_csv_row.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/asset_criticality/parse_asset_criticality_csv_row.test.ts @@ -46,7 +46,7 @@ describe('parseAssetCriticalityCsvRow', () => { // @ts-ignore result can now only be InvalidRecord expect(result.error).toMatchInlineSnapshot( - `"Invalid entity type \\"invalid\\", expected to be one of: user, host, service"` + `"Invalid entity type \\"invalid\\", expected to be one of: user, host, service, universal"` ); }); @@ -57,7 +57,7 @@ describe('parseAssetCriticalityCsvRow', () => { // @ts-ignore result can now only be InvalidRecord expect(result.error).toMatchInlineSnapshot( - `"Invalid entity type \\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...\\", expected to be one of: user, host, service"` + `"Invalid entity type \\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...\\", expected to be one of: user, host, service, universal"` ); }); diff --git a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/entity_store/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/entity_store/constants.ts index d74c0fdd3735d..4c050dc7a83ce 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/entity_store/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/entity_analytics/entity_store/constants.ts @@ -31,6 +31,7 @@ export const IDENTITY_FIELD_MAP: Record = { [EntityTypeEnum.host]: 'host.name', [EntityTypeEnum.user]: 'user.name', [EntityTypeEnum.service]: 'service.name', + [EntityTypeEnum.universal]: 'related.entity', }; export const getAvailableEntityTypes = (): EntityType[] => diff --git a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts index 2c7fa430907a2..14daad66a7c23 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/experimental_features.ts @@ -268,6 +268,12 @@ export const allowedExperimentalValues = Object.freeze({ */ crowdstrikeRunScriptEnabled: false, + /** + * Enables the Asset Inventory Entity Store feature. + * Allows initializing the Universal Entity Store via the API. + */ + assetInventoryStoreEnabled: false, + /** * Enables the Asset Inventory feature */ diff --git a/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 05daf410e6eff..ea6a83ad806e1 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/docs/openapi/ess/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -1075,6 +1075,7 @@ components: - user - host - service + - universal type: string HostEntity: type: object @@ -1145,6 +1146,7 @@ components: - host.name - user.name - service.name + - related.entity type: string IndexPattern: type: string diff --git a/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml b/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml index 8bbeb3c2486ff..519d382cbbcd8 100644 --- a/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml +++ b/x-pack/solutions/security/plugins/security_solution/docs/openapi/serverless/security_solution_entity_analytics_api_2023_10_31.bundled.schema.yaml @@ -1075,6 +1075,7 @@ components: - user - host - service + - universal type: string HostEntity: type: object @@ -1145,6 +1146,7 @@ components: - host.name - user.name - service.name + - related.entity type: string IndexPattern: type: string diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts index 5ae034fe5d86d..f3aa9fa5b0d09 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/asset_criticality/helpers.ts @@ -82,6 +82,7 @@ const entityTypeByIdField = { 'host.name': 'host', 'user.name': 'user', 'service.name': 'service', + 'related.entity': 'universal', } as const; export const getImplicitEntityFields = (record: AssetCriticalityUpsertWithDeleted) => { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts index a1352594ff47d..4a7365c729d95 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/component_template.ts @@ -10,18 +10,24 @@ import { EngineComponentResourceEnum, type EngineComponentStatus, } from '../../../../../common/api/entity_analytics'; -import type { UnitedEntityDefinition } from '../united_entity_definitions'; +import type { EntityEngineInstallationDescriptor } from '../installation/types'; const getComponentTemplateName = (definitionId: string) => `${definitionId}-latest@platform`; interface Options { - unitedDefinition: UnitedEntityDefinition; + /** + * The entity engine description id + **/ + id: string; esClient: ElasticsearchClient; } -export const createEntityIndexComponentTemplate = ({ unitedDefinition, esClient }: Options) => { - const { entityManagerDefinition, indexMappings } = unitedDefinition; - const name = getComponentTemplateName(entityManagerDefinition.id); +export const createEntityIndexComponentTemplate = ( + description: EntityEngineInstallationDescriptor, + esClient: ElasticsearchClient +) => { + const { id, indexMappings } = description; + const name = getComponentTemplateName(id); return esClient.cluster.putComponentTemplate({ name, body: { @@ -35,9 +41,8 @@ export const createEntityIndexComponentTemplate = ({ unitedDefinition, esClient }); }; -export const deleteEntityIndexComponentTemplate = ({ unitedDefinition, esClient }: Options) => { - const { entityManagerDefinition } = unitedDefinition; - const name = getComponentTemplateName(entityManagerDefinition.id); +export const deleteEntityIndexComponentTemplate = ({ id, esClient }: Options) => { + const name = getComponentTemplateName(id); return esClient.cluster.deleteComponentTemplate( { name }, { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts index 7064a4dd98851..35a7fc40fc58d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/enrich_policy.ts @@ -6,12 +6,14 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; -import type { EnrichPutPolicyRequest } from '@elastic/elasticsearch/lib/api/types'; +import type { EntityType } from '../../../../../common/api/entity_analytics'; import { EngineComponentResourceEnum } from '../../../../../common/api/entity_analytics'; import { getEntitiesIndexName } from '../utils'; -import type { UnitedEntityDefinition } from '../united_entity_definitions'; +import type { EntityEngineInstallationDescriptor } from '../installation/types'; -type DefinitionMetadata = Pick; +type DefinitionMetadata = Pick & { + namespace: string; +}; export const getFieldRetentionEnrichPolicyName = ({ namespace, @@ -21,41 +23,47 @@ export const getFieldRetentionEnrichPolicyName = ({ return `entity_store_field_retention_${entityType}_${namespace}_v${version}`; }; -const getFieldRetentionEnrichPolicy = ( - unitedDefinition: UnitedEntityDefinition -): EnrichPutPolicyRequest => { - const { namespace, entityType, fieldRetentionDefinition } = unitedDefinition; - return { - name: getFieldRetentionEnrichPolicyName(unitedDefinition), - match: { - indices: getEntitiesIndexName(entityType, namespace), - match_field: fieldRetentionDefinition.matchField, - enrich_fields: fieldRetentionDefinition.fields.map(({ field }) => field), - }, - }; -}; - export const createFieldRetentionEnrichPolicy = async ({ esClient, - unitedDefinition, + description, + options, }: { esClient: ElasticsearchClient; - unitedDefinition: UnitedEntityDefinition; + description: EntityEngineInstallationDescriptor; + options: { namespace: string }; }) => { - const policy = getFieldRetentionEnrichPolicy(unitedDefinition); - return esClient.enrich.putPolicy(policy); + return esClient.enrich.putPolicy({ + name: getFieldRetentionEnrichPolicyName({ + namespace: options.namespace, + entityType: description.entityType, + version: description.version, + }), + match: { + indices: getEntitiesIndexName(description.entityType, options.namespace), + match_field: description.identityField, + enrich_fields: description.fields.map(({ destination }) => destination), + }, + }); }; export const executeFieldRetentionEnrichPolicy = async ({ esClient, - unitedDefinition, + entityType, + version, logger, + options, }: { - unitedDefinition: DefinitionMetadata; + entityType: EntityType; + version: string; esClient: ElasticsearchClient; logger: Logger; + options: { namespace: string }; }): Promise<{ executed: boolean }> => { - const name = getFieldRetentionEnrichPolicyName(unitedDefinition); + const name = getFieldRetentionEnrichPolicyName({ + namespace: options.namespace, + entityType, + version, + }); try { await esClient.enrich.executePolicy({ name }); return { executed: true }; @@ -63,27 +71,31 @@ export const executeFieldRetentionEnrichPolicy = async ({ if (e.statusCode === 404) { return { executed: false }; } - logger.error( - `Error executing field retention enrich policy for ${unitedDefinition.entityType}: ${e.message}` - ); + logger.error(`Error executing field retention enrich policy for ${entityType}: ${e.message}`); throw e; } }; export const deleteFieldRetentionEnrichPolicy = async ({ - unitedDefinition, + description, + options, esClient, logger, attempts = 5, delayMs = 2000, }: { - unitedDefinition: DefinitionMetadata; + description: EntityEngineInstallationDescriptor; + options: { namespace: string }; esClient: ElasticsearchClient; logger: Logger; attempts?: number; delayMs?: number; }) => { - const name = getFieldRetentionEnrichPolicyName(unitedDefinition); + const name = getFieldRetentionEnrichPolicyName({ + namespace: options.namespace, + entityType: description.entityType, + version: description.version, + }); let currentAttempt = 1; while (currentAttempt <= attempts) { try { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts index 0de8fe20050ce..719d2df7a9521 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/entity_index.ts @@ -6,6 +6,7 @@ */ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; +import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; import { EngineComponentResourceEnum, type EngineComponentStatus, @@ -14,6 +15,8 @@ import { import { getEntitiesIndexName } from '../utils'; import { createOrUpdateIndex } from '../../utils/create_or_update_index'; +import type { EntityEngineInstallationDescriptor } from '../installation/types'; + interface Options { entityType: EntityType; esClient: ElasticsearchClient; @@ -58,3 +61,51 @@ export const getEntityIndexStatus = async ({ return { id: index, installed: exists, resource: EngineComponentResourceEnum.index }; }; + +export type MappingProperties = NonNullable; + +export const generateIndexMappings = ( + description: EntityEngineInstallationDescriptor +): MappingTypeMapping => { + const identityFieldMappings: MappingProperties = { + [description.identityField]: { + type: 'keyword', + fields: { + text: { + type: 'match_only_text', + }, + }, + }, + }; + + const otherFieldMappings = description.fields + .filter(({ mapping }) => mapping) + .reduce((acc, { destination, mapping }) => { + acc[destination] = mapping; + return acc; + }, {} as MappingProperties); + + return { + properties: { ...BASE_ENTITY_INDEX_MAPPING, ...identityFieldMappings, ...otherFieldMappings }, + }; +}; + +export const BASE_ENTITY_INDEX_MAPPING: MappingProperties = { + '@timestamp': { + type: 'date', + }, + 'asset.criticality': { + type: 'keyword', + }, + 'entity.name': { + type: 'keyword', + fields: { + text: { + type: 'match_only_text', + }, + }, + }, + 'entity.source': { + type: 'keyword', + }, +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts index f57102fd13f14..c6b2a5902ec7f 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_pipeline.ts @@ -7,22 +7,22 @@ import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; -import type { EntityDefinition } from '@kbn/entities-schema'; import { EngineComponentResourceEnum } from '../../../../../common/api/entity_analytics'; -import { type FieldRetentionDefinition } from '../field_retention_definition'; + import { debugDeepCopyContextStep, getDotExpanderSteps, getRemoveEmptyFieldSteps, removeEntityDefinitionFieldsStep, - retentionDefinitionToIngestProcessorSteps, } from './ingest_processor_steps'; -import { getIdentityFieldForEntityType } from '../utils'; + import { getFieldRetentionEnrichPolicyName } from './enrich_policy'; -import type { UnitedEntityDefinition } from '../united_entity_definitions'; -const getPlatformPipelineId = (definition: EntityDefinition) => { - return `${definition.id}-latest@platform`; +import { fieldOperatorToIngestProcessor } from '../field_retention'; +import type { EntityEngineInstallationDescriptor } from '../installation/types'; + +const getPlatformPipelineId = (descriptionId: string) => { + return `${descriptionId}-latest@platform`; }; // the field that the enrich processor writes to @@ -38,30 +38,28 @@ export const ENRICH_FIELD = 'historical'; * and the context field in the document to help with debugging. */ const buildIngestPipeline = ({ - version, - fieldRetentionDefinition, allEntityFields, debugMode, namespace, + description, }: { - fieldRetentionDefinition: FieldRetentionDefinition; allEntityFields: string[]; debugMode?: boolean; namespace: string; version: string; + description: EntityEngineInstallationDescriptor; }): IngestProcessorContainer[] => { - const { entityType, matchField } = fieldRetentionDefinition; const enrichPolicyName = getFieldRetentionEnrichPolicyName({ namespace, - entityType, - version, + entityType: description.entityType, + version: description.version, }); - return [ - ...(debugMode ? [debugDeepCopyContextStep()] : []), + + const processors = [ { enrich: { policy_name: enrichPolicyName, - field: matchField, + field: description.identityField, target_field: ENRICH_FIELD, }, }, @@ -74,14 +72,14 @@ const buildIngestPipeline = ({ { set: { field: 'entity.name', - value: `{{${getIdentityFieldForEntityType(entityType)}}}`, + value: `{{${description.identityField}}}`, }, }, ...getDotExpanderSteps(allEntityFields), - ...retentionDefinitionToIngestProcessorSteps(fieldRetentionDefinition, { - enrichField: ENRICH_FIELD, - }), - ...getRemoveEmptyFieldSteps([...allEntityFields, 'asset', `${entityType}.risk`]), + ...description.fields.map((field) => + fieldOperatorToIngestProcessor(field, { enrichField: ENRICH_FIELD }) + ), + ...getRemoveEmptyFieldSteps([...allEntityFields, 'asset', `${description.entityType}.risk`]), removeEntityDefinitionFieldsStep(), ...(!debugMode ? [ @@ -94,43 +92,45 @@ const buildIngestPipeline = ({ ] : []), ]; + + const extraSteps = + (typeof description.pipeline === 'function' + ? description.pipeline(processors) + : description.pipeline) ?? []; + + return [...(debugMode ? [debugDeepCopyContextStep()] : []), ...processors, ...extraSteps]; }; // developing the pipeline is a bit tricky, so we have a debug mode // set xpack.securitySolution.entityAnalytics.entityStore.developer.pipelineDebugMode // to true to keep the enrich field and the context field in the document to help with debugging. export const createPlatformPipeline = async ({ - unitedDefinition, logger, esClient, debugMode, + description, + options, }: { - unitedDefinition: UnitedEntityDefinition; + description: EntityEngineInstallationDescriptor; + options: { namespace: string }; logger: Logger; esClient: ElasticsearchClient; debugMode?: boolean; }) => { - const { fieldRetentionDefinition, entityManagerDefinition } = unitedDefinition; - const allEntityFields: string[] = (entityManagerDefinition?.metadata || []).map((m) => { - if (typeof m === 'string') { - return m; - } - - return m.destination; - }); + const allEntityFields = description.fields.map(({ destination }) => destination); const pipeline = { - id: getPlatformPipelineId(entityManagerDefinition), + id: getPlatformPipelineId(description.id), body: { _meta: { managed_by: 'entity_store', managed: true, }, - description: `Ingest pipeline for entity definition ${entityManagerDefinition.id}`, + description: `Ingest pipeline for entity definition ${description.id}`, processors: buildIngestPipeline({ - namespace: unitedDefinition.namespace, - version: unitedDefinition.version, - fieldRetentionDefinition, + namespace: options.namespace, + description, + version: description.version, allEntityFields, debugMode, }), @@ -143,15 +143,15 @@ export const createPlatformPipeline = async ({ }; export const deletePlatformPipeline = ({ - unitedDefinition, + description, logger, esClient, }: { - unitedDefinition: UnitedEntityDefinition; + description: EntityEngineInstallationDescriptor; logger: Logger; esClient: ElasticsearchClient; }) => { - const pipelineId = getPlatformPipelineId(unitedDefinition.entityManagerDefinition); + const pipelineId = getPlatformPipelineId(description.id); logger.debug(`Attempting to delete pipeline: ${pipelineId}`); return esClient.ingest.deletePipeline( { @@ -164,13 +164,13 @@ export const deletePlatformPipeline = ({ }; export const getPlatformPipelineStatus = async ({ - definition, + engineId, esClient, }: { - definition: EntityDefinition; + engineId: string; esClient: ElasticsearchClient; }) => { - const pipelineId = getPlatformPipelineId(definition); + const pipelineId = getPlatformPipelineId(engineId); const pipeline = await esClient.ingest.getPipeline( { id: pipelineId, diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/index.ts index e8b715783b0a3..11fac0f9663ec 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/index.ts @@ -9,4 +9,3 @@ export { debugDeepCopyContextStep } from './debug_deep_copy_context_step'; export { getDotExpanderSteps } from './get_dot_expander_steps'; export { getRemoveEmptyFieldSteps } from './get_remove_empty_field_steps'; export { removeEntityDefinitionFieldsStep } from './remove_entity_definition_fields_step'; -export { retentionDefinitionToIngestProcessorSteps } from './retention_definition_to_ingest_processor_steps'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/retention_definition_to_ingest_processor_steps.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/retention_definition_to_ingest_processor_steps.ts deleted file mode 100644 index 2b6c5f555eb35..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/elasticsearch_assets/ingest_processor_steps/retention_definition_to_ingest_processor_steps.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; -import type { - FieldRetentionDefinition, - FieldRetentionOperatorBuilderOptions, -} from '../../field_retention_definition'; -import { fieldOperatorToIngestProcessor } from '../../field_retention_definition'; - -/** - * Converts a field retention definition to the ingest processor steps - * required to apply the field retention policy. - */ -export const retentionDefinitionToIngestProcessorSteps = ( - fieldRetentionDefinition: FieldRetentionDefinition, - options: FieldRetentionOperatorBuilderOptions -): IngestProcessorContainer[] => { - return fieldRetentionDefinition.fields.map((field) => - fieldOperatorToIngestProcessor(field, options) - ); -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/constants.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/constants.ts new file mode 100644 index 0000000000000..f24b6f6a0c4c1 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/constants.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EntityType } from '../../../../../common/api/entity_analytics/entity_store'; +import { + HOST_DEFINITION_VERSION, + UNIVERSAL_DEFINITION_VERSION, + USER_DEFINITION_VERSION, + SERVICE_DEFINITION_VERSION, +} from './entity_descriptions'; + +export const VERSIONS_BY_ENTITY_TYPE: Record = { + host: HOST_DEFINITION_VERSION, + user: USER_DEFINITION_VERSION, + universal: UNIVERSAL_DEFINITION_VERSION, + service: SERVICE_DEFINITION_VERSION, +}; + +export const DEFAULT_FIELD_HISTORY_LENGTH = 10; +export const DEFAULT_SYNC_DELAY = '1m'; +export const DEFAULT_FREQUENCY = '1m'; +export const DEFAULT_LOOKBACK_PERIOD = '1d'; +export const DEFAULT_TIMESTAMP_FIELD = '@timestamp'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/common.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/common.ts new file mode 100644 index 0000000000000..d7c966093ff87 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/common.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EntityType } from '../../../../../../common/api/entity_analytics/entity_store'; +import type { FieldDescription } from '../../installation/types'; + +import { oldestValue, newestValue } from './field_utils'; + +export const getCommonFieldDescriptions = (entityType: EntityType): FieldDescription[] => { + return [ + oldestValue({ + source: '_index', + destination: 'entity.source', + }), + newestValue({ source: 'asset.criticality' }), + newestValue({ + source: `${entityType}.risk.calculated_level`, + }), + newestValue({ + source: `${entityType}.risk.calculated_score`, + mapping: { + type: 'float', + }, + }), + newestValue({ + source: `${entityType}.risk.calculated_score_norm`, + mapping: { + type: 'float', + }, + }), + ]; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/field_utils.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/field_utils.ts new file mode 100644 index 0000000000000..d19c56e4f7b2a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/field_utils.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { MappingProperty } from '@elastic/elasticsearch/lib/api/types'; +import type { FieldDescription } from '../../installation/types'; + +export const collectValues = ({ + destination, + source, + fieldHistoryLength = 10, + mapping = { type: 'keyword' }, +}: { + source: string; + destination?: string; + mapping?: MappingProperty; + fieldHistoryLength?: number; +}): FieldDescription => ({ + destination: destination ?? source, + source, + retention: { + operation: 'collect_values', + maxLength: fieldHistoryLength, + }, + aggregation: { + type: 'terms', + limit: fieldHistoryLength, + }, + mapping, +}); + +export const newestValue = ({ + destination, + mapping = { type: 'keyword' }, + source, + sort, +}: { + source: string; + destination?: string; + mapping?: MappingProperty; + sort?: Record; +}): FieldDescription => ({ + destination: destination ?? source, + source, + retention: { operation: 'prefer_newest_value' }, + aggregation: { + type: 'top_value', + sort: sort ?? { + '@timestamp': 'desc', + }, + }, + mapping, +}); + +export const oldestValue = ({ + source, + destination, + mapping = { type: 'keyword' }, + sort, +}: { + source: string; + destination?: string; + mapping?: MappingProperty; + sort?: Record; +}): FieldDescription => ({ + destination: destination ?? source, + source, + retention: { operation: 'prefer_oldest_value' }, + aggregation: { + type: 'top_value', + sort: sort ?? { + '@timestamp': 'asc', + }, + }, + mapping, +}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/host.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/host.ts new file mode 100644 index 0000000000000..7446f9a00911a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/host.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { collectValues as collect } from './field_utils'; +import type { EntityDescription } from '../types'; +import { getCommonFieldDescriptions } from './common'; + +export const HOST_DEFINITION_VERSION = '1.0.0'; +export const HOST_IDENTITY_FIELD = 'host.name'; +export const hostEntityEngineDescription: EntityDescription = { + entityType: 'host', + version: HOST_DEFINITION_VERSION, + identityField: HOST_IDENTITY_FIELD, + settings: { + timestampField: '@timestamp', + }, + fields: [ + collect({ source: 'host.domain' }), + collect({ source: 'host.hostname' }), + collect({ source: 'host.id' }), + collect({ + source: 'host.os.name', + mapping: { + type: 'keyword', + fields: { + text: { + type: 'match_only_text', + }, + }, + }, + }), + collect({ source: 'host.os.type' }), + collect({ + source: 'host.ip', + mapping: { + type: 'ip', + }, + }), + collect({ source: 'host.mac' }), + collect({ source: 'host.type' }), + collect({ source: 'host.architecture' }), + ...getCommonFieldDescriptions('host'), + ], +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/index.ts similarity index 79% rename from x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/index.ts index cf78a4b0e363b..8984c4aac0eca 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/index.ts @@ -8,4 +8,5 @@ export * from './host'; export * from './user'; export * from './service'; -export { getCommonUnitedFieldDefinitions } from './common'; +export * from './universal'; +export { getCommonFieldDescriptions } from './common'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/service.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/service.ts new file mode 100644 index 0000000000000..86c5091d2844a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/service.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EntityDescription } from '../types'; +import { collectValues as collect, newestValue } from './field_utils'; + +export const SERVICE_DEFINITION_VERSION = '1.0.0'; +export const SERVICE_IDENTITY_FIELD = 'service.name'; + +export const serviceEntityEngineDescription: EntityDescription = { + entityType: 'service', + version: SERVICE_DEFINITION_VERSION, + identityField: SERVICE_IDENTITY_FIELD, + settings: { + timestampField: '@timestamp', + }, + fields: [ + collect({ source: 'service.address' }), + collect({ source: 'service.environment' }), + collect({ source: 'service.ephemeral_id' }), + collect({ source: 'service.id' }), + collect({ source: 'service.node.name' }), + collect({ source: 'service.node.roles' }), + newestValue({ source: 'service.state' }), + collect({ source: 'service.type' }), + newestValue({ source: 'service.version' }), + ], +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/universal.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/universal.ts new file mode 100644 index 0000000000000..d4e0997ee4f93 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/universal.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EntityDescription } from '../types'; +import { collectValues as collect } from './field_utils'; + +export const UNIVERSAL_DEFINITION_VERSION = '1.0.0'; +export const UNIVERSAL_IDENTITY_FIELD = 'related.entity'; + +const entityMetadataExtractorProcessor = { + script: { + tag: 'entity_metadata_extractor', + on_failure: [ + { + set: { + field: 'error.message', + value: + 'Processor {{ _ingest.on_failure_processor_type }} with tag {{ _ingest.on_failure_processor_tag }} in pipeline {{ _ingest.on_failure_pipeline }} failed with message {{ _ingest.on_failure_message }}', + }, + }, + ], + lang: 'painless', + source: /* java */ ` + Map merged = ctx; + def id = ctx.entity.id; + + for (meta in ctx.collected.metadata) { + Object json = Processors.json(meta); + + if (((Map)json)[id] == null) { + continue; + } + + for (entry in ((Map)json)[id].entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + merged.put(key, value); + } + } + + merged.entity.id = id; + ctx = merged; + `, + }, +}; + +export const universalEntityEngineDescription: EntityDescription = { + version: UNIVERSAL_DEFINITION_VERSION, + entityType: 'universal', + identityField: UNIVERSAL_IDENTITY_FIELD, + fields: [collect({ source: 'entities.keyword', destination: 'collected.metadata' })], + settings: { + timestampField: 'event.ingested', + }, + pipeline: [entityMetadataExtractorProcessor], +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/user.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/user.ts new file mode 100644 index 0000000000000..b79bdf9766d96 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_descriptions/user.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { EntityDescription } from '../types'; +import { getCommonFieldDescriptions } from './common'; +import { collectValues as collect } from './field_utils'; + +export const USER_DEFINITION_VERSION = '1.0.0'; +export const USER_IDENTITY_FIELD = 'user.name'; +export const userEntityEngineDescription: EntityDescription = { + entityType: 'user', + version: USER_DEFINITION_VERSION, + identityField: USER_IDENTITY_FIELD, + settings: { + timestampField: '@timestamp', + }, + fields: [ + collect({ source: 'user.domain' }), + collect({ source: 'user.email' }), + collect({ + source: 'user.full_name', + mapping: { + type: 'keyword', + fields: { + text: { + type: 'match_only_text', + }, + }, + }, + }), + collect({ source: 'user.hash' }), + collect({ source: 'user.id' }), + collect({ source: 'user.roles' }), + ...getCommonFieldDescriptions('user'), + ], +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_manager_conversion.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_manager_conversion.ts new file mode 100644 index 0000000000000..acc69c9bf9d33 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/entity_manager_conversion.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { pick } from 'lodash/fp'; +import { entityDefinitionSchema, type EntityDefinition } from '@kbn/entities-schema'; +import { buildEntityDefinitionId } from '../utils'; +import type { EntityEngineInstallationDescriptor } from '../installation/types'; + +export const convertToEntityManagerDefinition = ( + description: EntityEngineInstallationDescriptor, + options: { namespace: string; filter: string } +): EntityDefinition => { + const metadata = description.fields.map(pick(['source', 'destination', 'aggregation'])); + + const definition = { + id: buildEntityDefinitionId(description.entityType, options.namespace), + name: `Security '${description.entityType}' Entity Store Definition`, + type: description.entityType, + indexPatterns: description.indexPatterns, + identityFields: [description.identityField], + displayNameTemplate: `{{${description.identityField}}}`, + metadata, + latest: { + timestampField: description.settings.timestampField, + lookbackPeriod: description.settings.lookbackPeriod, + settings: { + syncDelay: description.settings.syncDelay, + frequency: description.settings.frequency, + }, + }, + version: description.version, + managed: true, + }; + + return entityDefinitionSchema.parse(definition); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/types.ts new file mode 100644 index 0000000000000..e271c29e1d186 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_definitions/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { EntityEngineInstallationDescriptor } from '../installation/types'; + +type PickPartial = { + [P in K as P extends Optional ? never : P]: T[P]; +} & { + [P in K as P extends Optional ? P : never]?: Partial; +}; + +export type EntityDescription = PickPartial< + EntityEngineInstallationDescriptor, + | 'version' + | 'entityType' + | 'fields' + | 'identityField' + | 'indexPatterns' + | 'indexMappings' + | 'settings' + | 'pipeline', + 'indexPatterns' | 'indexMappings' | 'settings' | 'pipeline' +>; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts index b7cacea3c3757..8a1eaf9b5cc0c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.test.ts @@ -18,17 +18,27 @@ import type { AppClient } from '../../..'; import type { EntityStoreConfig } from './types'; import { mockGlobalState } from '../../../../public/common/mock'; import type { EntityDefinition } from '@kbn/entities-schema'; -import { getUnitedEntityDefinition } from './united_entity_definitions'; - -const unitedDefinition = getUnitedEntityDefinition({ - entityType: 'host', - namespace: 'test', - fieldHistoryLength: 10, - indexPatterns: [], - syncDelay: '1m', - frequency: '1m', -}); -const definition: EntityDefinition = unitedDefinition.entityManagerDefinition; +import { convertToEntityManagerDefinition } from './entity_definitions/entity_manager_conversion'; + +const definition: EntityDefinition = convertToEntityManagerDefinition( + { + id: 'host_engine', + entityType: 'host', + pipeline: [], + version: '0.0.1', + fields: [], + identityField: 'host.name', + indexMappings: {}, + indexPatterns: [], + settings: { + syncDelay: '1m', + frequency: '1m', + timestampField: '@timestamp', + lookbackPeriod: '24h', + }, + }, + { namespace: 'test', filter: '' } +); describe('EntityStoreDataClient', () => { const mockSavedObjectClient = savedObjectsClientMock.create(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts index f9889f3a0d530..afa21c16b3660 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/entity_store_data_client.ts @@ -51,7 +51,6 @@ import type { import { EngineDescriptorClient } from './saved_object/engine_descriptor'; import { ENGINE_STATUS, ENTITY_STORE_STATUS, MAX_SEARCH_RESPONSE_SIZE } from './constants'; import { AssetCriticalityMigrationClient } from '../asset_criticality/asset_criticality_migration_client'; -import { getUnitedEntityDefinition } from './united_entity_definitions'; import { startEntityStoreFieldRetentionEnrichTask, removeEntityStoreFieldRetentionEnrichTask, @@ -88,6 +87,8 @@ import { ENTITY_ENGINE_RESOURCE_INIT_FAILURE_EVENT, } from '../../telemetry/event_based/events'; import { CRITICALITY_VALUES } from '../asset_criticality/constants'; +import { createEngineDescription } from './installation/engine_description'; +import { convertToEntityManagerDefinition } from './entity_definitions/entity_manager_conversion'; // Workaround. TransformState type is wrong. The health type should be: TransformHealth from '@kbn/transform-plugin/common/types/transform_stats' export interface TransformHealth extends estypes.TransformGetTransformStatsTransformStatsHealth { @@ -175,7 +176,7 @@ export class EntityStoreDataClient { ? [getEntityStoreFieldRetentionEnrichTaskStatus({ namespace, taskManager })] : []), getPlatformPipelineStatus({ - definition, + engineId: definition.id, esClient: this.esClient, }), getFieldRetentionEnrichPolicyStatus({ @@ -212,9 +213,12 @@ export class EntityStoreDataClient { new Promise((resolve) => setTimeout(() => fn().then(resolve), 0)); const { experimentalFeatures } = this.options; - const enginesTypes = experimentalFeatures.serviceEntityStoreEnabled - ? [EntityTypeEnum.host, EntityTypeEnum.user, EntityTypeEnum.service] - : [EntityTypeEnum.host, EntityTypeEnum.user]; + + const enginesTypes: EntityType[] = [EntityTypeEnum.host, EntityTypeEnum.user]; + if (experimentalFeatures.serviceEntityStoreEnabled) { + enginesTypes.push(EntityTypeEnum.service); + } + // NOTE: Whilst the Universal Entity Store is also behind a feature flag, we do not want to enable it as part of this flow as of 8.18 const promises = enginesTypes.map((entity) => run(() => @@ -246,18 +250,15 @@ export class EntityStoreDataClient { if (withComponents) { const enginesWithComponents = await Promise.all( engines.map(async (engine) => { - const entityDefinitionId = buildEntityDefinitionId(engine.type, namespace); + const id = buildEntityDefinitionId(engine.type, namespace); const { definitions: [definition], } = await this.entityClient.getEntityDefinitions({ - id: entityDefinitionId, + id, includeState: withComponents, }); - const definitionComponents = this.getComponentFromEntityDefinition( - entityDefinitionId, - definition - ); + const definitionComponents = this.getComponentFromEntityDefinition(id, definition); const entityStoreComponents = await this.getEngineComponentsState( engine.type, @@ -282,6 +283,19 @@ export class EntityStoreDataClient { { indexPattern = '', filter = '', fieldHistoryLength = 10 }: InitEntityEngineRequestBody, { pipelineDebugMode = false }: { pipelineDebugMode?: boolean } = {} ): Promise { + const { experimentalFeatures } = this.options; + + if ( + entityType === EntityTypeEnum.universal && + !experimentalFeatures.assetInventoryStoreEnabled + ) { + throw new Error('Universal entity store is not enabled'); + } + + if (entityType === EntityTypeEnum.service && !experimentalFeatures.serviceEntityStoreEnabled) { + throw new Error('Service entity store is not enabled'); + } + if (!this.options.taskManager) { throw new Error('Task Manager is not available'); } @@ -350,40 +364,34 @@ export class EntityStoreDataClient { const setupStartTime = moment().utc().toISOString(); const { logger, namespace, appClient, dataViewsService } = this.options; try { - const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); + const defaultIndexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); - const unitedDefinition = getUnitedEntityDefinition({ - indexPatterns, + const description = createEngineDescription({ entityType, namespace, - fieldHistoryLength, - syncDelay: `${config.syncDelay.asSeconds()}s`, - frequency: `${config.frequency.asSeconds()}s`, + requestParams: { indexPattern, fieldHistoryLength }, + defaultIndexPatterns, + config, }); - const { entityManagerDefinition } = unitedDefinition; // clean up any existing entity store await this.delete(entityType, taskManager, { deleteData: false, deleteEngine: false }); // set up the entity manager definition + const definition = convertToEntityManagerDefinition(description, { + namespace, + filter, + }); + await this.entityClient.createEntityDefinition({ - definition: { - ...entityManagerDefinition, - filter, - indexPatterns: indexPattern - ? [...entityManagerDefinition.indexPatterns, ...indexPattern.split(',')] - : entityManagerDefinition.indexPatterns, - }, + definition, installOnly: true, }); this.log(`debug`, entityType, `Created entity definition`); // the index must be in place with the correct mapping before the enrich policy is created // this is because the enrich policy will fail if the index does not exist with the correct fields - await createEntityIndexComponentTemplate({ - unitedDefinition, - esClient: this.esClient, - }); + await createEntityIndexComponentTemplate(description, this.esClient); this.log(`debug`, entityType, `Created entity index component template`); await createEntityIndex({ entityType, @@ -396,20 +404,24 @@ export class EntityStoreDataClient { // we must create and execute the enrich policy before the pipeline is created // this is because the pipeline will fail if the enrich index does not exist await createFieldRetentionEnrichPolicy({ - unitedDefinition, + description, + options: { namespace }, esClient: this.esClient, }); this.log(`debug`, entityType, `Created field retention enrich policy`); await executeFieldRetentionEnrichPolicy({ - unitedDefinition, + entityType, + version: description.version, + options: { namespace }, esClient: this.esClient, logger, }); this.log(`debug`, entityType, `Executed field retention enrich policy`); await createPlatformPipeline({ + description, + options: { namespace }, debugMode: pipelineDebugMode, - unitedDefinition, logger, esClient: this.esClient, }); @@ -599,18 +611,18 @@ export class EntityStoreDataClient { const { deleteData, deleteEngine } = options; const descriptor = await this.engineClient.maybeGet(entityType); - const indexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); + const defaultIndexPatterns = await buildIndexPatterns(namespace, appClient, dataViewsService); - // TODO delete unitedDefinition from this method. we only need the id for deletion - const unitedDefinition = getUnitedEntityDefinition({ - indexPatterns, + const description = createEngineDescription({ entityType, - namespace: this.options.namespace, - fieldHistoryLength: descriptor?.fieldHistoryLength ?? 10, - syncDelay: `${config.syncDelay.asSeconds()}s`, - frequency: `${config.frequency.asSeconds()}s`, + namespace, + defaultIndexPatterns, + config, }); - const { entityManagerDefinition } = unitedDefinition; + + if (!description.id) { + throw new Error(`Unable to find entity definition for ${entityType}`); + } this.log('info', entityType, `Deleting entity store`); this.audit( @@ -623,7 +635,7 @@ export class EntityStoreDataClient { try { await this.entityClient .deleteEntityDefinition({ - id: entityManagerDefinition.id, + id: description.id, deleteData, }) // Swallowing the error as it is expected to fail if no entity definition exists @@ -633,20 +645,21 @@ export class EntityStoreDataClient { this.log('debug', entityType, `Deleted entity definition`); await deleteEntityIndexComponentTemplate({ - unitedDefinition, + id: description.id, esClient: this.esClient, }); this.log('debug', entityType, `Deleted entity index component template`); await deletePlatformPipeline({ - unitedDefinition, + description, logger, esClient: this.esClient, }); this.log('debug', entityType, `Deleted platform pipeline`); await deleteFieldRetentionEnrichPolicy({ - unitedDefinition, + description, + options: { namespace }, esClient: this.esClient, logger, }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/collect_values.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/collect_values.ts similarity index 77% rename from x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/collect_values.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/collect_values.ts index ee26c0dbd64c8..1857e4430b18c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/collect_values.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/collect_values.ts @@ -6,29 +6,28 @@ */ import { isFieldMissingOrEmpty } from '../painless'; -import type { BaseFieldRetentionOperator, FieldRetentionOperatorBuilder } from './types'; - -export interface CollectValues extends BaseFieldRetentionOperator { - operation: 'collect_values'; - maxLength: number; -} +import type { FieldRetentionOperatorBuilder } from './types'; /** * A field retention operator that collects up to `maxLength` values of the specified field. * Values are first collected from the field, then from the enrich field if the field is not present or empty. */ -export const collectValuesProcessor: FieldRetentionOperatorBuilder = ( - { field, maxLength }, +export const collectValuesProcessor: FieldRetentionOperatorBuilder = ( + { destination, retention }, { enrichField } ) => { - const ctxField = `ctx.${field}`; - const ctxEnrichField = `ctx.${enrichField}.${field}`; + if (retention?.operation !== 'collect_values') { + throw new Error('Wrong operation for collectValuesProcessor'); + } + + const ctxField = `ctx.${destination}`; + const ctxEnrichField = `ctx.${enrichField}.${destination}`; return { script: { lang: 'painless', source: ` Set uniqueVals = new HashSet(); - + if (!(${isFieldMissingOrEmpty(ctxField)})) { if(${ctxField} instanceof Collection) { uniqueVals.addAll(${ctxField}); @@ -36,17 +35,17 @@ export const collectValuesProcessor: FieldRetentionOperatorBuilder = (fieldOperator, options) => { - switch (fieldOperator.operation) { +export const fieldOperatorToIngestProcessor = ( + field: FieldDescription, + options: { enrichField: string } +): IngestProcessorContainer => { + if (!field.retention) { + throw new Error('Field retention operator is required'); + } + + switch (field.retention.operation) { case 'prefer_newest_value': - return preferNewestValueProcessor(fieldOperator, options); + return preferNewestValueProcessor(field, options); case 'prefer_oldest_value': - return preferOldestValueProcessor(fieldOperator, options); + return preferOldestValueProcessor(field, options); case 'collect_values': - return collectValuesProcessor(fieldOperator, options); + return collectValuesProcessor(field, options); } }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/prefer_newest_value.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/prefer_newest_value.ts similarity index 68% rename from x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/prefer_newest_value.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/prefer_newest_value.ts index 2378c67e3010d..fa156b976d062 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/prefer_newest_value.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/prefer_newest_value.ts @@ -5,29 +5,25 @@ * 2.0. */ -import type { BaseFieldRetentionOperator, FieldRetentionOperatorBuilder } from './types'; +import type { FieldRetentionOperatorBuilder } from './types'; import { isFieldMissingOrEmpty } from '../painless'; -export interface PreferNewestValue extends BaseFieldRetentionOperator { - operation: 'prefer_newest_value'; -} - /** * A field retention operator that prefers the newest value of the specified field. * If the field is missing or empty, the value from the enrich field is used. */ -export const preferNewestValueProcessor: FieldRetentionOperatorBuilder = ( - { field }, +export const preferNewestValueProcessor: FieldRetentionOperatorBuilder = ( + { destination }, { enrichField } ) => { - const latestField = `ctx.${field}`; - const historicalField = `${enrichField}.${field}`; + const latestField = `ctx.${destination}`; + const historicalField = `${enrichField}.${destination}`; return { set: { if: `(${isFieldMissingOrEmpty(latestField)}) && !(${isFieldMissingOrEmpty( `ctx.${historicalField}` )})`, - field, + field: destination, value: `{{${historicalField}}}`, }, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/prefer_oldest_value.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/prefer_oldest_value.ts similarity index 69% rename from x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/prefer_oldest_value.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/prefer_oldest_value.ts index a8a101d213ba9..c308fd2d50642 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/prefer_oldest_value.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/prefer_oldest_value.ts @@ -5,26 +5,22 @@ * 2.0. */ -import type { BaseFieldRetentionOperator, FieldRetentionOperatorBuilder } from './types'; +import type { FieldRetentionOperatorBuilder } from './types'; import { isFieldMissingOrEmpty } from '../painless'; -export interface PreferOldestValue extends BaseFieldRetentionOperator { - operation: 'prefer_oldest_value'; -} - /** * A field retention operator that prefers the oldest value of the specified field. * If the historical field is missing or empty, the latest value is used. */ -export const preferOldestValueProcessor: FieldRetentionOperatorBuilder = ( - { field }, +export const preferOldestValueProcessor: FieldRetentionOperatorBuilder = ( + { destination }, { enrichField } ) => { - const historicalField = `${enrichField}.${field}`; + const historicalField = `${enrichField}.${destination}`; return { set: { if: `!(${isFieldMissingOrEmpty(`ctx.${historicalField}`)})`, - field, + field: destination, value: `{{${historicalField}}}`, }, }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/types.ts new file mode 100644 index 0000000000000..088d943844d8c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention/types.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { FieldDescription } from '../installation/types'; + +interface Options { + enrichField: string; +} + +export type FieldRetentionOperatorBuilder = ( + field: FieldDescription, + options: Options +) => IngestProcessorContainer; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/types.ts deleted file mode 100644 index e07bd99525600..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/field_retention_definition/types.ts +++ /dev/null @@ -1,34 +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 type { IngestProcessorContainer } from '@elastic/elasticsearch/lib/api/types'; -import type { EntityType } from '../../../../../common/api/entity_analytics/entity_store'; -import type { CollectValues } from './collect_values'; -import type { PreferNewestValue } from './prefer_newest_value'; -import type { PreferOldestValue } from './prefer_oldest_value'; - -export interface FieldRetentionDefinition { - entityType: EntityType; - matchField: string; - fields: FieldRetentionOperator[]; -} - -export interface BaseFieldRetentionOperator { - field: string; - operation: string; -} - -export interface FieldRetentionOperatorBuilderOptions { - enrichField: string; -} - -export type FieldRetentionOperator = PreferNewestValue | PreferOldestValue | CollectValues; - -export type FieldRetentionOperatorBuilder = ( - operator: O, - options: FieldRetentionOperatorBuilderOptions -) => IngestProcessorContainer; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.test.ts similarity index 64% rename from x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts rename to x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.test.ts index 16e5e06aea8f2..81290e832978c 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.test.ts @@ -5,22 +5,29 @@ * 2.0. */ -import { getUnitedEntityDefinition } from './get_united_definition'; +import { duration } from 'moment'; +import { createEngineDescription } from './engine_description'; +import { convertToEntityManagerDefinition } from '../entity_definitions/entity_manager_conversion'; describe('getUnitedEntityDefinition', () => { - const indexPatterns = ['test*']; + const defaultIndexPatterns = ['test*']; describe('host', () => { - const unitedDefinition = getUnitedEntityDefinition({ + const description = createEngineDescription({ entityType: 'host', namespace: 'test', - fieldHistoryLength: 10, - indexPatterns, - syncDelay: '1m', - frequency: '1m', + requestParams: { + fieldHistoryLength: 10, + }, + defaultIndexPatterns, + config: { + syncDelay: duration(60, 'seconds'), + frequency: duration(60, 'seconds'), + developer: { pipelineDebugMode: false }, + }, }); it('mapping', () => { - expect(unitedDefinition.indexMappings).toMatchInlineSnapshot(` + expect(description.indexMappings).toMatchInlineSnapshot(` Object { "properties": Object { "@timestamp": Object { @@ -93,83 +100,14 @@ describe('getUnitedEntityDefinition', () => { } `); }); - it('fieldRetentionDefinition', () => { - expect(unitedDefinition.fieldRetentionDefinition).toMatchInlineSnapshot(` - Object { - "entityType": "host", - "fields": Array [ - Object { - "field": "host.domain", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.hostname", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.id", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.os.name", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.os.type", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.ip", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.mac", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.type", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "host.architecture", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "entity.source", - "operation": "prefer_oldest_value", - }, - Object { - "field": "asset.criticality", - "operation": "prefer_newest_value", - }, - Object { - "field": "host.risk.calculated_level", - "operation": "prefer_newest_value", - }, - Object { - "field": "host.risk.calculated_score", - "operation": "prefer_newest_value", - }, - Object { - "field": "host.risk.calculated_score_norm", - "operation": "prefer_newest_value", - }, - ], - "matchField": "host.name", - } - `); - }); + it('entityManagerDefinition', () => { - expect(unitedDefinition.entityManagerDefinition).toMatchInlineSnapshot(` + const entityManagerDefinition = convertToEntityManagerDefinition(description, { + namespace: 'test', + filter: '', + }); + + expect(entityManagerDefinition).toMatchInlineSnapshot(` Object { "displayNameTemplate": "{{host.name}}", "id": "security_host_test", @@ -183,10 +121,10 @@ describe('getUnitedEntityDefinition', () => { "test*", ], "latest": Object { - "lookbackPeriod": "24h", + "lookbackPeriod": "1d", "settings": Object { - "frequency": "1m", - "syncDelay": "1m", + "frequency": "60s", + "syncDelay": "60s", }, "timestampField": "@timestamp", }, @@ -323,17 +261,22 @@ describe('getUnitedEntityDefinition', () => { }); }); describe('user', () => { - const unitedDefinition = getUnitedEntityDefinition({ + const description = createEngineDescription({ entityType: 'user', namespace: 'test', - fieldHistoryLength: 10, - indexPatterns, - syncDelay: '1m', - frequency: '1m', + requestParams: { + fieldHistoryLength: 10, + }, + defaultIndexPatterns, + config: { + syncDelay: duration(60, 'seconds'), + frequency: duration(60, 'seconds'), + developer: { pipelineDebugMode: false }, + }, }); it('mapping', () => { - expect(unitedDefinition.indexMappings).toMatchInlineSnapshot(` + expect(description.indexMappings).toMatchInlineSnapshot(` Object { "properties": Object { "@timestamp": Object { @@ -397,68 +340,12 @@ describe('getUnitedEntityDefinition', () => { } `); }); - it('fieldRetentionDefinition', () => { - expect(unitedDefinition.fieldRetentionDefinition).toMatchInlineSnapshot(` - Object { - "entityType": "user", - "fields": Array [ - Object { - "field": "user.domain", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "user.email", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "user.full_name", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "user.hash", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "user.id", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "user.roles", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "entity.source", - "operation": "prefer_oldest_value", - }, - Object { - "field": "asset.criticality", - "operation": "prefer_newest_value", - }, - Object { - "field": "user.risk.calculated_level", - "operation": "prefer_newest_value", - }, - Object { - "field": "user.risk.calculated_score", - "operation": "prefer_newest_value", - }, - Object { - "field": "user.risk.calculated_score_norm", - "operation": "prefer_newest_value", - }, - ], - "matchField": "user.name", - } - `); - }); it('entityManagerDefinition', () => { - expect(unitedDefinition.entityManagerDefinition).toMatchInlineSnapshot(` + const entityManagerDefinition = convertToEntityManagerDefinition(description, { + namespace: 'test', + filter: '', + }); + expect(entityManagerDefinition).toMatchInlineSnapshot(` Object { "displayNameTemplate": "{{user.name}}", "id": "security_user_test", @@ -472,10 +359,10 @@ describe('getUnitedEntityDefinition', () => { "test*", ], "latest": Object { - "lookbackPeriod": "24h", + "lookbackPeriod": "1d", "settings": Object { - "frequency": "1m", - "syncDelay": "1m", + "frequency": "60s", + "syncDelay": "60s", }, "timestampField": "@timestamp", }, @@ -589,17 +476,22 @@ describe('getUnitedEntityDefinition', () => { }); describe('service', () => { - const unitedDefinition = getUnitedEntityDefinition({ + const description = createEngineDescription({ entityType: 'service', namespace: 'test', - fieldHistoryLength: 10, - indexPatterns, - syncDelay: '1m', - frequency: '1m', + requestParams: { + fieldHistoryLength: 10, + }, + defaultIndexPatterns, + config: { + syncDelay: duration(60, 'seconds'), + frequency: duration(60, 'seconds'), + developer: { pipelineDebugMode: false }, + }, }); it('mapping', () => { - expect(unitedDefinition.indexMappings).toMatchInlineSnapshot(` + expect(description.indexMappings).toMatchInlineSnapshot(` Object { "properties": Object { "@timestamp": Object { @@ -645,15 +537,6 @@ describe('getUnitedEntityDefinition', () => { "service.node.roles": Object { "type": "keyword", }, - "service.risk.calculated_level": Object { - "type": "keyword", - }, - "service.risk.calculated_score": Object { - "type": "float", - }, - "service.risk.calculated_score_norm": Object { - "type": "float", - }, "service.state": Object { "type": "keyword", }, @@ -667,81 +550,13 @@ describe('getUnitedEntityDefinition', () => { } `); }); - it('fieldRetentionDefinition', () => { - expect(unitedDefinition.fieldRetentionDefinition).toMatchInlineSnapshot(` - Object { - "entityType": "service", - "fields": Array [ - Object { - "field": "service.address", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.environment", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.ephemeral_id", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.id", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.node.name", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.node.roles", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.state", - "operation": "prefer_newest_value", - }, - Object { - "field": "service.type", - "maxLength": 10, - "operation": "collect_values", - }, - Object { - "field": "service.version", - "operation": "prefer_newest_value", - }, - Object { - "field": "entity.source", - "operation": "prefer_oldest_value", - }, - Object { - "field": "asset.criticality", - "operation": "prefer_newest_value", - }, - Object { - "field": "service.risk.calculated_level", - "operation": "prefer_newest_value", - }, - Object { - "field": "service.risk.calculated_score", - "operation": "prefer_newest_value", - }, - Object { - "field": "service.risk.calculated_score_norm", - "operation": "prefer_newest_value", - }, - ], - "matchField": "service.name", - } - `); - }); + it('entityManagerDefinition', () => { - expect(unitedDefinition.entityManagerDefinition).toMatchInlineSnapshot(` + const entityManagerDefinition = convertToEntityManagerDefinition(description, { + namespace: 'test', + filter: '', + }); + expect(entityManagerDefinition).toMatchInlineSnapshot(` Object { "displayNameTemplate": "{{service.name}}", "id": "security_service_test", @@ -755,10 +570,10 @@ describe('getUnitedEntityDefinition', () => { "test*", ], "latest": Object { - "lookbackPeriod": "24h", + "lookbackPeriod": "1d", "settings": Object { - "frequency": "1m", - "syncDelay": "1m", + "frequency": "60s", + "syncDelay": "60s", }, "timestampField": "@timestamp", }, @@ -840,56 +655,6 @@ describe('getUnitedEntityDefinition', () => { "destination": "service.version", "source": "service.version", }, - Object { - "aggregation": Object { - "sort": Object { - "@timestamp": "asc", - }, - "type": "top_value", - }, - "destination": "entity.source", - "source": "_index", - }, - Object { - "aggregation": Object { - "sort": Object { - "@timestamp": "desc", - }, - "type": "top_value", - }, - "destination": "asset.criticality", - "source": "asset.criticality", - }, - Object { - "aggregation": Object { - "sort": Object { - "@timestamp": "desc", - }, - "type": "top_value", - }, - "destination": "service.risk.calculated_level", - "source": "service.risk.calculated_level", - }, - Object { - "aggregation": Object { - "sort": Object { - "@timestamp": "desc", - }, - "type": "top_value", - }, - "destination": "service.risk.calculated_score", - "source": "service.risk.calculated_score", - }, - Object { - "aggregation": Object { - "sort": Object { - "@timestamp": "desc", - }, - "type": "top_value", - }, - "destination": "service.risk.calculated_score_norm", - "source": "service.risk.calculated_score_norm", - }, ], "name": "Security 'service' Entity Store Definition", "type": "service", diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.ts new file mode 100644 index 0000000000000..ccc4ba5f477a3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/engine_description.ts @@ -0,0 +1,95 @@ +/* + * 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 { pipe } from 'fp-ts/lib/function'; + +import { assign, concat, map, merge, update } from 'lodash/fp'; +import { set } from '@kbn/safer-lodash-set/fp'; + +import type { EntityType } from '../../../../../common/api/entity_analytics'; +import { + DEFAULT_FIELD_HISTORY_LENGTH, + DEFAULT_LOOKBACK_PERIOD, + DEFAULT_TIMESTAMP_FIELD, +} from '../entity_definitions/constants'; +import { generateIndexMappings } from '../elasticsearch_assets'; +import { + hostEntityEngineDescription, + userEntityEngineDescription, + universalEntityEngineDescription, + serviceEntityEngineDescription, +} from '../entity_definitions/entity_descriptions'; + +import type { EntityStoreConfig } from '../types'; +import { buildEntityDefinitionId } from '../utils'; +import type { EntityDescription } from '../entity_definitions/types'; +import type { EntityEngineInstallationDescriptor } from './types'; + +const engineDescriptionRegistry: Record = { + host: hostEntityEngineDescription, + user: userEntityEngineDescription, + universal: universalEntityEngineDescription, + service: serviceEntityEngineDescription, +}; +export const getAvailableEntityDescriptions = () => + Object.keys(engineDescriptionRegistry) as EntityType[]; + +interface EngineDescriptionParams { + entityType: EntityType; + namespace: string; + config: EntityStoreConfig; + requestParams?: { + indexPattern?: string; + fieldHistoryLength?: number; + }; + defaultIndexPatterns: string[]; +} + +export const createEngineDescription = (options: EngineDescriptionParams) => { + const { entityType, namespace, config, requestParams, defaultIndexPatterns } = options; + const fieldHistoryLength = requestParams?.fieldHistoryLength || DEFAULT_FIELD_HISTORY_LENGTH; + + const indexPatterns = requestParams?.indexPattern + ? defaultIndexPatterns.concat(requestParams?.indexPattern.split(',')) + : defaultIndexPatterns; + + const description = engineDescriptionRegistry[entityType]; + + const settings: EntityEngineInstallationDescriptor['settings'] = { + syncDelay: `${config.syncDelay.asSeconds()}s`, + frequency: `${config.frequency.asSeconds()}s`, + lookbackPeriod: description.settings?.lookbackPeriod || DEFAULT_LOOKBACK_PERIOD, + timestampField: description.settings?.timestampField || DEFAULT_TIMESTAMP_FIELD, + }; + + const updatedDescription = pipe( + description, + set('id', buildEntityDefinitionId(entityType, namespace)), + update('settings', assign(settings)), + updateIndexPatterns(indexPatterns), + updateRetentionFields(fieldHistoryLength), + addIndexMappings + ) as EntityEngineInstallationDescriptor; + + return updatedDescription; +}; + +const updateIndexPatterns = (indexPatterns: string[]) => + update('indexPatterns', (prev = []) => concat(indexPatterns, prev)); + +const updateRetentionFields = (fieldHistoryLength: number) => + update( + 'fields', + map( + merge({ + retention: { maxLength: fieldHistoryLength }, + aggregation: { limit: fieldHistoryLength }, + }) + ) + ); + +const addIndexMappings = (description: EntityEngineInstallationDescriptor) => + set('indexMappings', generateIndexMappings(description), description); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/types.ts new file mode 100644 index 0000000000000..b68ce5faa1ac4 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/installation/types.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + MappingTypeMapping, + IngestProcessorContainer, + MappingProperty, +} from '@elastic/elasticsearch/lib/api/types'; + +import type { EntityDefinition } from '@kbn/entities-schema'; +import type { EntityType } from '../../../../../common/api/entity_analytics'; + +export type EntityDefinitionMetadataElement = NonNullable[number]; + +export interface EntityEngineInstallationDescriptor { + id: string; + version: string; + entityType: EntityType; + + identityField: string; + + /** + * Default static index patterns to use as the source of entity data. + * By default, the Security Data View patterns will be added to this list. + * API parameters can be used to add additional patterns. + **/ + indexPatterns: string[]; + + /** + * The mappings for the entity store index. + */ + indexMappings: MappingTypeMapping; + + /** + * Field descriptions for the entity. + * Identity fields are not included here as they are treated separately. + */ + fields: FieldDescription[]; + + /** + * Entity manager default pivot transform settings. + * Any kibana.yml configuration will override these settings. + */ + settings: { + syncDelay: string; + frequency: string; + lookbackPeriod: string; + timestampField: string; + }; + + /** + * The ingest pipeline to apply to the entity data. + * This can be an array of processors which get appended to the default pipeline, + * or a function that takes the default processors and returns an array of processors. + **/ + pipeline: + | IngestProcessorContainer[] + | ((defaultProcessors: IngestProcessorContainer[]) => IngestProcessorContainer[]); +} + +export type FieldDescription = EntityDefinitionMetadataElement & { + mapping: MappingProperty; + retention: FieldRetentionOp; +}; + +export type FieldRetentionOp = + | { operation: 'collect_values'; maxLength: number } + | { operation: 'prefer_newest_value' } + | { operation: 'prefer_oldest_value' }; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts index d60f994488fca..3b802fc081e1a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/task/field_retention_enrichment_task.ts @@ -13,7 +13,6 @@ import type { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; -import { getAvailableEntityTypes } from '../../../../../common/entity_analytics/entity_store/constants'; import { EngineComponentResourceEnum, type EntityType, @@ -25,7 +24,7 @@ import { } from './state'; import { INTERVAL, SCOPE, TIMEOUT, TYPE, VERSION } from './constants'; import type { EntityAnalyticsRoutesDeps } from '../../types'; -import { getUnitedEntityDefinitionVersion } from '../united_entity_definitions'; + import { executeFieldRetentionEnrichPolicy } from '../elasticsearch_assets'; import { getEntitiesIndexName } from '../utils'; @@ -33,6 +32,8 @@ import { FIELD_RETENTION_ENRICH_POLICY_EXECUTION_EVENT, ENTITY_STORE_USAGE_EVENT, } from '../../../telemetry/event_based/events'; +import { VERSIONS_BY_ENTITY_TYPE } from '../entity_definitions/constants'; +import { getAvailableEntityDescriptions } from '../installation/engine_description'; const logFactory = (logger: Logger, taskId: string) => @@ -79,10 +80,10 @@ export const registerEntityStoreFieldRetentionEnrichTask = ({ const [coreStart, _] = await getStartServices(); const esClient = coreStart.elasticsearch.client.asInternalUser; - const unitedDefinitionVersion = getUnitedEntityDefinitionVersion(entityType); - return executeFieldRetentionEnrichPolicy({ - unitedDefinition: { namespace, entityType, version: unitedDefinitionVersion }, + entityType, + version: VERSIONS_BY_ENTITY_TYPE[entityType], + options: { namespace }, esClient, logger, }); @@ -202,7 +203,7 @@ export const runTask = async ({ return { state: updatedState }; } - const entityTypes = getAvailableEntityTypes(); + const entityTypes = getAvailableEntityDescriptions(); for (const entityType of entityTypes) { const start = Date.now(); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/constants.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/constants.ts deleted file mode 100644 index 43730fa06357a..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { MappingProperties } from './types'; - -export const BASE_ENTITY_INDEX_MAPPING: MappingProperties = { - '@timestamp': { - type: 'date', - }, - 'asset.criticality': { - type: 'keyword', - }, - 'entity.name': { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - 'entity.source': { - type: 'keyword', - }, -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/definition_utils.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/definition_utils.ts deleted file mode 100644 index a56dcaa253857..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/definition_utils.ts +++ /dev/null @@ -1,86 +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 type { MappingProperty } from '@elastic/elasticsearch/lib/api/types'; -import type { UnitedDefinitionField } from './types'; - -export const collectValues = ({ - field, - mapping = { type: 'keyword' }, - sourceField, - fieldHistoryLength, -}: { - field: string; - mapping?: MappingProperty; - sourceField?: string; - fieldHistoryLength: number; -}): UnitedDefinitionField => ({ - field, - definition: { - source: sourceField ?? field, - destination: field, - aggregation: { - type: 'terms', - limit: fieldHistoryLength, - }, - }, - retention_operator: { operation: 'collect_values', field, maxLength: fieldHistoryLength }, - mapping, -}); - -export const collectValuesWithLength = - (fieldHistoryLength: number) => - (opts: { field: string; mapping?: MappingProperty; sourceField?: string }) => - collectValues({ ...opts, fieldHistoryLength }); - -export const newestValue = ({ - field, - mapping = { type: 'keyword' }, - sourceField, -}: { - field: string; - mapping?: MappingProperty; - sourceField?: string; -}): UnitedDefinitionField => ({ - field, - definition: { - source: sourceField ?? field, - destination: field, - aggregation: { - type: 'top_value', - sort: { - '@timestamp': 'desc', - }, - }, - }, - retention_operator: { operation: 'prefer_newest_value', field }, - mapping, -}); - -export const oldestValue = ({ - field, - mapping = { type: 'keyword' }, - sourceField, -}: { - field: string; - mapping?: MappingProperty; - sourceField?: string; -}): UnitedDefinitionField => ({ - field, - definition: { - source: sourceField ?? field, - destination: field, - aggregation: { - type: 'top_value', - sort: { - '@timestamp': 'asc', - }, - }, - }, - retention_operator: { operation: 'prefer_oldest_value', field }, - mapping, -}); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/common.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/common.ts deleted file mode 100644 index ac974bf119d4a..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/common.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EntityType } from '../../../../../../common/api/entity_analytics/entity_store'; -import { getIdentityFieldForEntityType } from '../../utils'; -import { oldestValue, newestValue } from '../definition_utils'; -import type { UnitedDefinitionField } from '../types'; - -export const getCommonUnitedFieldDefinitions = ({ - entityType, - fieldHistoryLength, -}: { - entityType: EntityType; - fieldHistoryLength: number; -}): UnitedDefinitionField[] => { - const identityField = getIdentityFieldForEntityType(entityType); - return [ - oldestValue({ - sourceField: '_index', - field: 'entity.source', - }), - newestValue({ field: 'asset.criticality' }), - newestValue({ - field: `${entityType}.risk.calculated_level`, - }), - newestValue({ - field: `${entityType}.risk.calculated_score`, - mapping: { - type: 'float', - }, - }), - newestValue({ - field: `${entityType}.risk.calculated_score_norm`, - mapping: { - type: 'float', - }, - }), - { - field: identityField, - }, - ]; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/host.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/host.ts deleted file mode 100644 index 5abeffd05b1f1..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/host.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { collectValuesWithLength } from '../definition_utils'; -import type { UnitedDefinitionBuilder } from '../types'; - -export const HOST_DEFINITION_VERSION = '1.0.0'; -export const getHostUnitedDefinition: UnitedDefinitionBuilder = (fieldHistoryLength: number) => { - const collect = collectValuesWithLength(fieldHistoryLength); - return { - entityType: 'host', - version: HOST_DEFINITION_VERSION, - fields: [ - collect({ field: 'host.domain' }), - collect({ field: 'host.hostname' }), - collect({ field: 'host.id' }), - collect({ - field: 'host.os.name', - mapping: { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - }), - collect({ field: 'host.os.type' }), - collect({ - field: 'host.ip', - mapping: { - type: 'ip', - }, - }), - collect({ field: 'host.mac' }), - collect({ field: 'host.type' }), - collect({ field: 'host.architecture' }), - ], - }; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts deleted file mode 100644 index 26d10046b26ae..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/service.ts +++ /dev/null @@ -1,29 +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 { collectValuesWithLength, newestValue } from '../definition_utils'; -import type { UnitedDefinitionBuilder } from '../types'; - -export const SERVICE_DEFINITION_VERSION = '1.0.0'; -export const getServiceUnitedDefinition: UnitedDefinitionBuilder = (fieldHistoryLength: number) => { - const collect = collectValuesWithLength(fieldHistoryLength); - return { - entityType: 'service', - version: SERVICE_DEFINITION_VERSION, - fields: [ - collect({ field: 'service.address' }), - collect({ field: 'service.environment' }), - collect({ field: 'service.ephemeral_id' }), - collect({ field: 'service.id' }), - collect({ field: 'service.node.name' }), - collect({ field: 'service.node.roles' }), - newestValue({ field: 'service.state' }), - collect({ field: 'service.type' }), - newestValue({ field: 'service.version' }), - ], - }; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/user.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/user.ts deleted file mode 100644 index 3881425df77ce..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/entity_types/user.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { collectValuesWithLength } from '../definition_utils'; -import type { UnitedDefinitionBuilder } from '../types'; - -export const USER_DEFINITION_VERSION = '1.0.0'; -export const getUserUnitedDefinition: UnitedDefinitionBuilder = (fieldHistoryLength: number) => { - const collect = collectValuesWithLength(fieldHistoryLength); - return { - entityType: 'user', - version: USER_DEFINITION_VERSION, - fields: [ - collect({ field: 'user.domain' }), - collect({ field: 'user.email' }), - collect({ - field: 'user.full_name', - mapping: { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - }), - collect({ field: 'user.hash' }), - collect({ field: 'user.id' }), - collect({ field: 'user.roles' }), - ], - }; -}; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts deleted file mode 100644 index 28685172dc7d4..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/get_united_definition.ts +++ /dev/null @@ -1,69 +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 { memoize } from 'lodash'; -import type { EntityType } from '../../../../../common/api/entity_analytics'; -import { - getHostUnitedDefinition, - getUserUnitedDefinition, - getCommonUnitedFieldDefinitions, - USER_DEFINITION_VERSION, - HOST_DEFINITION_VERSION, - getServiceUnitedDefinition, -} from './entity_types'; -import type { UnitedDefinitionBuilder } from './types'; -import { UnitedEntityDefinition } from './united_entity_definition'; -const unitedDefinitionBuilders: Record = { - host: getHostUnitedDefinition, - user: getUserUnitedDefinition, - service: getServiceUnitedDefinition, -}; - -interface Options { - entityType: EntityType; - namespace: string; - fieldHistoryLength: number; - indexPatterns: string[]; - syncDelay: string; - frequency: string; -} - -export const getUnitedEntityDefinition = memoize( - ({ - entityType, - namespace, - fieldHistoryLength, - indexPatterns, - syncDelay, - frequency, - }: Options): UnitedEntityDefinition => { - const unitedDefinition = unitedDefinitionBuilders[entityType](fieldHistoryLength); - - unitedDefinition.fields.push( - ...getCommonUnitedFieldDefinitions({ - entityType, - fieldHistoryLength, - }) - ); - - return new UnitedEntityDefinition({ - ...unitedDefinition, - namespace, - indexPatterns, - syncDelay, - frequency, - }); - } -); - -const versionByEntityType: Record = { - host: HOST_DEFINITION_VERSION, - user: USER_DEFINITION_VERSION, - service: USER_DEFINITION_VERSION, -}; - -export const getUnitedEntityDefinitionVersion = (entityType: EntityType): string => - versionByEntityType[entityType]; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/index.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/index.ts deleted file mode 100644 index 5280e066d5b81..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/index.ts +++ /dev/null @@ -1,10 +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. - */ - -export * from './get_united_definition'; - -export { UnitedEntityDefinition } from './united_entity_definition'; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/types.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/types.ts deleted file mode 100644 index c48c6b09a8e56..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/types.ts +++ /dev/null @@ -1,30 +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 type { MappingProperty, MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; -import type { EntityDefinition } from '@kbn/entities-schema'; -import type { EntityType } from '../../../../../common/api/entity_analytics'; -import type { FieldRetentionOperator } from '../field_retention_definition'; - -export type MappingProperties = NonNullable; - -type EntityDefinitionMetadataElement = NonNullable[number]; - -export interface UnitedDefinitionField { - field: string; - retention_operator?: FieldRetentionOperator; - mapping?: MappingProperty; - definition?: EntityDefinitionMetadataElement; -} - -export interface UnitedEntityDefinitionConfig { - version: string; - entityType: EntityType; - fields: UnitedDefinitionField[]; -} - -export type UnitedDefinitionBuilder = (fieldHistoryLength: number) => UnitedEntityDefinitionConfig; diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/united_entity_definition.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/united_entity_definition.ts deleted file mode 100644 index fc7430ebb1806..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/united_entity_definitions/united_entity_definition.ts +++ /dev/null @@ -1,126 +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 { entityDefinitionSchema, type EntityDefinition } from '@kbn/entities-schema'; -import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/types'; -import type { EntityType } from '../../../../../common/api/entity_analytics/entity_store/common.gen'; -import { DEFAULT_LOOKBACK_PERIOD } from '../constants'; -import { buildEntityDefinitionId, getIdentityFieldForEntityType } from '../utils'; -import type { - FieldRetentionDefinition, - FieldRetentionOperator, -} from '../field_retention_definition'; -import type { MappingProperties, UnitedDefinitionField } from './types'; -import { BASE_ENTITY_INDEX_MAPPING } from './constants'; - -export class UnitedEntityDefinition { - version: string; - entityType: EntityType; - indexPatterns: string[]; - fields: UnitedDefinitionField[]; - namespace: string; - entityManagerDefinition: EntityDefinition; - fieldRetentionDefinition: FieldRetentionDefinition; - indexMappings: MappingTypeMapping; - syncDelay: string; - frequency: string; - - constructor(opts: { - version: string; - entityType: EntityType; - indexPatterns: string[]; - fields: UnitedDefinitionField[]; - namespace: string; - syncDelay: string; - frequency: string; - }) { - this.version = opts.version; - this.entityType = opts.entityType; - this.indexPatterns = opts.indexPatterns; - this.fields = opts.fields; - this.frequency = opts.frequency; - this.syncDelay = opts.syncDelay; - this.namespace = opts.namespace; - this.entityManagerDefinition = this.toEntityManagerDefinition(); - this.fieldRetentionDefinition = this.toFieldRetentionDefinition(); - this.indexMappings = this.toIndexMappings(); - } - - private toEntityManagerDefinition(): EntityDefinition { - const { entityType, namespace, indexPatterns, syncDelay, frequency } = this; - const identityField = getIdentityFieldForEntityType(this.entityType); - const metadata = this.fields - .filter((field) => field.definition) - .map((field) => field.definition!); // eslint-disable-line @typescript-eslint/no-non-null-assertion - - return entityDefinitionSchema.parse({ - id: buildEntityDefinitionId(entityType, namespace), - name: `Security '${entityType}' Entity Store Definition`, - type: entityType, - indexPatterns, - identityFields: [identityField], - displayNameTemplate: `{{${identityField}}}`, - metadata, - latest: { - timestampField: '@timestamp', - lookbackPeriod: DEFAULT_LOOKBACK_PERIOD, - settings: { - syncDelay, - frequency, - }, - }, - version: this.version, - managed: true, - }); - } - - private toFieldRetentionDefinition(): FieldRetentionDefinition { - return { - entityType: this.entityType, - matchField: getIdentityFieldForEntityType(this.entityType), - fields: this.fields - .filter((field) => field.retention_operator !== undefined) - .map((field) => field.retention_operator as FieldRetentionOperator), - }; - } - - private toIndexMappings(): MappingTypeMapping { - const identityField = getIdentityFieldForEntityType(this.entityType); - - const initialMappings: MappingProperties = { - ...BASE_ENTITY_INDEX_MAPPING, - [identityField]: { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }, - }; - - const properties = this.fields.reduce((acc, { field, mapping }) => { - if (!mapping) { - return acc; - } - acc[field] = mapping; - return acc; - }, initialMappings); - - properties[identityField] = { - type: 'keyword', - fields: { - text: { - type: 'match_only_text', - }, - }, - }; - - return { - properties, - }; - } -} diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts index c889d3a8ce34b..5844f539bcad3 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/entity_analytics/entity_store/utils/entity_utils.ts @@ -11,17 +11,12 @@ import { entitiesIndexPattern, } from '@kbn/entities-schema'; import type { DataViewsService, DataView } from '@kbn/data-views-plugin/common'; -import { IDENTITY_FIELD_MAP } from '../../../../../common/entity_analytics/entity_store/constants'; import type { AppClient } from '../../../../types'; import { getRiskScoreLatestIndex } from '../../../../../common/entity_analytics/risk_engine'; import { getAssetCriticalityIndex } from '../../../../../common/entity_analytics/asset_criticality'; import { type EntityType } from '../../../../../common/api/entity_analytics/entity_store/common.gen'; import { entityEngineDescriptorTypeName } from '../saved_object'; -export const getIdentityFieldForEntityType = (entityType: EntityType) => { - return IDENTITY_FIELD_MAP[entityType]; -}; - export const buildIndexPatterns = async ( space: string, appClient: AppClient, diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/field_retention_operators.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/field_retention_operators.ts index 6e8f7dc3f6578..1bb0205a75c81 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/field_retention_operators.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/entity_store/trial_license_complete_tier/field_retention_operators.ts @@ -6,10 +6,8 @@ */ import expect from '@kbn/expect'; -import { - FieldRetentionOperator, - fieldOperatorToIngestProcessor, -} from '@kbn/security-solution-plugin/server/lib/entity_analytics/entity_store/field_retention_definition'; +import { fieldOperatorToIngestProcessor } from '@kbn/security-solution-plugin/server/lib/entity_analytics/entity_store/field_retention'; +import { FieldDescription } from '@kbn/security-solution-plugin/server/lib/entity_analytics/entity_store/installation/types'; import { FtrProviderContext } from '../../../../ftr_provider_context'; import { applyIngestProcessorToDoc } from '../utils/ingest'; export default ({ getService }: FtrProviderContext) => { @@ -22,10 +20,7 @@ export default ({ getService }: FtrProviderContext) => { expect(aSorted).to.eql(bSorted); }; - const applyOperatorToDoc = async ( - operator: FieldRetentionOperator, - docSource: any - ): Promise => { + const applyOperatorToDoc = async (operator: FieldDescription, docSource: any): Promise => { const step = fieldOperatorToIngestProcessor(operator, { enrichField: 'historical' }); return applyIngestProcessorToDoc([step], docSource, es, log); @@ -34,10 +29,16 @@ export default ({ getService }: FtrProviderContext) => { describe('@ess @serverless @skipInServerlessMKI Entity store - Field Retention Pipeline Steps', () => { describe('collect_values operator', () => { it('should return value if no history', async () => { - const op: FieldRetentionOperator = { - operation: 'collect_values', - field: 'test_field', - maxLength: 10, + const op: FieldDescription = { + retention: { operation: 'collect_values', maxLength: 10 }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -50,10 +51,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not take from history if latest field has maxLength values', async () => { - const op: FieldRetentionOperator = { - operation: 'collect_values', - field: 'test_field', - maxLength: 1, + const op: FieldDescription = { + retention: { operation: 'collect_values', maxLength: 1 }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -69,10 +76,16 @@ export default ({ getService }: FtrProviderContext) => { }); it("should take from history if latest field doesn't have maxLength values", async () => { - const op: FieldRetentionOperator = { - operation: 'collect_values', - field: 'test_field', - maxLength: 10, + const op: FieldDescription = { + retention: { operation: 'collect_values', maxLength: 10 }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -88,10 +101,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should only take from history up to maxLength values', async () => { - const op: FieldRetentionOperator = { - operation: 'collect_values', - field: 'test_field', - maxLength: 2, + const op: FieldDescription = { + retention: { operation: 'collect_values', maxLength: 2 }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -107,10 +126,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should handle value not being an array', async () => { - const op: FieldRetentionOperator = { - operation: 'collect_values', - field: 'test_field', - maxLength: 2, + const op: FieldDescription = { + retention: { operation: 'collect_values', maxLength: 2 }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { test_field: 'foo', @@ -125,10 +150,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should handle missing values', async () => { - const op: FieldRetentionOperator = { - operation: 'collect_values', - field: 'test_field', - maxLength: 2, + const op: FieldDescription = { + retention: { operation: 'collect_values', maxLength: 2 }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = {}; @@ -139,9 +170,16 @@ export default ({ getService }: FtrProviderContext) => { }); describe('prefer_newest_value operator', () => { it('should return latest value if no history value', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -153,9 +191,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('latest'); }); it('should return history value if no latest value (undefined)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -169,9 +214,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('historical'); }); it('should return history value if no latest value (empty string)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -186,9 +238,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('historical'); }); it('should return history value if no latest value (empty array)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -203,9 +262,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('historical'); }); it('should return history value if no latest value (empty object)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -220,9 +286,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('historical'); }); it('should return latest value if both latest and history values', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -238,9 +311,16 @@ export default ({ getService }: FtrProviderContext) => { }); it('should handle missing values', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_newest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_newest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = {}; @@ -251,9 +331,16 @@ export default ({ getService }: FtrProviderContext) => { }); describe('prefer_oldest_value operator', () => { it('should return history value if no latest value', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_oldest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_oldest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -267,9 +354,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('historical'); }); it('should return latest value if no history value (undefined)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_oldest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_oldest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -284,9 +378,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('latest'); }); it('should return latest value if no history value (empty string)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_oldest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_oldest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -301,9 +402,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('latest'); }); it('should return latest value if no history value (empty array)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_oldest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_oldest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -318,9 +426,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('latest'); }); it('should return latest value if no history value (empty object)', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_oldest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_oldest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { @@ -335,9 +450,16 @@ export default ({ getService }: FtrProviderContext) => { expect(resultDoc.test_field).to.eql('latest'); }); it('should return history value if both latest and history values', async () => { - const op: FieldRetentionOperator = { - operation: 'prefer_oldest_value', - field: 'test_field', + const op: FieldDescription = { + retention: { operation: 'prefer_oldest_value' }, + destination: 'test_field', + source: 'test_field', + aggregation: { + type: 'terms', + limit: 10, + lookbackPeriod: undefined, + }, + mapping: { type: 'keyword' }, }; const doc = { diff --git a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts index 3dda5f462afdd..f57c2692c7253 100644 --- a/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts +++ b/x-pack/test/security_solution_api_integration/test_suites/entity_analytics/risk_engine/trial_license_complete_tier/asset_criticality_csv_upload.ts @@ -109,7 +109,7 @@ export default ({ getService }: FtrProviderContext) => { { index: 2, message: - 'Invalid entity type "invalid_entity", expected to be one of: user, host, service', + 'Invalid entity type "invalid_entity", expected to be one of: user, host, service, universal', }, { index: 3, From fe988fa55272a4ee6006a3a3ffc899a03fd4390c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Fri, 3 Jan 2025 17:44:52 +0100 Subject: [PATCH 13/43] [Infra] Refactor useSearchSession to useReloadRequestTime for timestamp based refresh (#205338) ## Summary Refactoring `useSearchSession` to `useReloadRequestTime` to decouple the usage of search.session for refreshing, avoiding a re-rendering loop issue. This change is then propagated to various Lens charts to make use of that instead of relying on searchSessionId. Closes #203412 ## How to test - Go to Infrastructure Inventory page in Observability - Enable Auto-Refresh - Click on a infra item (host/service/etc) to open the fly-out - Update the time range in the flyout - Things should update without spurious refreshes. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../components/asset_details/charts/chart.tsx | 6 ++-- .../components/kpis/container_kpi_charts.tsx | 16 ++++----- .../components/kpis/host_kpi_charts.tsx | 6 ++-- .../asset_details/components/kpis/kpi.tsx | 5 +-- .../asset_details/context_providers.tsx | 4 +-- .../asset_details/hooks/use_date_picker.ts | 8 ++--- .../hooks/use_loading_state.test.ts | 28 +++++++-------- .../asset_details/hooks/use_loading_state.ts | 14 ++++---- .../hooks/use_request_observable.test.ts | 20 ++++++----- .../tabs/metadata/metadata.test.tsx | 20 ++++++----- .../tabs/overview/kpis/kpi_grid.tsx | 8 ++--- .../asset_details/tabs/overview/logs.tsx | 6 ++-- .../public/components/lens/lens_chart.tsx | 8 ++--- .../public/components/lens/lens_wrapper.tsx | 10 +++--- .../infra/public/hooks/use_fetcher.tsx | 12 +++---- .../infra/public/hooks/use_lens_attributes.ts | 12 +++---- .../public/hooks/use_reload_request_time.ts | 25 ++++++++++++++ .../infra/public/hooks/use_search_session.ts | 34 ------------------- .../hosts/components/kpis/kpi_charts.tsx | 10 +++--- .../hosts/components/tabs/metrics/chart.tsx | 8 ++--- .../metrics/hosts/hooks/use_unified_search.ts | 24 ++++++------- .../infra/public/pages/metrics/index.tsx | 6 ++-- 22 files changed, 143 insertions(+), 147 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/infra/public/hooks/use_reload_request_time.ts delete mode 100644 x-pack/solutions/observability/plugins/infra/public/hooks/use_search_session.ts diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/charts/chart.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/charts/chart.tsx index 100515a3f13a0..3dddd1b92908c 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/charts/chart.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/charts/chart.tsx @@ -16,7 +16,7 @@ import type { LensChartProps } from '../../lens'; import { type BrushEndArgs, LensChart, type OnFilterEvent } from '../../lens'; import { useDatePickerContext } from '../hooks/use_date_picker'; import { extractRangeFromChartFilterEvent } from './chart_utils'; -import { useSearchSessionContext } from '../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../hooks/use_reload_request_time'; export type ChartProps = Pick & { id: string; @@ -35,7 +35,7 @@ export const Chart = ({ lensAttributes, }: ChartProps) => { const { setDateRange } = useDatePickerContext(); - const { searchSessionId } = useSearchSessionContext(); + const { reloadRequestTime } = useReloadRequestTimeContext(); const { services: { dataViews }, } = useKibanaContextForPlugin(); @@ -87,7 +87,7 @@ export const Chart = ({ borderRadius="m" dateRange={dateRange} height={METRIC_CHART_HEIGHT} - searchSessionId={searchSessionId} + lastReloadRequestTime={reloadRequestTime} filters={filters} lensAttributes={lensAttributes} overrides={overrides} diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx index 95291d157745e..2420c0372c903 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/container_kpi_charts.tsx @@ -22,7 +22,7 @@ export interface ContainerKpiChartsProps { dateRange: TimeRange; query?: Query; filters?: Filter[]; - searchSessionId?: string; + lastReloadRequestTime?: number; options?: { getSubtitle?: (formulaValue: string) => string; }; @@ -34,7 +34,7 @@ export const ContainerKpiCharts = ({ dataView, filters, query, - searchSessionId, + lastReloadRequestTime, loading = false, }: ContainerKpiChartsProps) => { const isDockerContainer = useIntegrationCheck({ dependsOn: INTEGRATIONS.docker }); @@ -53,7 +53,7 @@ export const ContainerKpiCharts = ({ dataView={dataView} filters={filters} query={query} - searchSessionId={searchSessionId} + lastReloadRequestTime={lastReloadRequestTime} loading={loading} /> )} @@ -63,7 +63,7 @@ export const ContainerKpiCharts = ({ dataView={dataView} filters={filters} query={query} - searchSessionId={searchSessionId} + lastReloadRequestTime={lastReloadRequestTime} loading={loading} /> )} @@ -76,7 +76,7 @@ const DockerKpiCharts = ({ dataView, filters, query, - searchSessionId, + lastReloadRequestTime, loading = false, }: ContainerKpiChartsProps) => { const charts = useDockerContainerKpiCharts({ @@ -92,7 +92,7 @@ const DockerKpiCharts = ({ dateRange={dateRange} filters={filters} query={query} - searchSessionId={searchSessionId} + lastReloadRequestTime={lastReloadRequestTime} loading={loading} />
@@ -107,7 +107,7 @@ const KubernetesKpiCharts = ({ filters, options, query, - searchSessionId, + lastReloadRequestTime, loading = false, }: ContainerKpiChartsProps) => { const charts = useK8sContainerKpiCharts({ @@ -123,7 +123,7 @@ const KubernetesKpiCharts = ({ dateRange={dateRange} filters={filters} query={query} - searchSessionId={searchSessionId} + lastReloadRequestTime={lastReloadRequestTime} loading={loading} />
diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/host_kpi_charts.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/host_kpi_charts.tsx index 64345efb1af8a..2b68627176a0c 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/host_kpi_charts.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/host_kpi_charts.tsx @@ -17,7 +17,7 @@ export interface HostKpiChartsProps { dateRange: TimeRange; query?: Query; filters?: Filter[]; - searchSessionId?: string; + lastReloadRequestTime?: number; getSubtitle?: (formulaValue: string) => string; loading?: boolean; } @@ -28,7 +28,7 @@ export const HostKpiCharts = ({ filters, getSubtitle, query, - searchSessionId, + lastReloadRequestTime, loading = false, }: HostKpiChartsProps) => { const charts = useHostKpiCharts({ @@ -45,7 +45,7 @@ export const HostKpiCharts = ({ dateRange={dateRange} filters={filters} query={query} - searchSessionId={searchSessionId} + lastReloadRequestTime={lastReloadRequestTime} loading={loading} />
diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/kpi.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/kpi.tsx index 3545a54d813db..6143133b561e5 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/kpi.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/components/kpis/kpi.tsx @@ -15,7 +15,7 @@ export const Kpi = ({ dateRange, query, filters, - searchSessionId, + lastReloadRequestTime, loading, ...chartProps }: LensConfig & { @@ -25,6 +25,7 @@ export const Kpi = ({ filters?: Filter[]; searchSessionId?: string; loading?: boolean; + lastReloadRequestTime?: number; }) => { const tooltipContent = useMemo( () => @@ -44,7 +45,7 @@ export const Kpi = ({ query={query} loading={loading} toolTip={tooltipContent} - searchSessionId={searchSessionId} + lastReloadRequestTime={lastReloadRequestTime} disableTriggers hidePanelTitles /> diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/context_providers.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/context_providers.tsx index 8f76e3de99d5a..2dc9e877f3df7 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/context_providers.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/context_providers.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { SearchSessionProvider } from '../../hooks/use_search_session'; +import { ReloadRequestTimeProvider } from '../../hooks/use_reload_request_time'; import { AssetDetailsRenderPropsProvider } from './hooks/use_asset_details_render_props'; import { DatePickerProvider } from './hooks/use_date_picker'; import { LoadingStateProvider } from './hooks/use_loading_state'; @@ -22,7 +22,7 @@ const RenderWithOptionalSearchSessionProvider = ({ }) => { if (renderMode.mode === 'flyout') { // flyout mode requires its own search session so that it doesn't interfere with the main page - return {children}; + return {children}; } return <>{children}; }; diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_date_picker.ts b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_date_picker.ts index a35bda74e22a3..e200b0d58b997 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_date_picker.ts +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_date_picker.ts @@ -10,7 +10,7 @@ import createContainer from 'constate'; import { useCallback, useMemo, useState } from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; import { BehaviorSubject } from 'rxjs'; -import { useSearchSessionContext } from '../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../hooks/use_reload_request_time'; import { parseDateRange } from '../../../utils/datemath'; import type { AssetDetailsProps } from '../types'; import { getDefaultDateRange, toTimestampRange } from '../utils'; @@ -22,7 +22,7 @@ export function useDatePicker({ dateRange = getDefaultDateRange(), autoRefresh, }: UseDateRangeProviderProps) { - const { updateSearchSessionId } = useSearchSessionContext(); + const { updateReloadRequestTime } = useReloadRequestTimeContext(); const autoRefreshTick$ = useMemo(() => new BehaviorSubject(null), []); const autoRefreshConfig$ = useMemo( () => new BehaviorSubject(undefined), @@ -52,9 +52,9 @@ export function useDatePicker({ (newDateRange: TimeRange) => { setUrlState({ dateRange: newDateRange }); setParsedDateRange(parseDateRange(newDateRange)); - updateSearchSessionId(); + updateReloadRequestTime(); }, - [setUrlState, updateSearchSessionId] + [setUrlState, updateReloadRequestTime] ); const onRefresh = useCallback( diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.test.ts b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.test.ts index 2475c97cec0e8..17d62a48caeb1 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.test.ts +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.test.ts @@ -12,12 +12,12 @@ import { BehaviorSubject, EMPTY, of, Subject, Subscription, skip } from 'rxjs'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { coreMock } from '@kbn/core/public/mocks'; import { SearchSessionState, waitUntilNextSessionCompletes$ } from '@kbn/data-plugin/public'; -import { useSearchSessionContext } from '../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../hooks/use_reload_request_time'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; jest.mock('./use_date_picker'); jest.mock('../../../hooks/use_kibana'); -jest.mock('../../../hooks/use_search_session'); +jest.mock('../../../hooks/use_reload_request_time'); jest.mock('@kbn/data-plugin/public', () => ({ ...jest.requireActual('@kbn/data-plugin/public'), @@ -36,8 +36,8 @@ const waitUntilNextSessionCompletesMock$ = waitUntilNextSessionCompletes$ as jes typeof waitUntilNextSessionCompletes$ >; -const useSearchSessionContextMock = useSearchSessionContext as jest.MockedFunction< - typeof useSearchSessionContext +const useRequestTimeContextMock = useReloadRequestTimeContext as jest.MockedFunction< + typeof useReloadRequestTimeContext >; describe('useLoadingState', () => { @@ -50,12 +50,12 @@ describe('useLoadingState', () => { const sessionState$ = new BehaviorSubject(SearchSessionState.None); - const updateSearchSessionIdMock = jest.fn(); + const updateReloadRequestTimeMock = jest.fn(); - const mockSearchSessionContext = () => { - useSearchSessionContextMock.mockReturnValue({ - updateSearchSessionId: updateSearchSessionIdMock, - searchSessionId: '', + const mockRequestTimeContext = () => { + useRequestTimeContextMock.mockReturnValue({ + updateReloadRequestTime: updateReloadRequestTimeMock, + reloadRequestTime: 0, }); }; @@ -89,7 +89,7 @@ describe('useLoadingState', () => { subscription = new Subscription(); jest.useFakeTimers(); waitUntilNextSessionCompletesMock$.mockReturnValue(of(SearchSessionState.None)); - mockSearchSessionContext(); + mockRequestTimeContext(); mockUseKibana(); mockDatePickerContext(); }); @@ -152,7 +152,7 @@ describe('useLoadingState', () => { unmount(); }); - it('should not call updateSearchSessionId if waitUntilNextSessionCompletesMock$ returns empty', async () => { + it('should not call updateRequestTime if waitUntilNextSessionCompletesMock$ returns empty', async () => { const { unmount } = renderHook(() => useLoadingState()); // waitUntilNextSessionCompletes$ returns EMPTY when the status is loading or none @@ -165,12 +165,12 @@ describe('useLoadingState', () => { }); // only the mount call must happen - await waitFor(() => expect(updateSearchSessionIdMock).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(updateReloadRequestTimeMock).toHaveBeenCalledTimes(1)); unmount(); }); - it('should call updateSearchSessionId when waitUntilNextSessionCompletesMock$ returns', async () => { + it('should call updateRequestTime when waitUntilNextSessionCompletesMock$ returns', async () => { const { unmount } = renderHook(() => useLoadingState()); // waitUntilNextSessionCompletes$ returns something when the status is Completed or BackgroundCompleted @@ -182,7 +182,7 @@ describe('useLoadingState', () => { jest.runOnlyPendingTimers(); }); - await waitFor(() => expect(updateSearchSessionIdMock).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(updateReloadRequestTimeMock).toHaveBeenCalledTimes(2)); unmount(); }); diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.ts b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.ts index f3098b6dca8f8..a8228b821dd6f 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.ts +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_loading_state.ts @@ -26,7 +26,7 @@ import { useCallback, useEffect, useMemo } from 'react'; import { SearchSessionState, waitUntilNextSessionCompletes$ } from '@kbn/data-plugin/public'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { useDatePickerContext } from './use_date_picker'; -import { useSearchSessionContext } from '../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../hooks/use_reload_request_time'; export type RequestState = 'running' | 'done' | 'error'; const WAIT_MS = 1000; @@ -37,15 +37,15 @@ export const useLoadingState = () => { const { data: { search }, } = services; - const { updateSearchSessionId } = useSearchSessionContext(); + const { updateReloadRequestTime } = useReloadRequestTimeContext(); const isAutoRefreshRequestPending$ = useMemo(() => new BehaviorSubject(false), []); const requestsCount$ = useMemo(() => new BehaviorSubject(0), []); const requestState$ = useMemo(() => new BehaviorSubject(null), []); useEffect(() => { - updateSearchSessionId(); - }, [updateSearchSessionId]); + updateReloadRequestTime(); + }, [updateReloadRequestTime]); const waitUntilRequestsCompletes$ = useCallback( () => @@ -140,7 +140,7 @@ export const useLoadingState = () => { // This will only be called when Lens is used in the Asset Details page return waitUntilNextSessionCompletes$(search.session).pipe( tap(() => { - updateSearchSessionId(); + updateReloadRequestTime(); }) ); } @@ -149,7 +149,7 @@ export const useLoadingState = () => { return of(null).pipe( tap(() => { if (!isAutoRefreshRequestPending) { - updateSearchSessionId(); + updateReloadRequestTime(); } }) ); @@ -169,7 +169,7 @@ export const useLoadingState = () => { requestState$, requestsCount$, search.session, - updateSearchSessionId, + updateReloadRequestTime, waitUntilRequestsCompletes$, ]); diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_request_observable.test.ts b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_request_observable.test.ts index 9be24cbfcf3f4..8ea36562c04a4 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_request_observable.test.ts +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/hooks/use_request_observable.test.ts @@ -9,12 +9,12 @@ import { act, waitFor, renderHook } from '@testing-library/react'; import { useRequestObservable } from './use_request_observable'; import { type RequestState, useLoadingStateContext } from './use_loading_state'; import { useDatePickerContext, type UseDateRangeProviderProps } from './use_date_picker'; -import { useSearchSessionContext } from '../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../hooks/use_reload_request_time'; import { BehaviorSubject } from 'rxjs'; jest.mock('./use_loading_state'); jest.mock('./use_date_picker'); -jest.mock('../../../hooks/use_search_session'); +jest.mock('../../../hooks/use_reload_request_time'); const useLoadingStateContextMock = useLoadingStateContext as jest.MockedFunction< typeof useLoadingStateContext @@ -23,8 +23,8 @@ const useDatePickerContextMock = useDatePickerContext as jest.MockedFunction< typeof useDatePickerContext >; -const useSearchSessionMock = useSearchSessionContext as jest.MockedFunction< - typeof useSearchSessionContext +const useReloadRequestTimeMock = useReloadRequestTimeContext as jest.MockedFunction< + typeof useReloadRequestTimeContext >; describe('useRequestObservable', () => { @@ -38,10 +38,10 @@ describe('useRequestObservable', () => { // needed to spy on `next` function requestStateMock$.next = jest.fn(); - const mockUseSearchSessionMock = () => { - useSearchSessionMock.mockReturnValue({ - updateSearchSessionId: jest.fn(() => {}), - searchSessionId: '', + const mockUseRequestTimeMock = () => { + useReloadRequestTimeMock.mockReturnValue({ + updateReloadRequestTime: jest.fn(() => {}), + reloadRequestTime: 0, }); }; @@ -59,12 +59,14 @@ describe('useRequestObservable', () => { }; beforeEach(() => { + jest.useFakeTimers(); mockDatePickerContext(); - mockUseSearchSessionMock(); + mockUseRequestTimeMock(); mockUseLoadingStateContextMock(); }); afterEach(() => { + jest.useRealTimers(); jest.clearAllMocks(); }); diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx index 37b72f03e80b3..d8bd93e03ca40 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/metadata/metadata.test.tsx @@ -16,18 +16,18 @@ import { ContextProviders } from '../../context_providers'; import { coreMock } from '@kbn/core/public/mocks'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; -import { useSearchSessionContext } from '../../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../../hooks/use_reload_request_time'; jest.mock('../../../../containers/metrics_source'); jest.mock('../../hooks/use_metadata'); jest.mock('../../../../hooks/use_kibana'); -jest.mock('../../../../hooks/use_search_session'); +jest.mock('../../../../hooks/use_reload_request_time'); const useKibanaMock = useKibanaContextForPlugin as jest.MockedFunction< typeof useKibanaContextForPlugin >; -const useSearchSessionContextMock = useSearchSessionContext as jest.MockedFunction< - typeof useSearchSessionContext +const useRequestTimeContextMock = useReloadRequestTimeContext as jest.MockedFunction< + typeof useReloadRequestTimeContext >; const mockUseKibana = () => { @@ -39,10 +39,10 @@ const mockUseKibana = () => { } as unknown as ReturnType); }; -const mockSearchSessionContext = () => { - useSearchSessionContextMock.mockReturnValue({ - updateSearchSessionId: jest.fn(), - searchSessionId: '', +const mockRequestTimeContext = () => { + useRequestTimeContextMock.mockReturnValue({ + updateReloadRequestTime: jest.fn(), + reloadRequestTime: 0, }); }; @@ -73,11 +73,13 @@ const renderHostMetadata = () => ); beforeEach(() => { + jest.useFakeTimers(); mockUseKibana(); - mockSearchSessionContext(); + mockRequestTimeContext(); }); afterEach(() => { + jest.useRealTimers(); jest.clearAllMocks(); }); diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx index a908e5c1f6234..2e731ad9fb828 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/kpis/kpi_grid.tsx @@ -16,7 +16,7 @@ import { import { buildCombinedAssetFilter } from '../../../../../utils/filters/build'; import { HostKpiCharts } from '../../../components/kpis/host_kpi_charts'; import { ContainerKpiCharts } from '../../../components/kpis/container_kpi_charts'; -import { useSearchSessionContext } from '../../../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../../../hooks/use_reload_request_time'; interface Props { dataView?: DataView; @@ -26,7 +26,7 @@ interface Props { } export const KPIGrid = ({ assetId, assetType, dataView, dateRange }: Props) => { - const { searchSessionId } = useSearchSessionContext(); + const { reloadRequestTime } = useReloadRequestTimeContext(); const filters = useMemo(() => { return [ @@ -45,14 +45,14 @@ export const KPIGrid = ({ assetId, assetType, dataView, dateRange }: Props) => { dataView={dataView} filters={filters} dateRange={dateRange} - searchSessionId={searchSessionId} + lastReloadRequestTime={reloadRequestTime} /> ) : ( )}
diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/logs.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/logs.tsx index 5ca334acf20ec..df2979fe4feb6 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/logs.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/overview/logs.tsx @@ -14,7 +14,7 @@ import { type InventoryItemType, } from '@kbn/metrics-data-access-plugin/common'; import { buildCombinedAssetFilter } from '../../../../utils/filters/build'; -import { useSearchSessionContext } from '../../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../../hooks/use_reload_request_time'; import { useLogsCharts } from '../../hooks/use_log_charts'; import { Kpi } from '../../components/kpis/kpi'; @@ -26,7 +26,7 @@ interface Props { } export const LogsContent = ({ assetId, assetType, dataView, dateRange }: Props) => { - const { searchSessionId } = useSearchSessionContext(); + const { reloadRequestTime } = useReloadRequestTimeContext(); const filters = useMemo(() => { return [ @@ -50,7 +50,7 @@ export const LogsContent = ({ assetId, assetType, dataView, dateRange }: Props) {...chartProps} dateRange={dateRange} filters={filters} - searchSessionId={searchSessionId} + lastReloadRequestTime={reloadRequestTime} />
))} diff --git a/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_chart.tsx b/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_chart.tsx index c8cc3e58b8c95..6e3c4c7bb5c33 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_chart.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_chart.tsx @@ -35,7 +35,7 @@ const DEFAULT_DISABLED_ACTIONS = [ export type LensChartProps = BaseChartProps & Pick & { toolTip?: React.ReactElement; - searchSessionId?: string; + reloadRequestTime?: number; description?: string; } & { lensAttributes: UseLensAttributesParams; @@ -55,7 +55,7 @@ export const LensChart = React.memo( onFilter, overrides, toolTip, - searchSessionId, + reloadRequestTime, disableTriggers = false, height = MIN_HEIGHT, loading = false, @@ -71,7 +71,7 @@ export const LensChart = React.memo( timeRange: dateRange, query, filters, - searchSessionId, + lastReloadRequestTime: reloadRequestTime, }); const handleBeforeBadgesRender = useCallback((messages: UserMessage[]) => { @@ -141,7 +141,7 @@ export const LensChart = React.memo( query={query} overrides={overrides} onBrushEnd={onBrushEnd} - searchSessionId={searchSessionId} + lastReloadRequestTime={reloadRequestTime} onFilter={onFilter} onBeforeBadgesRender={handleBeforeBadgesRender} /> diff --git a/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_wrapper.tsx b/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_wrapper.tsx index 0350b16a512a3..d647e9683a92d 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_wrapper.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/lens/lens_wrapper.tsx @@ -18,10 +18,10 @@ export const LensWrapper = ({ attributes, dateRange, filters, - searchSessionId, loading = false, onLoad, query, + lastReloadRequestTime, ...props }: LensWrapperProps) => { const { euiTheme } = useEuiTheme(); @@ -33,7 +33,7 @@ export const LensWrapper = ({ dateRange, filters, query, - searchSessionId, + lastReloadRequestTime, }); const ref = useRef(null); @@ -62,7 +62,7 @@ export const LensWrapper = ({ dateRange, filters, query, - searchSessionId, + lastReloadRequestTime, }); } }, [ @@ -71,7 +71,7 @@ export const LensWrapper = ({ filters, intersectionObserverEntry?.isIntersecting, query, - searchSessionId, + lastReloadRequestTime, ]); const handleOnLoad = useCallback( @@ -109,7 +109,7 @@ export const LensWrapper = ({ {isLoading && } Pro data: undefined, status: FETCH_STATUS.NOT_INITIATED, }); - const { searchSessionId } = useSearchSessionContext(); - const [cachedSearchSessionId, setCachedSearchSessionId] = useState(''); + const { reloadRequestTime } = useReloadRequestTimeContext(); + const [cachedReloadRequestTime, setCachedReloadRequestTime] = useState(reloadRequestTime); const autoFetchRef = useRef(autoFetch); const controller = useRef(new AbortController()); @@ -198,10 +198,10 @@ export function useFetcher Pro useEffect(() => { // Allows the caller of useFetcher to control when the fetch can be triggered if (autoFetch) { - setCachedSearchSessionId(searchSessionId); + setCachedReloadRequestTime(reloadRequestTime); } autoFetchRef.current = autoFetch; - }, [autoFetch, searchSessionId]); + }, [autoFetch, reloadRequestTime]); useEffect(() => { return () => { @@ -213,7 +213,7 @@ export function useFetcher Pro if (autoFetchRef.current) { triggerFetch(); } - }, [autoFetchRef, fetchWithAbort, cachedSearchSessionId, triggerFetch]); + }, [autoFetchRef, fetchWithAbort, cachedReloadRequestTime, triggerFetch]); return useMemo( () => ({ diff --git a/x-pack/solutions/observability/plugins/infra/public/hooks/use_lens_attributes.ts b/x-pack/solutions/observability/plugins/infra/public/hooks/use_lens_attributes.ts index dc386b205e706..6d8950f17f677 100644 --- a/x-pack/solutions/observability/plugins/infra/public/hooks/use_lens_attributes.ts +++ b/x-pack/solutions/observability/plugins/infra/public/hooks/use_lens_attributes.ts @@ -65,12 +65,12 @@ export const useLensAttributes = (params: UseLensAttributesParams) => { timeRange, query, filters, - searchSessionId, + lastReloadRequestTime, }: { timeRange: TimeRange; filters: Filter[]; query: Query | AggregateQuery; - searchSessionId?: string; + lastReloadRequestTime?: number; }) => () => { const injectedAttributes = injectFilters({ filters, query }); @@ -80,7 +80,7 @@ export const useLensAttributes = (params: UseLensAttributesParams) => { id: '', timeRange, attributes: injectedAttributes, - searchSessionId, + lastReloadRequestTime, }, { openInNewTab: true, @@ -96,15 +96,15 @@ export const useLensAttributes = (params: UseLensAttributesParams) => { timeRange, filters = [], query = { language: 'kuery', query: '' }, - searchSessionId, + lastReloadRequestTime, }: { timeRange: TimeRange; filters?: Filter[]; query?: Query | AggregateQuery; - searchSessionId?: string; + lastReloadRequestTime?: number; }) => { const openInLens = getOpenInLensAction( - openInLensAction({ timeRange, filters, query, searchSessionId }) + openInLensAction({ timeRange, filters, query, lastReloadRequestTime }) ); return [openInLens]; }, diff --git a/x-pack/solutions/observability/plugins/infra/public/hooks/use_reload_request_time.ts b/x-pack/solutions/observability/plugins/infra/public/hooks/use_reload_request_time.ts new file mode 100644 index 0000000000000..4b3f3e32e80e8 --- /dev/null +++ b/x-pack/solutions/observability/plugins/infra/public/hooks/use_reload_request_time.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import createContainer from 'constate'; +import { useCallback, useState } from 'react'; + +export const useReloadRequestTime = () => { + const [reloadRequestTime, setReloadRequestTime] = useState(Date.now()); + + const updateReloadRequestTime = useCallback(() => { + setReloadRequestTime(Date.now()); + }, []); + + return { + updateReloadRequestTime, + reloadRequestTime, + }; +}; + +export const [ReloadRequestTimeProvider, useReloadRequestTimeContext] = + createContainer(useReloadRequestTime); diff --git a/x-pack/solutions/observability/plugins/infra/public/hooks/use_search_session.ts b/x-pack/solutions/observability/plugins/infra/public/hooks/use_search_session.ts deleted file mode 100644 index d0d818cf4bf93..0000000000000 --- a/x-pack/solutions/observability/plugins/infra/public/hooks/use_search_session.ts +++ /dev/null @@ -1,34 +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 createContainer from 'constate'; -import { useCallback, useEffect, useState } from 'react'; -import { useKibanaContextForPlugin } from './use_kibana'; - -export const useSearchSession = () => { - const { services } = useKibanaContextForPlugin(); - const { - data: { search }, - } = services; - const [searchSessionId, setSearchSessionId] = useState(''); - - const updateSearchSessionId = useCallback(() => { - const sessionId = search.session.start(); - setSearchSessionId(sessionId); - }, [search.session]); - - useEffect(() => { - updateSearchSessionId(); - }, [updateSearchSessionId]); - - return { - updateSearchSessionId, - searchSessionId, - }; -}; - -export const [SearchSessionProvider, useSearchSessionContext] = createContainer(useSearchSession); diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_charts.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_charts.tsx index dc945af7d0d37..23031bda3f912 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_charts.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/kpis/kpi_charts.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { useSearchSessionContext } from '../../../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../../../hooks/use_reload_request_time'; import { HostKpiCharts } from '../../../../../components/asset_details'; import { buildCombinedAssetFilter } from '../../../../../utils/filters/build'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search'; @@ -17,7 +17,7 @@ import { useMetricsDataViewContext } from '../../../../../containers/metrics_sou export const KpiCharts = () => { const { searchCriteria } = useUnifiedSearchContext(); - const { searchSessionId } = useSearchSessionContext(); + const { reloadRequestTime } = useReloadRequestTimeContext(); const { hostNodes, loading: hostsLoading } = useHostsViewContext(); const { loading: hostCountLoading, count: hostCount } = useHostCountContext(); const { metricsView } = useMetricsDataViewContext(); @@ -60,14 +60,14 @@ export const KpiCharts = () => { }); }; - // prevents requestTs and searchCriteria state from reloading the chart + // prevents requests and searchCriteria state from reloading the chart // we want it to reload only once the table has finished loading. // attributes passed to useAfterLoadedState don't need to be memoized const { afterLoadedState } = useAfterLoadedState(loading, { dateRange: searchCriteria.dateRange, query: shouldUseSearchCriteria ? searchCriteria.query : undefined, filters, - searchSessionId, + reloadRequestTime, getSubtitle, }); @@ -77,7 +77,7 @@ export const KpiCharts = () => { dateRange={afterLoadedState.dateRange} filters={afterLoadedState.filters} query={afterLoadedState.query} - searchSessionId={afterLoadedState.searchSessionId} + lastReloadRequestTime={afterLoadedState.reloadRequestTime} getSubtitle={afterLoadedState.getSubtitle} loading={loading} /> diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx index cf282d4ed3f29..d78fd1ee30b3f 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/metrics/chart.tsx @@ -7,7 +7,7 @@ import React from 'react'; import type { LensConfig, LensDataviewDataset } from '@kbn/lens-embeddable-utils/config_builder'; import useAsync from 'react-use/lib/useAsync'; -import { useSearchSessionContext } from '../../../../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../../../../hooks/use_reload_request_time'; import { resolveDataView } from '../../../../../../utils/data_view'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; import { HOST_NAME_FIELD } from '../../../../../../../common/constants'; @@ -26,7 +26,7 @@ export type ChartProps = LensConfig & { export const Chart = ({ id, ...chartProps }: ChartProps) => { const { searchCriteria } = useUnifiedSearchContext(); const { loading } = useHostsViewContext(); - const { searchSessionId } = useSearchSessionContext(); + const { reloadRequestTime } = useReloadRequestTimeContext(); const { currentPage } = useHostsTableContext(); const { services: { dataViews }, @@ -40,7 +40,7 @@ export const Chart = ({ id, ...chartProps }: ChartProps) => { const { afterLoadedState } = useAfterLoadedState(loading, { dateRange: searchCriteria.dateRange, query: shouldUseSearchCriteria ? searchCriteria.query : undefined, - searchSessionId, + reloadRequestTime, }); const { value: filters = [] } = useAsync(async () => { @@ -77,7 +77,7 @@ export const Chart = ({ id, ...chartProps }: ChartProps) => { loading={loading} filters={filters} query={afterLoadedState.query} - searchSessionId={afterLoadedState.searchSessionId} + lastReloadRequestTime={afterLoadedState.reloadRequestTime} /> ); }; diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts index cfbb8aeb7ee80..2ff8062787f2b 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -13,7 +13,7 @@ import deepEqual from 'fast-deep-equal'; import useEffectOnce from 'react-use/lib/useEffectOnce'; import { useKibanaQuerySettings } from '@kbn/observability-shared-plugin/public'; import { useTimeRange } from '../../../../hooks/use_time_range'; -import { useSearchSessionContext } from '../../../../hooks/use_search_session'; +import { useReloadRequestTimeContext } from '../../../../hooks/use_reload_request_time'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { telemetryTimeRangeFormatter } from '../../../../../common/formatters/telemetry_time_range'; import { useMetricsDataViewContext } from '../../../../containers/metrics_source'; @@ -43,7 +43,7 @@ export const useUnifiedSearch = () => { const [error, setError] = useState(null); const [searchCriteria, setSearch] = useHostsUrlState(); const { metricsView } = useMetricsDataViewContext(); - const { updateSearchSessionId } = useSearchSessionContext(); + const { updateReloadRequestTime } = useReloadRequestTimeContext(); const { services } = useKibanaContextForPlugin(); const kibanaQuerySettings = useKibanaQuerySettings(); @@ -73,33 +73,33 @@ export const useUnifiedSearch = () => { const onFiltersChange = useCallback( (filters: Filter[]) => { setSearch({ type: 'SET_FILTERS', filters }); - updateSearchSessionId(); + updateReloadRequestTime(); }, - [setSearch, updateSearchSessionId] + [setSearch, updateReloadRequestTime] ); const onPanelFiltersChange = useCallback( (panelFilters: Filter[]) => { setSearch({ type: 'SET_PANEL_FILTERS', panelFilters }); - updateSearchSessionId(); + updateReloadRequestTime(); }, - [setSearch, updateSearchSessionId] + [setSearch, updateReloadRequestTime] ); const onLimitChange = useCallback( (limit: number) => { setSearch({ type: 'SET_LIMIT', limit }); - updateSearchSessionId(); + updateReloadRequestTime(); }, - [setSearch, updateSearchSessionId] + [setSearch, updateReloadRequestTime] ); const onDateRangeChange = useCallback( (dateRange: StringDateRange) => { setSearch({ type: 'SET_DATE_RANGE', dateRange }); - updateSearchSessionId(); + updateReloadRequestTime(); }, - [setSearch, updateSearchSessionId] + [setSearch, updateReloadRequestTime] ); const onQueryChange = useCallback( @@ -108,12 +108,12 @@ export const useUnifiedSearch = () => { setError(null); validateQuery(query); setSearch({ type: 'SET_QUERY', query }); - updateSearchSessionId(); + updateReloadRequestTime(); } catch (err) { setError(err); } }, - [validateQuery, setSearch, updateSearchSessionId] + [validateQuery, setSearch, updateReloadRequestTime] ); const onSubmit = useCallback( diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/index.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/index.tsx index aab707003b224..295aaa8efa295 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/index.tsx @@ -35,7 +35,7 @@ import { NotFoundPage } from '../404'; import { ReactQueryProvider } from '../../containers/react_query_provider'; import { usePluginConfig } from '../../containers/plugin_config_context'; import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; -import { SearchSessionProvider } from '../../hooks/use_search_session'; +import { ReloadRequestTimeProvider } from '../../hooks/use_reload_request_time'; import { OnboardingFlow } from '../../components/shared/templates/no_data_config'; const ADD_DATA_LABEL = i18n.translate('xpack.infra.metricsHeaderAddDataButtonLabel', { @@ -75,7 +75,7 @@ export const InfrastructurePage = () => { - + { /> - + From 9933cc65950e065da42df3ca8bf4d98aad787c43 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Fri, 3 Jan 2025 08:52:50 -0800 Subject: [PATCH 14/43] [Cloud Security] Contextual flyout table sorting (#203950) ## Summary This PR adds sorting capability to Alerts, Misconfiguration and Vulnerabilities table https://github.com/user-attachments/assets/12cc203e-470c-4166-bb8a-e2b64f4141e3 --- .../common/utils/helpers.test.ts | 75 ++++++++--- .../common/utils/helpers.ts | 30 +++-- .../hooks/use_misconfiguration_findings.ts | 17 ++- .../src/hooks/use_vulnerabilities_findings.ts | 34 ++++- .../public/src/types.ts | 4 +- .../public/src/utils/hooks_utils.ts | 7 +- .../alerts_findings_details_table.tsx | 126 ++++++++++++++---- ...isconfiguration_findings_details_table.tsx | 74 +++++++--- ...vulnerabilities_findings_details_table.tsx | 85 +++++++----- 9 files changed, 337 insertions(+), 115 deletions(-) diff --git a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts index 6b85d2ead28fe..edcef278ab593 100644 --- a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts +++ b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.test.ts @@ -318,8 +318,18 @@ describe('test helper methods', () => { }); describe('buildEntityAlertsQuery', () => { - const getExpectedAlertsQuery = (size?: number, severity?: string) => { + const field: 'host.name' | 'user.name' = 'host.name'; + const query = 'exampleHost'; + const to = 'Tomorrow'; + const from = 'Today'; + const getExpectedAlertsQuery = ( + size?: number, + severity?: string, + sortField?: string, + sortDirection?: 'asc' | 'desc' + ) => { return { + sort: sortField ? [{ [sortField]: sortDirection }] : [], size: size || 0, _source: false, fields: [ @@ -379,37 +389,64 @@ describe('test helper methods', () => { }; it('should return the correct query when given all params', () => { - const field = 'host.name'; - const query = 'exampleHost'; - const to = 'Tomorrow'; - const from = 'Today'; const size = 100; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + }; - expect(buildEntityAlertsQuery(field, to, from, query, size)).toEqual( - getExpectedAlertsQuery(size) - ); + expect(buildEntityAlertsQuery(testObjectParams)).toEqual(getExpectedAlertsQuery(size)); }); it('should return the correct query when not given size', () => { - const field = 'host.name'; - const query = 'exampleHost'; - const to = 'Tomorrow'; - const from = 'Today'; const size = undefined; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + }; - expect(buildEntityAlertsQuery(field, to, from, query)).toEqual(getExpectedAlertsQuery(size)); + expect(buildEntityAlertsQuery(testObjectParams)).toEqual(getExpectedAlertsQuery(size)); }); it('should return the correct query when given severity query', () => { - const field = 'host.name'; - const query = 'exampleHost'; - const to = 'Tomorrow'; - const from = 'Today'; const size = undefined; const severity = 'low'; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + severity, + }; + + expect(buildEntityAlertsQuery(testObjectParams)).toEqual(getExpectedAlertsQuery(size, 'low')); + }); + + it('should return the correct query when given sort parameter', () => { + const size = undefined; + const severity = 'low'; + const sortField = 'sort.field'; + const sortDirection = 'asc'; + const testObjectParams = { + field, + to, + from, + queryValue: query, + size, + severity, + sortField, + sortDirection, + }; - expect(buildEntityAlertsQuery(field, to, from, query, size, severity)).toEqual( - getExpectedAlertsQuery(size, 'low') + expect(buildEntityAlertsQuery(testObjectParams)).toEqual( + getExpectedAlertsQuery(size, 'low', sortField, sortDirection) ); }); }); diff --git a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts index 8335fea705069..4283436418eab 100644 --- a/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts +++ b/x-pack/platform/packages/shared/kbn-cloud-security-posture/common/utils/helpers.ts @@ -9,6 +9,17 @@ import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; import { i18n } from '@kbn/i18n'; import type { CspBenchmarkRulesStates } from '../schema/rules/latest'; +interface BuildEntityAlertsQueryParams { + field: 'user.name' | 'host.name'; + to: string; + from: string; + queryValue?: string; + size?: number; + severity?: string; + sortField?: string; + sortDirection?: string; +} + export const defaultErrorMessage = i18n.translate( 'sharedPlatformPackages.csp.common.utils.helpers.unknownError', { @@ -106,17 +117,20 @@ export const buildVulnerabilityEntityFlyoutPreviewQuery = ( return buildGenericEntityFlyoutPreviewQuery(field, queryValue, status, queryField); }; -export const buildEntityAlertsQuery = ( - field: string, - to: string, - from: string, - queryValue?: string, - size?: number, - severity?: string -) => { +export const buildEntityAlertsQuery = ({ + field, + to, + from, + queryValue = '', + size = 0, + severity, + sortField, + sortDirection, +}: BuildEntityAlertsQueryParams) => { return { size: size || 0, _source: false, + sort: sortField ? [{ [sortField]: sortDirection }] : [], fields: [ '_id', '_index', diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts index d07ac51cc6075..e77a9d61cb686 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_misconfiguration_findings.ts @@ -23,6 +23,18 @@ import { getMisconfigurationAggregationCount, } from '../utils/hooks_utils'; +export enum MISCONFIGURATION { + RESULT_EVALUATION = 'result.evaluation', + RULE_NAME = 'rule.name', +} +export interface MisconfigurationFindingTableDetailsFields { + [MISCONFIGURATION.RESULT_EVALUATION]: string; + [MISCONFIGURATION.RULE_NAME]: string; +} + +export type MisconfigurationFindingDetailFields = Pick & + MisconfigurationFindingTableDetailsFields; + export const useMisconfigurationFindings = (options: UseCspOptions) => { const { data, @@ -50,10 +62,11 @@ export const useMisconfigurationFindings = (options: UseCspOptions) => { return { count: getMisconfigurationAggregationCount(aggregations?.count.buckets), rows: hits.hits.map((finding) => ({ - result: finding._source?.result, rule: finding?._source?.rule, resource: finding?._source?.resource, - })) as Array>, + [MISCONFIGURATION.RULE_NAME]: finding?._source?.rule?.name, + [MISCONFIGURATION.RESULT_EVALUATION]: finding._source?.result?.evaluation, + })) as MisconfigurationFindingDetailFields[], }; }, { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts index d8e44ad5c4328..cf39aa295dffc 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/hooks/use_vulnerabilities_findings.ts @@ -14,17 +14,43 @@ import { AggregationsMultiBucketAggregateBase, AggregationsStringRareTermsBucketKeys, } from '@elastic/elasticsearch/lib/api/types'; -import type { CspVulnerabilityFinding } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; +import type { + CspVulnerabilityFinding, + Vulnerability, +} from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; import type { CoreStart } from '@kbn/core/public'; import type { CspClientPluginStartDeps, UseCspOptions } from '../types'; import { showErrorToast } from '../..'; import { getVulnerabilitiesAggregationCount, getVulnerabilitiesQuery } from '../utils/hooks_utils'; +export enum VULNERABILITY { + ID = 'vulnerability.id', + SEVERITY = 'vulnerability.severity', + PACKAGE_NAME = 'vulnerability.package.name', +} + type LatestFindingsRequest = IKibanaSearchRequest; type LatestFindingsResponse = IKibanaSearchResponse< SearchResponse >; +export interface VulnerabilitiesPackage extends Vulnerability { + package: { + name: string; + version: string; + }; +} + +export interface VulnerabilitiesFindingTableDetailsFields { + [VULNERABILITY.ID]: string; + [VULNERABILITY.SEVERITY]: string; + [VULNERABILITY.PACKAGE_NAME]: string; +} + +export type VulnerabilitiesFindingDetailFields = Pick & + Pick & + VulnerabilitiesFindingTableDetailsFields; + interface FindingsAggs { count: AggregationsMultiBucketAggregateBase; } @@ -56,7 +82,11 @@ export const useVulnerabilitiesFindings = (options: UseCspOptions) => { rows: hits.hits.map((finding) => ({ vulnerability: finding._source?.vulnerability, resource: finding._source?.resource, - })) as Array>, + score: finding._source?.vulnerability?.score, + [VULNERABILITY.ID]: finding._source?.vulnerability?.id, + [VULNERABILITY.SEVERITY]: finding._source?.vulnerability?.severity, + [VULNERABILITY.PACKAGE_NAME]: finding._source?.package?.name, + })) as VulnerabilitiesFindingDetailFields[], }; }, { diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts index 03fa60479806b..738df0265a9c9 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/types.ts @@ -64,7 +64,9 @@ export interface CspBaseEsQuery { } export interface UseCspOptions extends CspBaseEsQuery { - sort: string[][]; + sort: Array<{ + [key: string]: string; + }>; enabled: boolean; pageSize: number; ignore_unavailable?: boolean; diff --git a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts index 88416477021ca..324549098ed17 100644 --- a/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts +++ b/x-pack/solutions/security/packages/kbn-cloud-security-posture/public/src/utils/hooks_utils.ts @@ -73,12 +73,11 @@ export const getMisconfigurationAggregationCount = ( }; export const buildMisconfigurationsFindingsQuery = ( - { query }: UseCspOptions, + { query, sort }: UseCspOptions, rulesStates: CspBenchmarkRulesStates, isPreview = false ) => { const mutedRulesFilterQuery = buildMutedRulesFilter(rulesStates); - return { index: CDR_MISCONFIGURATIONS_INDEX_PATTERN, size: isPreview ? 0 : 500, @@ -86,6 +85,7 @@ export const buildMisconfigurationsFindingsQuery = ( ignore_unavailable: true, query: buildMisconfigurationsFindingsQueryWithFilters(query, mutedRulesFilterQuery), _source: MISCONFIGURATIONS_SOURCE_FIELDS, + sort, }; }; @@ -163,12 +163,13 @@ export const getFindingsCountAggQueryVulnerabilities = () => ({ }, }); -export const getVulnerabilitiesQuery = ({ query }: UseCspOptions, isPreview = false) => ({ +export const getVulnerabilitiesQuery = ({ query, sort }: UseCspOptions, isPreview = false) => ({ index: CDR_VULNERABILITIES_INDEX_PATTERN, size: isPreview ? 0 : 500, aggs: getFindingsCountAggQueryVulnerabilities(), ignore_unavailable: true, query: buildVulnerabilityFindingsQueryWithFilters(query), + sort, }); const buildVulnerabilityFindingsQueryWithFilters = (query: UseCspOptions['query']) => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx index 2b1a2002667c2..78c5604615903 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx @@ -7,7 +7,7 @@ import React, { memo, useCallback, useEffect, useState } from 'react'; import { capitalize } from 'lodash'; -import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; @@ -38,24 +38,35 @@ import { ALERT_PREVIEW_BANNER } from '../../../flyout/document_details/preview/c import { FILTER_OPEN, FILTER_ACKNOWLEDGED } from '../../../../common/types'; import { useNonClosedAlerts } from '../../hooks/use_non_closed_alerts'; +enum KIBANA_ALERTS { + SEVERITY = 'kibana.alert.severity', + RULE_NAME = 'kibana.alert.rule.name', + WORKFLOW_STATUS = 'kibana.alert.workflow_status', +} + type AlertSeverity = 'low' | 'medium' | 'high' | 'critical'; +type AlertsSortFieldType = + | 'id' + | 'index' + | KIBANA_ALERTS.SEVERITY + | KIBANA_ALERTS.WORKFLOW_STATUS + | KIBANA_ALERTS.RULE_NAME; + interface ResultAlertsField { _id: string[]; _index: string[]; - 'kibana.alert.rule.uuid': string[]; - 'kibana.alert.severity': AlertSeverity[]; - 'kibana.alert.rule.name': string[]; - 'kibana.alert.workflow_status': string[]; + [KIBANA_ALERTS.SEVERITY]: AlertSeverity[]; + [KIBANA_ALERTS.RULE_NAME]: string[]; + [KIBANA_ALERTS.WORKFLOW_STATUS]: string[]; } interface ContextualFlyoutAlertsField { id: string; index: string; - ruleUuid: string; - ruleName: string; - severity: AlertSeverity; - status: string; + [KIBANA_ALERTS.SEVERITY]: AlertSeverity; + [KIBANA_ALERTS.RULE_NAME]: string; + [KIBANA_ALERTS.WORKFLOW_STATUS]: string; } interface AlertsDetailsFields { @@ -76,6 +87,16 @@ export const AlertsDetailsTable = memo( const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); + const [sortField, setSortField] = useState(KIBANA_ALERTS.SEVERITY); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + const alertsPagination = (alerts: ContextualFlyoutAlertsField[]) => { let pageOfItems; @@ -95,7 +116,16 @@ export const AlertsDetailsTable = memo( const { to, from } = useGlobalTime(); const { signalIndexName } = useSignalIndex(); const { data, setQuery } = useQueryAlerts({ - query: buildEntityAlertsQuery(field, to, from, value, 500, ''), + query: buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: '', + sortField, + sortDirection, + }), queryName: ALERTS_QUERY_NAMES.BY_RULE_BY_STATUS, indexName: signalIndexName, }); @@ -124,12 +154,32 @@ export const AlertsDetailsTable = memo( color: getSeverityColor(key), filter: () => { setCurrentFilter(key); - setQuery(buildEntityAlertsQuery(field, to, from, value, 500, key)); + setQuery( + buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: key, + sortField, + sortDirection, + }) + ); }, isCurrentFilter: currentFilter === key, reset: (event: React.MouseEvent) => { setCurrentFilter(''); - setQuery(buildEntityAlertsQuery(field, to, from, value, 500, '')); + setQuery( + buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: '', + }) + ); event?.stopPropagation(); }, })); @@ -139,10 +189,9 @@ export const AlertsDetailsTable = memo( return { id: item.fields?._id?.[0], index: item.fields?._index?.[0], - ruleName: item.fields?.['kibana.alert.rule.name']?.[0], - ruleUuid: item.fields?.['kibana.alert.rule.uuid']?.[0], - severity: item.fields?.['kibana.alert.severity']?.[0], - status: item.fields?.['kibana.alert.workflow_status']?.[0], + [KIBANA_ALERTS.RULE_NAME]: item.fields?.[KIBANA_ALERTS.RULE_NAME]?.[0], + [KIBANA_ALERTS.SEVERITY]: item.fields?.[KIBANA_ALERTS.SEVERITY]?.[0], + [KIBANA_ALERTS.WORKFLOW_STATUS]: item.fields?.[KIBANA_ALERTS.WORKFLOW_STATUS]?.[0], }; } ); @@ -156,13 +205,34 @@ export const AlertsDetailsTable = memo( pageSizeOptions: [10, 25, 100], }; - const onTableChange = ({ page }: Criteria) => { - if (page) { - const { index, size } = page; - setPageIndex(index); - setPageSize(size); - } - }; + const onTableChange = useCallback( + ({ page, sort }: Criteria) => { + if (page) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + + if (sort) { + const { field: fieldSort, direction } = sort; + setSortField(fieldSort); + setSortDirection(direction); + setQuery( + buildEntityAlertsQuery({ + field, + to, + from, + queryValue: value, + size: 500, + severity: currentFilter, + sortField: fieldSort, + sortDirection: direction, + }) + ); + } + }, + [currentFilter, field, from, setQuery, to, value] + ); const { openPreviewPanel } = useExpandableFlyoutApi(); @@ -196,7 +266,7 @@ export const AlertsDetailsTable = memo( ), }, { - field: 'ruleName', + field: KIBANA_ALERTS.RULE_NAME, render: (ruleName: string) => {ruleName}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.alerts.table.ruleNameColumnName', @@ -205,9 +275,10 @@ export const AlertsDetailsTable = memo( } ), width: '55%', + sortable: true, }, { - field: 'severity', + field: KIBANA_ALERTS.SEVERITY, render: (severity: AlertSeverity) => ( @@ -220,9 +291,10 @@ export const AlertsDetailsTable = memo( } ), width: '20%', + sortable: true, }, { - field: 'status', + field: KIBANA_ALERTS.WORKFLOW_STATUS, render: (status: string) => {capitalize(status)}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.alerts.table.statusColumnName', @@ -231,6 +303,7 @@ export const AlertsDetailsTable = memo( } ), width: '20%', + sortable: true, }, ]; @@ -281,6 +354,7 @@ export const AlertsDetailsTable = memo( pagination={pagination} onChange={onTableChange} data-test-subj={'securitySolutionFlyoutMisconfigurationFindingsTable'} + sorting={sorting} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx index 61c89e196a183..c03f7585cfc72 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/misconfiguration_findings_details_table.tsx @@ -5,15 +5,18 @@ * 2.0. */ -import React, { memo, useEffect, useState } from 'react'; -import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import React, { memo, useCallback, useEffect, useState } from 'react'; +import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon } from '@elastic/eui'; -import { useMisconfigurationFindings } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; +import type { MisconfigurationFindingDetailFields } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; +import { + useMisconfigurationFindings, + MISCONFIGURATION, +} from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_findings'; import { i18n } from '@kbn/i18n'; +import type { CspFindingResult } from '@kbn/cloud-security-posture-common'; import { MISCONFIGURATION_STATUS, - type CspFinding, - type CspFindingResult, buildMisconfigurationEntityFlyoutPreviewQuery, } from '@kbn/cloud-security-posture-common'; import { euiThemeVars } from '@kbn/ui-theme'; @@ -32,7 +35,11 @@ import { SecurityPageName } from '@kbn/deeplinks-security'; import { useHasMisconfigurations } from '@kbn/cloud-security-posture/src/hooks/use_has_misconfigurations'; import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; -type MisconfigurationFindingDetailFields = Pick; +type MisconfigurationSortFieldType = + | MISCONFIGURATION.RESULT_EVALUATION + | MISCONFIGURATION.RULE_NAME + | 'resource' + | 'rule'; const getFindingsStats = ( passedFindingsStats: number, @@ -95,9 +102,17 @@ export const MisconfigurationFindingsDetailsTable = memo( const [currentFilter, setCurrentFilter] = useState(''); + const [sortField, setSortField] = useState( + MISCONFIGURATION.RESULT_EVALUATION + ); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const sortFieldDirection: { [key: string]: string } = {}; + sortFieldDirection[sortField] = sortDirection; + const { data } = useMisconfigurationFindings({ query: buildMisconfigurationEntityFlyoutPreviewQuery(field, value, currentFilter), - sort: [], + sort: [sortFieldDirection], enabled: true, pageSize: 1, }); @@ -107,6 +122,13 @@ export const MisconfigurationFindingsDetailsTable = memo( const [pageIndex, setPageIndex] = useState(0); const [pageSize, setPageSize] = useState(10); + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; + const findingsPagination = (findings: MisconfigurationFindingDetailFields[]) => { let pageOfItems; @@ -134,14 +156,21 @@ export const MisconfigurationFindingsDetailsTable = memo( totalItemCount, pageSizeOptions: [10, 25, 100], }; - - const onTableChange = ({ page }: Criteria) => { - if (page) { - const { index, size } = page; - setPageIndex(index); - setPageSize(size); - } - }; + const onTableChange = useCallback( + ({ page, sort }: Criteria) => { + if (page) { + const { index, size } = page; + setPageIndex(index); + setPageSize(size); + } + if (sort) { + const { field: fieldSort, direction } = sort; + setSortField(fieldSort); + setSortDirection(direction); + } + }, + [] + ); const getNavUrlParams = useGetNavigationUrlParams(); @@ -189,8 +218,10 @@ export const MisconfigurationFindingsDetailsTable = memo( ), }, { - field: 'result', - render: (result: CspFindingResult) => , + field: MISCONFIGURATION.RESULT_EVALUATION, + render: (result: CspFindingResult['evaluation'] | undefined) => ( + + ), name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.misconfigurations.table.resultColumnName', { @@ -198,10 +229,13 @@ export const MisconfigurationFindingsDetailsTable = memo( } ), width: `${resultWidth}px`, + sortable: true, }, { - field: 'rule', - render: (rule: CspBenchmarkRuleMetadata) => {rule?.name}, + field: MISCONFIGURATION.RULE_NAME, + render: (ruleName: CspBenchmarkRuleMetadata['name']) => ( + {ruleName} + ), name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.misconfigurations.table.ruleColumnName', { @@ -209,6 +243,7 @@ export const MisconfigurationFindingsDetailsTable = memo( } ), width: `calc(100% - ${linkWidth + resultWidth}px)`, + sortable: true, }, ]; @@ -245,6 +280,7 @@ export const MisconfigurationFindingsDetailsTable = memo( pagination={pagination} onChange={onTableChange} data-test-subj={'securitySolutionFlyoutMisconfigurationFindingsTable'} + sorting={sorting} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx index 84f85af509b85..5afc639624eda 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/vulnerabilities_findings_details_table.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useEffect, useState } from 'react'; -import type { Criteria, EuiBasicTableColumn } from '@elastic/eui'; +import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { @@ -14,11 +14,14 @@ import { type VulnSeverity, } from '@kbn/cloud-security-posture-common'; import { DistributionBar } from '@kbn/security-solution-distribution-bar'; -import { useVulnerabilitiesFindings } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings'; import type { - CspVulnerabilityFinding, - Vulnerability, -} from '@kbn/cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding'; + VulnerabilitiesFindingDetailFields, + VulnerabilitiesPackage, +} from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings'; +import { + useVulnerabilitiesFindings, + VULNERABILITY, +} from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_findings'; import { getVulnerabilityStats, CVSScoreBadge, @@ -35,17 +38,13 @@ import { useGetNavigationUrlParams } from '@kbn/cloud-security-posture/src/hooks import { useHasVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_has_vulnerabilities'; import { SecuritySolutionLinkAnchor } from '../../../common/components/links'; -type VulnerabilitiesFindingDetailFields = Pick< - CspVulnerabilityFinding, - 'vulnerability' | 'resource' ->; - -interface VulnerabilitiesPackage extends Vulnerability { - package: { - name: string; - version: string; - }; -} +type VulnerabilitySortFieldType = + | 'score' + | 'vulnerability' + | 'resource' + | VULNERABILITY.SEVERITY + | VULNERABILITY.ID + | VULNERABILITY.PACKAGE_NAME; export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: string }) => { useEffect(() => { @@ -56,10 +55,23 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str }, []); const [currentFilter, setCurrentFilter] = useState(''); + const [sortField, setSortField] = useState(VULNERABILITY.SEVERITY); + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); + + const sortFieldDirection: { [key: string]: string } = {}; + sortFieldDirection[sortField === 'score' ? 'vulnerability.score.base' : sortField] = + sortDirection; + + const sorting: EuiTableSortingType = { + sort: { + field: sortField, + direction: sortDirection, + }, + }; const { data } = useVulnerabilitiesFindings({ query: buildVulnerabilityEntityFlyoutPreviewQuery('host.name', value, currentFilter), - sort: [], + sort: [sortFieldDirection], enabled: true, pageSize: 1, }); @@ -96,12 +108,17 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str pageSizeOptions: [10, 25, 100], }; - const onTableChange = ({ page }: Criteria) => { + const onTableChange = ({ page, sort }: Criteria) => { if (page) { const { index, size } = page; setPageIndex(index); setPageSize(size); } + if (sort) { + const { field: fieldSort, direction } = sort; + setSortField(fieldSort); + setSortDirection(direction); + } }; const getNavUrlParams = useGetNavigationUrlParams(); @@ -164,22 +181,20 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str ), }, { - field: 'vulnerability', - render: (vulnerability: Vulnerability) => {vulnerability?.id}, + field: VULNERABILITY.ID, + render: (id: string) => {id}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.vulnerability.table.resultColumnName', { defaultMessage: 'Vulnerability' } ), width: '20%', + sortable: true, }, { - field: 'vulnerability', - render: (vulnerability: Vulnerability) => ( + field: 'score', + render: (score: { version?: string; base?: number }) => ( - + ), name: i18n.translate( @@ -187,15 +202,14 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str { defaultMessage: 'CVSS' } ), width: '15%', + sortable: true, }, { - field: 'vulnerability', - render: (vulnerability: Vulnerability) => ( + field: VULNERABILITY.SEVERITY, + render: (severity: string) => ( <> - + ), @@ -204,17 +218,17 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str { defaultMessage: 'Severity' } ), width: '20%', + sortable: true, }, { - field: 'vulnerability', - render: (vulnerability: VulnerabilitiesPackage) => ( - {vulnerability?.package?.name} - ), + field: VULNERABILITY.PACKAGE_NAME, + render: (packageName: string) => {packageName}, name: i18n.translate( 'xpack.securitySolution.flyout.left.insights.vulnerability.table.ruleColumnName', { defaultMessage: 'Package' } ), width: '40%', + sortable: true, }, ]; @@ -248,6 +262,7 @@ export const VulnerabilitiesFindingsDetailsTable = memo(({ value }: { value: str pagination={pagination} onChange={onTableChange} data-test-subj={'securitySolutionFlyoutVulnerabilitiesFindingsTable'} + sorting={sorting} /> From e8ef186be87a7b62a30f5e73c486f1521be0b19c Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 3 Jan 2025 12:22:39 -0500 Subject: [PATCH 15/43] Dependency ownership for security solution teams, part 1 (#205523) ## Summary This updates our `renovate.json` configuration to mark the security solution teams as owners of their set of dependencies. --- renovate.json | 197 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 187 insertions(+), 10 deletions(-) diff --git a/renovate.json b/renovate.json index 7e528ca255379..13adda6e9da31 100644 --- a/renovate.json +++ b/renovate.json @@ -2249,33 +2249,126 @@ "cypress" ], "reviewers": [ - "Team:apm", - "Team: SecuritySolution" + "team:apm", + "team:security-solution" ], "matchBaseBranches": [ "main" ], "labels": [ "buildkite-ci", + "backport:all-open", + "release_note:skip", "ci:all-cypress-suites" ], "minimumReleaseAge": "7 days", "enabled": true }, { - "groupName": "security solution modules", + "groupName": "zod", + "matchDepNames": [ + "zod" + ], + "reviewers": [ + "team:security-solution" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team: SecuritySolution", + "backport:all-open", + "release_note:skip" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "adm-zip", "matchDepNames": [ - "zod", - "langchain" + "adm-zip", + "@types/adm-zip" ], "reviewers": [ - "Team: SecuritySolution" + "team:security-solution" ], "matchBaseBranches": [ "main" ], "labels": [ - "Team: SecuritySolution" + "Team: SecuritySolution", + "backport:all-open", + "release_note:skip" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "rbush", + "matchDepNames": [ + "rbush", + "@types/rbush" + ], + "reviewers": [ + "team:security-solution" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team: SecuritySolution", + "backport:all-open", + "release_note:skip" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "buildkite-test-collector", + "matchDepNames": [ + "buildkite-test-collector" + ], + "reviewers": [ + "team:security-solution" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team: SecuritySolution", + "backport:all-open", + "release_note:skip" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "misc security solution deps", + "matchDepNames": [ + "camelcase-keys", + "copy-to-clipboard", + "css-box-model", + "i18n-iso-countries", + "node-diff3", + "re-resizable", + "react-diff-view", + "snakecase-keys", + "suricata-sid-db", + "ts-easing", + "typescript-fsa", + "typescript-fsa-reducers", + "unidiff" + ], + "reviewers": [ + "team:security-solution" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team: SecuritySolution", + "backport:all-open", + "release_note:skip" ], "minimumReleaseAge": "7 days", "enabled": true @@ -2732,17 +2825,101 @@ "enabled": true }, { - "groupName": "native-hdr-histogram", + "groupName": "@appland/sql-parser", "matchDepNames": [ - "native-hdr-histogram" + "@appland/sql-parser" ], "reviewers": [ - "team:obs-ux-infra_services-team" + "team:security-defend-workflows" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Defend Workflows", + "release_note:skip", + "backport:all-open" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "langchain", + "matchDepNames": [ + "langchain", + "@langchain/community", + "@langchain/core", + "@langchain/google-genai", + "@langchain/langgraph", + "@langchain/openai" + ], + "reviewers": [ + "team:security-defend-workflows" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Defend Workflows", + "release_note:skip", + "backport:all-open" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "@google/generative-ai", + "matchDepNames": [ + "@google/generative-ai" + ], + "reviewers": [ + "team:security-defend-workflows" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Defend Workflows", + "release_note:skip", + "backport:all-open" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "@smithy", + "matchDepNames": [ + "@smithy/eventstream-codec", + "@smithy/util-utf8" + ], + "reviewers": [ + "team:security-defend-workflows" + ], + "matchBaseBranches": [ + "main" + ], + "labels": [ + "Team:Defend Workflows", + "release_note:skip", + "backport:all-open" + ], + "minimumReleaseAge": "7 days", + "enabled": true + }, + { + "groupName": "nunjucks", + "matchDepNames": [ + "@types/nunjucks", + "nunjucks" + ], + "reviewers": [ + "team:security-scalability" ], "matchBaseBranches": [ "main" ], "labels": [ + "Team:Security-Scalability", "release_note:skip", "backport:all-open" ], From 8902f7026c4fea148c31b83bf1c2c14e59850d45 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Fri, 3 Jan 2025 17:25:13 +0000 Subject: [PATCH 16/43] Remove links to Logs Explorer (#203685) Resolves https://github.com/elastic/kibana/issues/182229 ## Release Note Remove Logs Explorer ## Summary - Removes the Discover/Logs Explorer tabs in the top nav - Changes all links to Logs Explorer in other applications to Discover - Removes all APIs exposed by Logs Explorer Note: This does not remove Logs Explorer from the codebase. There will be a follow up for that work. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .buildkite/ftr_oblt_stateful_configs.yml | 1 - .../deeplinks/observability/locators/index.ts | 1 - .../observability/locators/logs_explorer.ts | 84 -- .../locators/observability_logs_explorer.ts | 34 - .../deeplinks/observability/tsconfig.json | 1 - .../plugins/shared/data_views/common/types.ts | 6 +- .../top_nav/discover_topnav_inline.test.tsx | 33 - .../top_nav/discover_topnav_inline.tsx | 10 - .../components/logs_explorer_tabs/index.ts | 15 - .../logs_explorer_tabs.test.tsx | 111 --- .../logs_explorer_tabs/logs_explorer_tabs.tsx | 108 -- .../plugins/shared/discover/public/index.ts | 1 - .../collectors/application_usage/schema.ts | 1 - src/plugins/telemetry/schema/oss_plugins.json | 131 --- .../translations/translations/fr-FR.json | 10 - .../translations/translations/ja-JP.json | 10 - .../translations/translations/zh-CN.json | 10 - .../dataset_quality/common/translations.ts | 14 - .../degraded_field_flyout/index.tsx | 18 +- .../dataset_quality_details/header.tsx | 10 +- .../document_trends/degraded_docs/index.tsx | 22 +- .../public/hooks/use_redirect_link.ts | 119 +-- .../hooks/use_redirect_link_telemetry.ts | 23 +- .../shared/dataset_quality/tsconfig.json | 1 - .../components/agent_logs/agent_logs.test.tsx | 11 +- .../agent_logs/view_logs_button.tsx | 22 +- .../agent_activity_flyout/index.test.tsx | 8 +- .../components/view_errors.test.tsx | 8 +- .../public/custom_logs_assets_extension.tsx | 6 +- .../shared/logs_shared/common/index.ts | 12 +- .../common/locators/get_logs_locators.ts | 22 +- .../logs_shared/common/locators/helpers.ts | 37 +- .../logs_shared/common/locators/index.ts | 11 +- .../common/locators/logs_locator.ts | 50 +- .../common/locators/node_logs_locator.ts | 40 - .../common/locators/trace_logs_locator.ts | 38 - .../logs_shared/common/locators/types.ts | 26 +- .../open_in_logs_explorer_button.tsx | 4 +- .../shared/logs_shared/public/plugin.tsx | 25 +- .../shared/logs_shared/public/types.ts | 12 +- .../shared/logs_shared/server/types.ts | 3 + .../plugins/shared/logs_shared/tsconfig.json | 1 + .../helpers/app_context.mock.ts | 2 - .../plugins/upgrade_assistant/tsconfig.json | 3 +- .../instance_actions_menu/index.tsx | 11 +- .../instance_actions_menu/menu_sections.ts | 29 +- .../explore_logs_button.tsx | 10 +- .../logs_explorer_locator_config.ts | 6 +- .../transaction_action_menu/sections.test.ts | 23 +- .../transaction_action_menu/sections.ts | 47 +- .../transaction_action_menu.test.tsx | 31 +- .../transaction_action_menu.tsx | 6 +- .../apm_plugin/mock_apm_plugin_context.tsx | 12 +- .../log_threshold/log_threshold_rule_type.tsx | 10 +- .../plugins/infra/public/apps/logs_app.tsx | 5 +- .../asset_details/tabs/logs/logs.tsx | 36 +- .../public/pages/link_to/redirect_to_logs.tsx | 8 +- .../pages/link_to/redirect_to_node_logs.tsx | 28 +- .../infra/public/pages/logs/page_content.tsx | 9 +- .../tabs/logs/logs_link_to_stream.tsx | 14 +- .../components/waffle/node_context_menu.tsx | 18 +- .../plugins/infra/public/register_feature.ts | 15 - .../public/utils/logs_overview_fetchers.ts | 9 +- .../logs_explorer/common/locators/index.ts | 14 - .../common/locators/logs_explorer/index.ts | 8 - .../logs_explorer_locator.test.ts | 58 -- .../logs_explorer/logs_explorer_locator.ts | 43 - .../common/locators/logs_explorer/types.ts | 13 - .../plugins/logs_explorer/public/plugin.ts | 23 +- .../plugins/logs_explorer/public/types.ts | 7 +- .../plugins/logs_explorer/server/plugin.ts | 24 +- .../get_view_in_app_url.test.ts | 45 +- .../get_view_in_app_url.ts | 14 +- .../plugins/observability/kibana.jsonc | 3 +- .../alert_details_app_section.tsx | 4 +- .../observability/public/navigation_tree.ts | 16 +- .../public/pages/landing/landing.tsx | 11 +- .../observability_status/content.ts | 6 +- .../components/sections/logs/logs_section.tsx | 2 +- .../plugins/observability/public/plugin.ts | 8 +- .../register_observability_rule_types.ts | 6 +- .../custom_threshold_executor.ts | 10 +- .../plugins/observability/server/plugin.ts | 8 +- .../common/index.ts | 5 - .../common/locators/all_datasets_locator.ts | 33 - .../common/locators/data_view_locator.ts | 40 - .../common/locators/index.ts | 21 - .../common/locators/locators.test.ts | 426 -------- .../common/locators/single_dataset_locator.ts | 48 - .../common/locators/types.ts | 9 - .../locators/utils/construct_locator_path.ts | 105 -- .../common/locators/utils/index.ts | 8 - .../observability_logs_explorer/kibana.jsonc | 5 +- .../applications/redirect_to_discover.tsx | 33 + .../components/logs_explorer_top_nav_menu.tsx | 7 - .../public/plugin.ts | 106 +- .../public/types.ts | 10 +- .../custom_logs/install_elastic_agent.cy.ts | 5 +- .../observability_onboarding/kibana.jsonc | 5 +- .../auto_detect/auto_detect_panel.tsx | 25 +- .../custom_logs/install_elastic_agent.tsx | 17 +- .../use_aws_service_get_started_list.ts | 20 +- .../quickstart_flows/otel_logs/index.tsx | 17 +- .../shared/locator_button_empty.tsx | 78 -- .../observability_onboarding/tsconfig.json | 4 +- .../public/navigation_tree.ts | 15 +- .../custom_threshold/avg_pct_fired.ts | 5 +- .../custom_threshold/avg_pct_no_data.ts | 5 +- .../custom_threshold/avg_ticks_fired.ts | 5 +- .../custom_threshold/documents_count_fired.ts | 5 +- .../custom_threshold/p99_pct_fired.ts | 15 +- .../dataset_quality_details.ts | 12 +- .../dataset_quality/dataset_quality_table.ts | 8 +- .../functional/apps/infra/logs/link_to.ts | 22 +- .../apps/observability_logs_explorer/app.ts | 58 -- .../columns_selection.ts | 344 ------- .../common/package_registry_config.yml | 2 - .../observability_logs_explorer/config.ts | 90 -- .../custom_control_columns.ts | 186 ---- .../data_source_selection_state.ts | 106 -- .../data_source_selector.ts | 930 ------------------ .../observability_logs_explorer/field_list.ts | 105 -- .../filter_controls.ts | 28 - .../observability_logs_explorer/flyout.ts | 76 -- .../header_menu.ts | 105 -- .../apps/observability_logs_explorer/index.ts | 23 - .../observability_logs_explorer/navigation.ts | 126 --- x-pack/test/tsconfig.json | 1 - .../dataset_quality_details.ts | 12 +- .../dataset_quality/dataset_quality_table.ts | 8 +- .../test_suites/observability/index.ts | 1 - .../test_suites/observability/navigation.ts | 9 +- .../observability_logs_explorer/app.ts | 65 -- .../columns_selection.ts | 345 ------- .../custom_control_columns.ts | 187 ---- .../data_source_selection_state.ts | 123 --- .../data_source_selector.ts | 865 ---------------- .../observability_logs_explorer/field_list.ts | 110 --- .../filter_controls.ts | 29 - .../observability_logs_explorer/flyout.ts | 77 -- .../header_menu.ts | 196 ---- .../observability_logs_explorer/index.ts | 23 - .../observability_logs_explorer/navigation.ts | 127 --- .../observability/onboarding/auto_detect.ts | 8 +- .../role_management/custom_role_access.ts | 2 +- x-pack/test_serverless/tsconfig.json | 2 - 146 files changed, 535 insertions(+), 6718 deletions(-) delete mode 100644 src/platform/packages/shared/deeplinks/observability/locators/logs_explorer.ts delete mode 100644 src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/index.ts delete mode 100644 src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx delete mode 100644 src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx delete mode 100644 x-pack/platform/plugins/shared/logs_shared/common/locators/node_logs_locator.ts delete mode 100644 x-pack/platform/plugins/shared/logs_shared/common/locators/trace_logs_locator.ts delete mode 100644 x-pack/solutions/observability/plugins/logs_explorer/common/locators/index.ts delete mode 100644 x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/index.ts delete mode 100644 x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.test.ts delete mode 100644 x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.ts delete mode 100644 x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/types.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/all_datasets_locator.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/data_view_locator.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/index.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/locators.test.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/single_dataset_locator.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/types.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/construct_locator_path.ts delete mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/index.ts create mode 100644 x-pack/solutions/observability/plugins/observability_logs_explorer/public/applications/redirect_to_discover.tsx delete mode 100644 x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/shared/locator_button_empty.tsx delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/app.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/common/package_registry_config.yml delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/config.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/data_source_selection_state.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/field_list.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/filter_controls.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/flyout.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/header_menu.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/index.ts delete mode 100644 x-pack/test/functional/apps/observability_logs_explorer/navigation.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/app.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/index.ts delete mode 100644 x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts diff --git a/.buildkite/ftr_oblt_stateful_configs.yml b/.buildkite/ftr_oblt_stateful_configs.yml index d4c1df51176d2..c2dd4a025b2ff 100644 --- a/.buildkite/ftr_oblt_stateful_configs.yml +++ b/.buildkite/ftr_oblt_stateful_configs.yml @@ -38,7 +38,6 @@ enabled: - x-pack/test/apm_api_integration/rules/config.ts - x-pack/test/apm_api_integration/trial/config.ts - x-pack/test/dataset_quality_api_integration/basic/config.ts - - x-pack/test/functional/apps/observability_logs_explorer/config.ts - x-pack/test/functional/apps/dataset_quality/config.ts - x-pack/test/functional/apps/slo/embeddables/config.ts - x-pack/test/functional/apps/uptime/config.ts diff --git a/src/platform/packages/shared/deeplinks/observability/locators/index.ts b/src/platform/packages/shared/deeplinks/observability/locators/index.ts index 3317df2268df3..6d1c14dd473fd 100644 --- a/src/platform/packages/shared/deeplinks/observability/locators/index.ts +++ b/src/platform/packages/shared/deeplinks/observability/locators/index.ts @@ -10,7 +10,6 @@ export * from './apm'; export * from './dataset_quality'; export * from './dataset_quality_details'; -export * from './logs_explorer'; export * from './observability_logs_explorer'; export * from './observability_onboarding'; export * from './uptime'; diff --git a/src/platform/packages/shared/deeplinks/observability/locators/logs_explorer.ts b/src/platform/packages/shared/deeplinks/observability/locators/logs_explorer.ts deleted file mode 100644 index 61a2aa1cf92e7..0000000000000 --- a/src/platform/packages/shared/deeplinks/observability/locators/logs_explorer.ts +++ /dev/null @@ -1,84 +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", 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 type { SerializableRecord } from '@kbn/utility-types'; -import type { Filter, TimeRange, Query, AggregateQuery } from '@kbn/es-query'; - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type RefreshInterval = { - pause: boolean; - value: number; -}; - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type FilterControls = { - namespace?: ListFilterControl; -}; - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type ListFilterControl = { - mode: 'include'; - values: string[]; -}; - -export const LOGS_EXPLORER_LOCATOR_ID = 'LOGS_EXPLORER_LOCATOR'; - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type DocumentFieldGridColumnOptions = { - type: 'document-field'; - field: string; - width?: number; -}; - -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type SmartFieldGridColumnOptions = { - type: 'smart-field'; - smartField: 'content' | 'resource'; - width?: number; -}; - -export type GridColumnDisplayOptions = DocumentFieldGridColumnOptions | SmartFieldGridColumnOptions; - -export interface LogsExplorerNavigationParams extends SerializableRecord { - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - /** - * Optionally set the refresh interval. - */ - refreshInterval?: RefreshInterval; - /** - * Optionally set a query. - */ - query?: Query | AggregateQuery; - /** - * Columns displayed in the table - */ - columns?: GridColumnDisplayOptions[]; - /** - * Optionally apply free-form filters. - */ - filters?: Filter[]; - /** - * Optionally apply curated filter controls - */ - filterControls?: FilterControls; - /** - * Optionally set chart's breakdown field - */ - breakdownField?: string; -} - -export interface LogsExplorerLocatorParams extends LogsExplorerNavigationParams { - /** - * Dataset name to be selected. - */ - dataset?: string; -} diff --git a/src/platform/packages/shared/deeplinks/observability/locators/observability_logs_explorer.ts b/src/platform/packages/shared/deeplinks/observability/locators/observability_logs_explorer.ts index ae4cbc12cec6d..63ec2afa8f86e 100644 --- a/src/platform/packages/shared/deeplinks/observability/locators/observability_logs_explorer.ts +++ b/src/platform/packages/shared/deeplinks/observability/locators/observability_logs_explorer.ts @@ -8,7 +8,6 @@ */ import { SerializableRecord } from '@kbn/utility-types'; -import { LogsExplorerNavigationParams } from './logs_explorer'; // Will become a union once we have more origins export interface ObservabilityLogsExplorerLocationState extends SerializableRecord { @@ -17,38 +16,5 @@ export interface ObservabilityLogsExplorerLocationState extends SerializableReco }; } -export type DatasetLocatorParams = LogsExplorerNavigationParams & - ObservabilityLogsExplorerLocationState; - -// All datasets locator -export const ALL_DATASETS_LOCATOR_ID = 'ALL_DATASETS_LOCATOR'; - -export type AllDatasetsLocatorParams = DatasetLocatorParams; - -// Single dataset locator -export const SINGLE_DATASET_LOCATOR_ID = 'SINGLE_DATASET_LOCATOR'; - -export interface SingleDatasetLocatorParams extends DatasetLocatorParams { - /** - * Integration name to be selected. - */ - integration?: string; - /** - * Dataset name to be selected. - * ex: system.syslog - */ - dataset: string; -} - -// Data view locator -export const OBS_LOGS_EXPLORER_DATA_VIEW_LOCATOR_ID = 'OBS_LOGS_EXPLORER_DATA_VIEW_LOCATOR'; - -export interface ObsLogsExplorerDataViewLocatorParams extends DatasetLocatorParams { - /** - * Data view id to select - */ - id: string; -} - // To store the last used logs viewer (either of discover or observability-logs-explorer) export const OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY = 'obs-logs-explorer:lastUsedViewer'; diff --git a/src/platform/packages/shared/deeplinks/observability/tsconfig.json b/src/platform/packages/shared/deeplinks/observability/tsconfig.json index e1c207f1ba4d2..2433d3412c05b 100644 --- a/src/platform/packages/shared/deeplinks/observability/tsconfig.json +++ b/src/platform/packages/shared/deeplinks/observability/tsconfig.json @@ -17,6 +17,5 @@ ], "kbn_references": [ "@kbn/utility-types", - "@kbn/es-query", ] } diff --git a/src/platform/plugins/shared/data_views/common/types.ts b/src/platform/plugins/shared/data_views/common/types.ts index 8c229a01d0477..b038f5f9b0f7b 100644 --- a/src/platform/plugins/shared/data_views/common/types.ts +++ b/src/platform/plugins/shared/data_views/common/types.ts @@ -502,7 +502,9 @@ export type DataViewSpec = { */ version?: string; /** - * Data view title + * Contrary to its name, this property sets the index pattern of the data view. (e.g. `logs-*,metrics-*`) + * + * Use the `name` property instead to set a human readable name for the data view. */ title?: string; /** @@ -546,7 +548,7 @@ export type DataViewSpec = { */ namespaces?: string[]; /** - * Name of the data view. Human readable name used to differentiate data view. + * Human readable name used to differentiate the data view. */ name?: string; /** diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.test.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.test.tsx index 552ea0bb29547..7b2cfde82a9cb 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.test.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.test.tsx @@ -110,37 +110,4 @@ describe('DiscoverTopNavInline', () => { }); }); }); - - describe('LogsExplorerTabs', () => { - it('should render when showLogsExplorerTabs is true', async () => { - const props = getProps(); - props.stateContainer.customizationContext.inlineTopNav.showLogsExplorerTabs = true; - render( - - - - ); - const topNav = screen.queryByTestId('discoverTopNavInline'); - expect(topNav).not.toBeNull(); - await waitFor(() => { - const logsExplorerTabs = screen.queryByTestId('logsExplorerTabs'); - expect(logsExplorerTabs).not.toBeNull(); - }); - }); - - it('should not render when showLogsExplorerTabs is false', async () => { - const props = getProps(); - render( - - - - ); - const topNav = screen.queryByTestId('discoverTopNavInline'); - expect(topNav).not.toBeNull(); - await waitFor(() => { - const logsExplorerTabs = screen.queryByTestId('logsExplorerTabs'); - expect(logsExplorerTabs).toBeNull(); - }); - }); - }); }); diff --git a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.tsx b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.tsx index b2567e85cda32..399fa909d48ab 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/top_nav/discover_topnav_inline.tsx @@ -11,8 +11,6 @@ import React from 'react'; import { EuiHeader, EuiHeaderSection, EuiHeaderSectionItem } from '@elastic/eui'; import { TopNavMenuBadges, TopNavMenuItems } from '@kbn/navigation-plugin/public'; import { euiThemeVars } from '@kbn/ui-theme'; -import { LogsExplorerTabs } from '../../../../components/logs_explorer_tabs'; -import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { useDiscoverTopNav } from './use_discover_topnav'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; @@ -24,7 +22,6 @@ export const DiscoverTopNavInline = ({ hideNavMenuItems?: boolean; }) => { const { customizationContext } = stateContainer; - const services = useDiscoverServices(); const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer }); if ( @@ -39,13 +36,6 @@ export const DiscoverTopNavInline = ({ css={{ boxShadow: 'none', backgroundColor: euiThemeVars.euiPageBackgroundColor }} data-test-subj="discoverTopNavInline" > - {customizationContext.inlineTopNav.showLogsExplorerTabs && ( - - - - - - )} {!hideNavMenuItems && ( diff --git a/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/index.ts b/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/index.ts deleted file mode 100644 index af6bf54f910f1..0000000000000 --- a/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/index.ts +++ /dev/null @@ -1,15 +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", 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 { withSuspense } from '@kbn/shared-ux-utility'; -import { lazy } from 'react'; - -export type { LogsExplorerTabsProps } from './logs_explorer_tabs'; - -export const LogsExplorerTabs = withSuspense(lazy(() => import('./logs_explorer_tabs'))); diff --git a/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx b/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx deleted file mode 100644 index e353fe1971ec9..0000000000000 --- a/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.test.tsx +++ /dev/null @@ -1,111 +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", 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 userEvent from '@testing-library/user-event'; -import { render, screen } from '@testing-library/react'; -import React from 'react'; -import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; -import { - ALL_DATASETS_LOCATOR_ID, - OBSERVABILITY_LOGS_EXPLORER_APP_ID, -} from '@kbn/deeplinks-observability'; -import { discoverServiceMock } from '../../__mocks__/services'; -import { LogsExplorerTabs, LogsExplorerTabsProps } from './logs_explorer_tabs'; -import { DISCOVER_APP_LOCATOR } from '../../../common'; - -const mockSetLastUsedViewer = jest.fn(); -jest.mock('react-use/lib/useLocalStorage', () => { - return jest.fn((key: string, _initialValue: string) => { - return [undefined, mockSetLastUsedViewer]; // Always use undefined as the initial value - }); -}); - -const createMockLocator = (id: string) => ({ - navigate: jest.fn(), - getRedirectUrl: jest.fn().mockReturnValue(id), -}); - -describe('LogsExplorerTabs', () => { - const renderTabs = (selectedTab: LogsExplorerTabsProps['selectedTab'] = 'discover') => { - const mockDiscoverLocator = createMockLocator(DISCOVER_APP_LOCATOR); - const mockLogsExplorerLocator = createMockLocator(ALL_DATASETS_LOCATOR_ID); - const services = { - ...discoverServiceMock, - share: { - ...discoverServiceMock.share, - url: { - ...discoverServiceMock.share?.url, - locators: { - get: jest.fn((id) => { - switch (id) { - case DISCOVER_APP_LOCATOR: - return mockDiscoverLocator; - case ALL_DATASETS_LOCATOR_ID: - return mockLogsExplorerLocator; - default: - throw new Error(`Unknown locator id: ${id}`); - } - }), - }, - }, - }, - } as unknown as typeof discoverServiceMock; - - const { unmount } = render(); - - return { - mockDiscoverLocator, - mockLogsExplorerLocator, - unmount, - }; - }; - - const getDiscoverTab = () => screen.getByText('Discover').closest('a')!; - const getLogsExplorerTab = () => screen.getByText('Logs Explorer').closest('a')!; - - it('should render properly', () => { - const { mockDiscoverLocator, mockLogsExplorerLocator } = renderTabs(); - expect(getDiscoverTab()).toBeInTheDocument(); - expect(mockDiscoverLocator.getRedirectUrl).toHaveBeenCalledWith({}); - expect(getDiscoverTab()).toHaveAttribute('href', DISCOVER_APP_LOCATOR); - expect(getLogsExplorerTab()).toBeInTheDocument(); - expect(mockLogsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith({}); - expect(getLogsExplorerTab()).toHaveAttribute('href', ALL_DATASETS_LOCATOR_ID); - }); - - it('should render Discover as the selected tab', async () => { - const { mockDiscoverLocator, mockLogsExplorerLocator } = renderTabs(); - expect(getDiscoverTab()).toHaveAttribute('aria-selected', 'true'); - await userEvent.click(getDiscoverTab()); - expect(mockDiscoverLocator.navigate).not.toHaveBeenCalled(); - expect(getLogsExplorerTab()).toHaveAttribute('aria-selected', 'false'); - await userEvent.click(getLogsExplorerTab()); - expect(mockLogsExplorerLocator.navigate).toHaveBeenCalledWith({}); - }); - - it('should render Log Explorer as the selected tab', async () => { - const { mockDiscoverLocator, mockLogsExplorerLocator } = renderTabs('logs-explorer'); - expect(getLogsExplorerTab()).toHaveAttribute('aria-selected', 'true'); - await userEvent.click(getLogsExplorerTab()); - expect(mockLogsExplorerLocator.navigate).not.toHaveBeenCalled(); - expect(getDiscoverTab()).toHaveAttribute('aria-selected', 'false'); - await userEvent.click(getDiscoverTab()); - expect(mockDiscoverLocator.navigate).toHaveBeenCalledWith({}); - }); - - it('should update the last used viewer in local storage for selectedTab', async () => { - const { unmount } = renderTabs('discover'); - expect(mockSetLastUsedViewer).toHaveBeenCalledWith(DISCOVER_APP_ID); - - unmount(); - mockSetLastUsedViewer.mockClear(); - renderTabs('logs-explorer'); - expect(mockSetLastUsedViewer).toHaveBeenCalledWith(OBSERVABILITY_LOGS_EXPLORER_APP_ID); - }); -}); diff --git a/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx b/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx deleted file mode 100644 index c7082c21344ac..0000000000000 --- a/src/platform/plugins/shared/discover/public/components/logs_explorer_tabs/logs_explorer_tabs.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 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 { EuiTab, EuiTabs, useEuiTheme } from '@elastic/eui'; -import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; -import { - AllDatasetsLocatorParams, - ALL_DATASETS_LOCATOR_ID, - OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, - OBSERVABILITY_LOGS_EXPLORER_APP_ID, -} from '@kbn/deeplinks-observability'; -import { i18n } from '@kbn/i18n'; -import React, { MouseEvent, useEffect } from 'react'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { DiscoverAppLocatorParams, DISCOVER_APP_LOCATOR } from '../../../common'; -import type { DiscoverServices } from '../../build_services'; - -export interface LogsExplorerTabsProps { - services: Pick; - selectedTab: 'discover' | 'logs-explorer'; -} - -const emptyParams = {}; - -export const LogsExplorerTabs = ({ services, selectedTab }: LogsExplorerTabsProps) => { - const { euiTheme } = useEuiTheme(); - const locators = services.share?.url.locators; - const discoverLocator = locators?.get(DISCOVER_APP_LOCATOR); - const logsExplorerLocator = locators?.get(ALL_DATASETS_LOCATOR_ID); - const discoverUrl = discoverLocator?.getRedirectUrl(emptyParams); - const logsExplorerUrl = logsExplorerLocator?.getRedirectUrl(emptyParams); - - const [lastUsedViewer, setLastUsedViewer] = useLocalStorage< - typeof DISCOVER_APP_ID | typeof OBSERVABILITY_LOGS_EXPLORER_APP_ID - >(OBS_LOGS_EXPLORER_LOGS_VIEWER_KEY, OBSERVABILITY_LOGS_EXPLORER_APP_ID); - - const navigateToDiscover = createNavigateHandler(() => { - if (selectedTab !== 'discover') { - discoverLocator?.navigate(emptyParams); - } - }); - - const navigateToLogsExplorer = createNavigateHandler(() => { - if (selectedTab !== 'logs-explorer') { - logsExplorerLocator?.navigate(emptyParams); - } - }); - - useEffect(() => { - if (selectedTab === 'discover' && lastUsedViewer !== DISCOVER_APP_ID) { - setLastUsedViewer(DISCOVER_APP_ID); - } - - if (selectedTab === 'logs-explorer' && lastUsedViewer !== OBSERVABILITY_LOGS_EXPLORER_APP_ID) { - setLastUsedViewer(OBSERVABILITY_LOGS_EXPLORER_APP_ID); - } - }, [setLastUsedViewer, lastUsedViewer, selectedTab]); - - return ( - - - {i18n.translate('discover.logsExplorerTabs.discover', { - defaultMessage: 'Discover', - })} - - - {i18n.translate('discover.logsExplorerTabs.logsExplorer', { - defaultMessage: 'Logs Explorer', - })} - - - ); -}; - -// eslint-disable-next-line import/no-default-export -export default LogsExplorerTabs; - -const isModifiedEvent = (event: MouseEvent) => - event.metaKey || event.altKey || event.ctrlKey || event.shiftKey; - -const isLeftClickEvent = (event: MouseEvent) => event.button === 0; - -const createNavigateHandler = (onClick: () => void) => (e: MouseEvent) => { - if (isModifiedEvent(e) || !isLeftClickEvent(e)) { - return; - } - - e.preventDefault(); - onClick(); -}; diff --git a/src/platform/plugins/shared/discover/public/index.ts b/src/platform/plugins/shared/discover/public/index.ts index b54665bdc50a0..a359184f3b4a6 100644 --- a/src/platform/plugins/shared/discover/public/index.ts +++ b/src/platform/plugins/shared/discover/public/index.ts @@ -40,5 +40,4 @@ export { type NonPersistedDisplayOptions, } from './embeddable'; export { loadSharingDataHelpers } from './utils'; -export { LogsExplorerTabs, type LogsExplorerTabsProps } from './components/logs_explorer_tabs'; export type { DiscoverServices } from './build_services'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 81e564da36292..2542b4f04f15a 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -162,7 +162,6 @@ export const applicationUsageSchema = { monitoring: commonSchema, 'observability-log-explorer': commonSchema, 'observability-logs-explorer': commonSchema, - 'last-used-logs-viewer': commonSchema, 'observability-overview': commonSchema, observabilityOnboarding: commonSchema, observabilityAIAssistant: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 4508cdf1bbaa1..cc2e9f99ec9ea 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -5504,137 +5504,6 @@ } } }, - "last-used-logs-viewer": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, "observability-overview": { "properties": { "appId": { diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index e8b9a9637f474..57b3b22ddfa7c 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -2640,8 +2640,6 @@ "discover.logs.popoverAction.filterFor": "Filtrer sur", "discover.logs.popoverAction.filterOut": "Exclure", "discover.logs.popoverAction.openPopover": "Ouvrir la fenêtre contextuelle", - "discover.logsExplorerTabs.discover": "Discover", - "discover.logsExplorerTabs.logsExplorer": "Explorateur de logs", "discover.noMatchRoute.bannerText": "L'application Discover ne reconnaît pas cet itinéraire : {route}", "discover.noMatchRoute.bannerTitleText": "Page introuvable", "discover.noResults.kqlExamples.combineMultipleText": "Combinaison de plusieurs requêtes avec AND/OR", @@ -15056,9 +15054,7 @@ "xpack.datasetQuality.details.integrationActionsText": "Actions d'intégration", "xpack.datasetQuality.details.integrationnameText": "Intégration", "xpack.datasetQuality.details.integrationVersionText": "Version", - "xpack.datasetQuality.details.logsExplorerAriaText": "Explorateur de logs", "xpack.datasetQuality.details.openInDiscoverText": "Ouvrir dans Discover", - "xpack.datasetQuality.details.openInLogsExplorerText": "Ouvrir dans l'explorateur de logs", "xpack.datasetQuality.details.overviewPanel.datasetQuality.degradedDocs": "Documents dégradés", "xpack.datasetQuality.details.overviewPanel.datasetQuality.title": "Qualité de l'ensemble de données", "xpack.datasetQuality.details.overviewPanel.documents.size": "Taille", @@ -20047,7 +20043,6 @@ "xpack.fleet.agentLogs.logDisabledCallOutTitle": "La collecte de logs est désactivée", "xpack.fleet.agentLogs.logLevelSelectText": "Niveau du log", "xpack.fleet.agentLogs.oldAgentWarningTitle": "La vue Logs requiert Elastic Agent 7.11 ou une version ultérieure. Pour mettre à niveau un agent, accédez au menu Actions ou {downloadLink} une version plus récente.", - "xpack.fleet.agentLogs.openInLogsUiLinkText": "Ouvrir dans l'explorateur de logs", "xpack.fleet.agentLogs.resetLogLevel.errorTitleText": "Erreur lors de la réinitialisation du niveau de logging de l'agent", "xpack.fleet.agentLogs.resetLogLevel.successText": "Réinitialiser le niveau de logging de l'agent pour la politique", "xpack.fleet.agentLogs.resetLogLevelLabelText": "Réinitialiser pour rétablir la politique", @@ -20279,7 +20274,6 @@ "xpack.fleet.appNavigation.settingsLinkText": "Paramètres", "xpack.fleet.appNavigation.uninstallTokensText": "Désinstaller les jetons", "xpack.fleet.appTitle": "Fleet", - "xpack.fleet.assets.customLogs.description": "Afficher les données des logs personnalisés dans Logs Explorer", "xpack.fleet.assets.customLogs.name": "Logs", "xpack.fleet.azureArmTemplate.guide.description": "Un modèle Azure Resource Manager (ARM) crée toutes les ressources nécessaires pour évaluer le niveau de sécurité de votre organisation Azure. Suivez les étapes ci-dessous pour lancer le modèle ARM. En savoir plus sur {learnMore}.", "xpack.fleet.azureArmTemplate.guide.learnMoreLinkText": "Azure Resource Manager", @@ -24696,8 +24690,6 @@ "xpack.infra.profiling.threadsInfoPopoverBody": "Visualisez les traces d'appel de profilage regroupées par noms de threads de processus. Cette vue vous permet d'identifier les threads qui consomment le plus de ressources de processeur et d'examiner la pile d'appels de chaque thread. Vous pouvez ainsi identifier rapidement les lignes de codes consommant beaucoup de ressources dans le thread.", "xpack.infra.registerFeatures.infraOpsDescription": "Explorez les indicateurs et logs d'infrastructure pour les serveurs, conteneurs et services courants.", "xpack.infra.registerFeatures.infraOpsTitle": "Indicateurs", - "xpack.infra.registerFeatures.logsDescription": "Diffusez les logs en temps réel ou faites défiler les vues d'historique comme sur une console.", - "xpack.infra.registerFeatures.logsTitle": "Logs", "xpack.infra.routes.customDashboards": "Les tableaux de bord personnalisés ne sont pas activés", "xpack.infra.sampleDataLinkLabel": "Logs", "xpack.infra.saveDashboardModal.euiIcon.iconWithTooltipLabel": "Icône avec infobulle", @@ -33849,7 +33841,6 @@ "xpack.observability.overview.guidedSetupTourDismissButton": "Rejeter", "xpack.observability.overview.guidedSetupTourTitle": "L'assistant de données est toujours disponible", "xpack.observability.overview.loadingObservability": "Chargement d'Observability", - "xpack.observability.overview.logs.appLink": "Essayer Logs Explorer", "xpack.observability.overview.logs.subtitle": "Taux de logs par minute", "xpack.observability.overview.logs.title": "Événements de log", "xpack.observability.overview.logsLabel": "Logs", @@ -33975,7 +33966,6 @@ "xpack.observability.statusVisualization.apm.link": "Ajouter des données", "xpack.observability.statusVisualization.apm.title": "APM", "xpack.observability.statusVisualization.logs.description": "Un monitoring rapide, simple, évolutif et centralisé des logs avec un support technique dédié pour les sources de données les plus communes.", - "xpack.observability.statusVisualization.logs.goToAppTitle": "Essayer Logs Explorer", "xpack.observability.statusVisualization.logs.link": "Ajouter des intégrations", "xpack.observability.statusVisualization.logs.title": "Logs", "xpack.observability.statusVisualization.metrics.description": "Diffusez, visualisez et analysez vos indicateurs d'infrastructure.", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 3dbfd5adf0bfe..4bebdb1cd1366 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -2639,8 +2639,6 @@ "discover.logs.popoverAction.filterFor": "フィルター", "discover.logs.popoverAction.filterOut": "除外", "discover.logs.popoverAction.openPopover": "ポップオーバーを開く", - "discover.logsExplorerTabs.discover": "Discover", - "discover.logsExplorerTabs.logsExplorer": "ログエクスプローラー", "discover.noMatchRoute.bannerText": "Discoverアプリケーションはこのルート{route}を認識できません", "discover.noMatchRoute.bannerTitleText": "ページが見つかりません", "discover.noResults.kqlExamples.combineMultipleText": "AND/ORを使用して複数のクエリーを結合", @@ -14921,9 +14919,7 @@ "xpack.datasetQuality.details.integrationActionsText": "統合アクション", "xpack.datasetQuality.details.integrationnameText": "統合", "xpack.datasetQuality.details.integrationVersionText": "Version", - "xpack.datasetQuality.details.logsExplorerAriaText": "ログエクスプローラー", "xpack.datasetQuality.details.openInDiscoverText": "Discoverで開く", - "xpack.datasetQuality.details.openInLogsExplorerText": "ログエクスプローラーで開く", "xpack.datasetQuality.details.overviewPanel.datasetQuality.degradedDocs": "劣化したドキュメント", "xpack.datasetQuality.details.overviewPanel.datasetQuality.title": "データセット品質", "xpack.datasetQuality.details.overviewPanel.documents.size": "サイズ", @@ -19904,7 +19900,6 @@ "xpack.fleet.agentLogs.logDisabledCallOutTitle": "ログ収集は無効です", "xpack.fleet.agentLogs.logLevelSelectText": "ログレベル", "xpack.fleet.agentLogs.oldAgentWarningTitle": "ログの表示には、Elastic Agent 7.11以降が必要です。エージェントをアップグレードするには、[アクション]メニューに移動するか、新しいバージョンを{downloadLink}。", - "xpack.fleet.agentLogs.openInLogsUiLinkText": "ログエクスプローラーで開く", "xpack.fleet.agentLogs.resetLogLevel.errorTitleText": "エージェントログレベルのリセットエラー", "xpack.fleet.agentLogs.resetLogLevel.successText": "エージェントログレベルをポリシーにリセット", "xpack.fleet.agentLogs.resetLogLevelLabelText": "ポリシーにリセット", @@ -20136,7 +20131,6 @@ "xpack.fleet.appNavigation.settingsLinkText": "設定", "xpack.fleet.appNavigation.uninstallTokensText": "トークンをアンインストール", "xpack.fleet.appTitle": "Fleet", - "xpack.fleet.assets.customLogs.description": "ログエクスプローラーでカスタムログデータを表示", "xpack.fleet.assets.customLogs.name": "ログ", "xpack.fleet.azureArmTemplate.guide.description": "Azure Resource Manager(ARM)テンプレートは、Azure組織のセキュリティ態勢を評価するために必要なすべてのリソースを作成します。以下の手順に従って、ARMテンプレートを起動します。{learnMore}の詳細をご覧ください。", "xpack.fleet.azureArmTemplate.guide.learnMoreLinkText": "Azure Resource Manager", @@ -24555,8 +24549,6 @@ "xpack.infra.profiling.threadsInfoPopoverBody": "プロセススレッド名別にグループ化されたプロファイリングスタックトレースを可視化します。このビューでは、CPUリソースを消費している上位のスレッドを特定し、各スレッドのコールスタックにドリルダウンすることで、スレッド内でリソース消費が多いコードの行をすばやく特定できます。", "xpack.infra.registerFeatures.infraOpsDescription": "共通のサーバー、コンテナー、サービスのインフラストラクチャーメトリックとログを閲覧します。", "xpack.infra.registerFeatures.infraOpsTitle": "メトリック", - "xpack.infra.registerFeatures.logsDescription": "ログをリアルタイムでストリームするか、コンソール式の UI で履歴ビューをスクロールします。", - "xpack.infra.registerFeatures.logsTitle": "ログ", "xpack.infra.routes.customDashboards": "カスタムダッシュボードは有効ではありません", "xpack.infra.sampleDataLinkLabel": "ログ", "xpack.infra.saveDashboardModal.euiIcon.iconWithTooltipLabel": "ツールチップが表示されるアイコン", @@ -33710,7 +33702,6 @@ "xpack.observability.overview.guidedSetupTourDismissButton": "閉じる", "xpack.observability.overview.guidedSetupTourTitle": "データアシスタントは常に使用できます", "xpack.observability.overview.loadingObservability": "オブザーバビリティを読み込んでいます", - "xpack.observability.overview.logs.appLink": "ログエクスプローラーを開く", "xpack.observability.overview.logs.subtitle": "毎分のログレート", "xpack.observability.overview.logs.title": "ログイベント", "xpack.observability.overview.logsLabel": "ログ", @@ -33837,7 +33828,6 @@ "xpack.observability.statusVisualization.apm.link": "データの追加", "xpack.observability.statusVisualization.apm.title": "APM", "xpack.observability.statusVisualization.logs.description": "共通データソースのアウトオブボックスサポートで、一元化されたログ監視の速度、容易性、拡張性を高めます。", - "xpack.observability.statusVisualization.logs.goToAppTitle": "ログエクスプローラーを開く", "xpack.observability.statusVisualization.logs.link": "統合の追加", "xpack.observability.statusVisualization.logs.title": "ログ", "xpack.observability.statusVisualization.metrics.description": "インフラストラクチャーメトリックをストリーム、可視化、分析します。", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index aeff58a5582c4..23655e9886d48 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -2630,8 +2630,6 @@ "discover.logs.popoverAction.filterFor": "筛留", "discover.logs.popoverAction.filterOut": "筛除", "discover.logs.popoverAction.openPopover": "打开弹出框", - "discover.logsExplorerTabs.discover": "Discover", - "discover.logsExplorerTabs.logsExplorer": "日志浏览器", "discover.noMatchRoute.bannerText": "Discover 应用程序无法识别此路由:{route}", "discover.noMatchRoute.bannerTitleText": "未找到页面", "discover.noResults.kqlExamples.combineMultipleText": "正在用 AND/OR 组合多个请求", @@ -14652,9 +14650,7 @@ "xpack.datasetQuality.details.integrationActionsText": "集成操作", "xpack.datasetQuality.details.integrationnameText": "集成", "xpack.datasetQuality.details.integrationVersionText": "版本", - "xpack.datasetQuality.details.logsExplorerAriaText": "日志浏览器", "xpack.datasetQuality.details.openInDiscoverText": "在 Discover 中打开", - "xpack.datasetQuality.details.openInLogsExplorerText": "在日志浏览器中打开", "xpack.datasetQuality.details.overviewPanel.datasetQuality.degradedDocs": "已降级文档", "xpack.datasetQuality.details.overviewPanel.datasetQuality.title": "数据集质量", "xpack.datasetQuality.details.overviewPanel.documents.size": "大小", @@ -19596,7 +19592,6 @@ "xpack.fleet.agentLogs.logDisabledCallOutTitle": "日志收集已禁用", "xpack.fleet.agentLogs.logLevelSelectText": "日志级别", "xpack.fleet.agentLogs.oldAgentWarningTitle": "'日志'视图需要 Elastic Agent 7.11 或更高版本。要升级代理,请前往'操作'菜单或{downloadLink}更新的版本。", - "xpack.fleet.agentLogs.openInLogsUiLinkText": "在日志浏览器中打开", "xpack.fleet.agentLogs.resetLogLevel.errorTitleText": "重置代理日志记录级别时出错", "xpack.fleet.agentLogs.resetLogLevel.successText": "将代理日志记录级别重置为策略", "xpack.fleet.agentLogs.resetLogLevelLabelText": "重置为策略", @@ -19827,7 +19822,6 @@ "xpack.fleet.appNavigation.settingsLinkText": "设置", "xpack.fleet.appNavigation.uninstallTokensText": "卸载令牌", "xpack.fleet.appTitle": "Fleet", - "xpack.fleet.assets.customLogs.description": "在日志浏览器中查看定制日志数据", "xpack.fleet.assets.customLogs.name": "日志", "xpack.fleet.azureArmTemplate.guide.description": "Azure Resource Manager (ARM) 模板将创建所有必要资源来评估您的 Azure 组织的安全态势。请按照以下步骤启动 ARM 模板。详细了解 {learnMore}。", "xpack.fleet.azureArmTemplate.guide.learnMoreLinkText": "Azure 资源管理器", @@ -24172,8 +24166,6 @@ "xpack.infra.profiling.threadsInfoPopoverBody": "可视化按进程线程名称分组的分析堆栈跟踪。此视图有助于您识别消耗 CPU 资源的排名靠前线程,并允许您向下钻取到每个线程的调用堆栈,以便您快速确定线程中的资源密集型代码行。", "xpack.infra.registerFeatures.infraOpsDescription": "浏览常用服务器、容器和服务的基础设施指标和日志。", "xpack.infra.registerFeatures.infraOpsTitle": "指标", - "xpack.infra.registerFeatures.logsDescription": "实时流式传输日志或在类似控制台的工具中滚动浏览历史视图。", - "xpack.infra.registerFeatures.logsTitle": "日志", "xpack.infra.routes.customDashboards": "未启用定制仪表板", "xpack.infra.sampleDataLinkLabel": "日志", "xpack.infra.saveDashboardModal.euiIcon.iconWithTooltipLabel": "带工具提示的图标", @@ -33226,7 +33218,6 @@ "xpack.observability.overview.guidedSetupTourDismissButton": "关闭", "xpack.observability.overview.guidedSetupTourTitle": "数据助手始终可用", "xpack.observability.overview.loadingObservability": "正在加载可观测性", - "xpack.observability.overview.logs.appLink": "显示日志浏览器", "xpack.observability.overview.logs.subtitle": "每分钟日志速率", "xpack.observability.overview.logs.title": "日志事件", "xpack.observability.overview.logsLabel": "日志", @@ -33353,7 +33344,6 @@ "xpack.observability.statusVisualization.apm.link": "添加数据", "xpack.observability.statusVisualization.apm.title": "APM", "xpack.observability.statusVisualization.logs.description": "通过开箱支持常用数据源,实现快速、轻松而可扩展的集中式日志监测。", - "xpack.observability.statusVisualization.logs.goToAppTitle": "显示日志浏览器", "xpack.observability.statusVisualization.logs.link": "添加集成", "xpack.observability.statusVisualization.logs.title": "日志", "xpack.observability.statusVisualization.metrics.description": "流式传输、可视化并分析您的基础架构指标。", diff --git a/x-pack/platform/plugins/shared/dataset_quality/common/translations.ts b/x-pack/platform/plugins/shared/dataset_quality/common/translations.ts index 1026dd8ea58d3..8cb5e82ba514d 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/common/translations.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/common/translations.ts @@ -43,20 +43,6 @@ export const flyoutCancelText = i18n.translate('xpack.datasetQuality.flyoutCance defaultMessage: 'Cancel', }); -export const openInLogsExplorerText = i18n.translate( - 'xpack.datasetQuality.details.openInLogsExplorerText', - { - defaultMessage: 'Open in Logs Explorer', - } -); - -export const logsExplorerAriaText = i18n.translate( - 'xpack.datasetQuality.details.logsExplorerAriaText', - { - defaultMessage: 'Logs Explorer', - } -); - export const openInDiscoverText = i18n.translate( 'xpack.datasetQuality.details.openInDiscoverText', { diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/degraded_field_flyout/index.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/degraded_field_flyout/index.tsx index bb72b4f6de20f..9c32f0a5605c2 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/degraded_field_flyout/index.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/degraded_field_flyout/index.tsx @@ -33,9 +33,7 @@ import { degradedFieldMessageIssueDoesNotExistInLatestIndex, discoverAriaText, fieldIgnoredText, - logsExplorerAriaText, openInDiscoverText, - openInLogsExplorerText, overviewDegradedFieldsSectionTitle, } from '../../../../common/translations'; import { DegradedFieldInfo } from './field_info'; @@ -95,21 +93,11 @@ export default function DegradedFieldFlyout() { {expandedDegradedField} {fieldIgnoredText} - + - {redirectLinkProps.isLogsExplorerAvailable - ? openInLogsExplorerText - : openInDiscoverText} + {openInDiscoverText} diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/degraded_docs/index.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/degraded_docs/index.tsx index e25b1da9540f8..76490d848422c 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/degraded_docs/index.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/degraded_docs/index.tsx @@ -27,9 +27,7 @@ import { css } from '@emotion/react'; import { UnifiedBreakdownFieldSelector } from '@kbn/unified-histogram-plugin/public'; import { discoverAriaText, - logsExplorerAriaText, openInDiscoverText, - openInLogsExplorerText, overviewDegradedDocsText, } from '../../../../../../common/translations'; import { DegradedDocsChart } from './degraded_docs_chart'; @@ -133,25 +131,11 @@ export default function DegradedDocs({ lastReloadTime }: { lastReloadTime: numbe onBreakdownFieldChange={breakdown.onChange} /> - + ({ sendTelemetry: SendTelemetryFn; }) => { const { - services: { share, application }, + services: { share }, } = useKibanaContextForPlugin(); const { from, to } = timeRangeConfig; - const logsExplorerLocator = - share.url.locators.get(SINGLE_DATASET_LOCATOR_ID); - - const isLogsExplorerAppAccessible = useObservable( - useMemo( - () => - application.applications$.pipe( - map( - (apps) => - (apps.get(OBSERVABILITY_LOGS_EXPLORER_APP_ID)?.status ?? AppStatus.inaccessible) === - AppStatus.accessible - ) - ), - [application.applications$] - ), - false - ); - return useMemo<{ linkProps: RouterLinkProps; navigate: () => void; isLogsExplorerAvailable: boolean; }>(() => { - const isLogsExplorerAvailable = - isLogsExplorerAppAccessible && !!logsExplorerLocator && dataStreamStat.type === 'logs'; - const config = isLogsExplorerAvailable - ? buildLogsExplorerConfig({ - locator: logsExplorerLocator, - dataStreamStat, - query, - from, - to, - breakdownField, - }) - : buildDiscoverConfig({ - locatorClient: share.url.locators, - dataStreamStat, - query, - from, - to, - breakdownField, - }); + const config = buildDiscoverConfig({ + locatorClient: share.url.locators, + dataStreamStat, + query, + from, + to, + breakdownField, + }); const onClickWithTelemetry = (event: Parameters[0]) => { sendTelemetry(); @@ -104,68 +66,9 @@ export const useRedirectLink = ({ onClick: onClickWithTelemetry, }, navigate: navigateWithTelemetry, - isLogsExplorerAvailable, + isLogsExplorerAvailable: false, }; - }, [ - breakdownField, - dataStreamStat, - from, - to, - logsExplorerLocator, - query, - sendTelemetry, - share.url.locators, - isLogsExplorerAppAccessible, - ]); -}; - -const buildLogsExplorerConfig = ({ - locator, - dataStreamStat, - query, - from, - to, - breakdownField, -}: { - locator: LocatorPublic; - dataStreamStat: T; - query?: Query | AggregateQuery; - from: string; - to: string; - breakdownField?: string; -}): { - navigate: () => void; - routerLinkProps: RouterLinkProps; -} => { - const params: SingleDatasetLocatorParams = { - dataset: dataStreamStat.name, - timeRange: { - from, - to, - }, - integration: dataStreamStat.integration?.name, - query, - filterControls: { - namespace: { - mode: 'include', - values: [dataStreamStat.namespace], - }, - }, - breakdownField, - }; - - const urlToLogsExplorer = locator.getRedirectUrl(params); - - const navigateToLogsExplorer = () => { - locator.navigate(params) as Promise; - }; - - const logsExplorerLinkProps = getRouterLinkProps({ - href: urlToLogsExplorer, - onClick: navigateToLogsExplorer, - }); - - return { routerLinkProps: logsExplorerLinkProps, navigate: navigateToLogsExplorer }; + }, [breakdownField, dataStreamStat, from, to, query, sendTelemetry, share.url.locators]); }; const buildDiscoverConfig = ({ diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_redirect_link_telemetry.ts b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_redirect_link_telemetry.ts index a70de0bd20295..49fed94ffcf0b 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_redirect_link_telemetry.ts +++ b/x-pack/platform/plugins/shared/dataset_quality/public/hooks/use_redirect_link_telemetry.ts @@ -7,14 +7,9 @@ import { useCallback } from 'react'; import { AggregateQuery, Query } from '@kbn/es-query'; -import { - SINGLE_DATASET_LOCATOR_ID, - SingleDatasetLocatorParams, -} from '@kbn/deeplinks-observability'; import { NavigationSource } from '../services/telemetry'; import { useDatasetTelemetry } from './use_dataset_telemetry'; import { useDatasetDetailsTelemetry } from './use_dataset_details_telemetry'; -import { useKibanaContextForPlugin } from '../utils'; export type SendTelemetryFn = | ReturnType['sendTelemetry'] @@ -47,27 +42,13 @@ export const useDatasetDetailsRedirectLinkTelemetry = ({ navigationSource: NavigationSource; query?: Query | AggregateQuery; }) => { - const { - services: { share }, - } = useKibanaContextForPlugin(); - const logsExplorerLocator = - share.url.locators.get(SINGLE_DATASET_LOCATOR_ID); - const isLogsExplorer = !!logsExplorerLocator; const { trackDetailsNavigated, navigationTargets } = useDatasetDetailsTelemetry(); const sendTelemetry = useCallback(() => { const isIgnoredFilter = query ? JSON.stringify(query).includes('_ignored') : false; - const target = isLogsExplorer ? navigationTargets.LogsExplorer : navigationTargets.Discover; - trackDetailsNavigated(target, navigationSource, isIgnoredFilter); - }, [ - query, - isLogsExplorer, - navigationTargets.LogsExplorer, - navigationTargets.Discover, - trackDetailsNavigated, - navigationSource, - ]); + trackDetailsNavigated(navigationTargets.Discover, navigationSource, isIgnoredFilter); + }, [query, navigationTargets.Discover, trackDetailsNavigated, navigationSource]); return { sendTelemetry, diff --git a/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json b/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json index 1db6f18d1a9c9..0c126e97f22d1 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json +++ b/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json @@ -55,7 +55,6 @@ "@kbn/usage-collection-plugin", "@kbn/rison", "@kbn/task-manager-plugin", - "@kbn/core-application-browser", "@kbn/field-utils", "@kbn/logging" ], diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.test.tsx index dc3e56f176216..c306837affaac 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/agent_logs.test.tsx @@ -26,11 +26,14 @@ jest.mock('@kbn/logs-shared-plugin/public', () => { LogStream: () =>
, }; }); + jest.mock('@kbn/logs-shared-plugin/common', () => { + const originalModule = jest.requireActual('@kbn/logs-shared-plugin/common'); return { - getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({ - logsLocator: { getRedirectUrl: jest.fn(() => 'https://logs-explorer-redirect-url') }, - }), + ...originalModule, + getLogsLocatorFromUrlService: jest + .fn() + .mockReturnValue({ getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') }), }; }); @@ -130,7 +133,7 @@ describe('AgentLogsUI', () => { const result = renderComponent(); expect(result.getByTestId('viewInLogsBtn')).toHaveAttribute( 'href', - `https://logs-explorer-redirect-url` + `https://discover-redirect-url` ); }); diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/view_logs_button.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/view_logs_button.tsx index bc0b1b7c38a1b..2632baf7865eb 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/view_logs_button.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_logs/view_logs_button.tsx @@ -7,7 +7,11 @@ import React, { useMemo } from 'react'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { + getLogsLocatorFromUrlService, + getTimeRangeStartFromTime, + getTimeRangeEndFromTime, +} from '@kbn/logs-shared-plugin/common'; import moment from 'moment'; @@ -35,7 +39,7 @@ export const ViewLogsButton: React.FunctionComponent = ({ endTime, }) => { const { share } = useStartServices(); - const { logsLocator } = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url); const authz = useAuthz(); const logsUrl = useMemo(() => { @@ -44,21 +48,21 @@ export const ViewLogsButton: React.FunctionComponent = ({ const defaultStartTime = getFormattedRange(oneDayAgo); const defaultEndTime = getFormattedRange(now); - return logsLocator.getRedirectUrl({ + return logsLocator?.getRedirectUrl({ time: endTime ? endTime : defaultEndTime, timeRange: { - startTime: startTime ? startTime : defaultStartTime, - endTime: endTime ? endTime : defaultEndTime, + from: getTimeRangeStartFromTime(startTime ? startTime : defaultStartTime), + to: getTimeRangeEndFromTime(endTime ? endTime : defaultEndTime), }, filter: logStreamQuery, }); }, [endTime, logStreamQuery, logsLocator, startTime]); - return authz.fleet.readAgents && logsLocator ? ( - + return authz.fleet.readAgents && logsUrl ? ( + ) : null; diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.test.tsx index 10c653336dc57..dc1a915b91263 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_activity_flyout/index.test.tsx @@ -28,10 +28,12 @@ const mockUseStartServices = useStartServices as jest.Mock; const mockedUseAuthz = useAuthz as jest.Mock; jest.mock('@kbn/logs-shared-plugin/common', () => { + const originalModule = jest.requireActual('@kbn/logs-shared-plugin/common'); return { - getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({ - logsLocator: { getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') }, - }), + ...originalModule, + getLogsLocatorFromUrlService: jest + .fn() + .mockReturnValue({ getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') }), }; }); diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/view_errors.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/view_errors.test.tsx index e8f73eae3a2b9..39ff3d3890d0f 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/view_errors.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/view_errors.test.tsx @@ -40,10 +40,12 @@ jest.mock('@kbn/shared-ux-link-redirect-app', () => ({ })); jest.mock('@kbn/logs-shared-plugin/common', () => { + const originalModule = jest.requireActual('@kbn/logs-shared-plugin/common'); return { - getLogsLocatorsFromUrlService: jest.fn().mockReturnValue({ - logsLocator: { getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') }, - }), + ...originalModule, + getLogsLocatorFromUrlService: jest + .fn() + .mockReturnValue({ getRedirectUrl: jest.fn(() => 'https://discover-redirect-url') }), }; }); diff --git a/x-pack/platform/plugins/shared/fleet/public/custom_logs_assets_extension.tsx b/x-pack/platform/plugins/shared/fleet/public/custom_logs_assets_extension.tsx index 64c33a9e9298a..8f4d0b1951553 100644 --- a/x-pack/platform/plugins/shared/fleet/public/custom_logs_assets_extension.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/custom_logs_assets_extension.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorFromUrlService } from '@kbn/logs-shared-plugin/common'; import { CustomAssetsAccordion } from './components/custom_assets_accordion'; import type { CustomAssetsAccordionProps } from './components/custom_assets_accordion'; @@ -19,14 +19,14 @@ export const CustomLogsAssetsExtension: PackageAssetsComponent = () => { const { share: { url }, } = useStartServices(); - const { logsLocator } = getLogsLocatorsFromUrlService(url); + const logsLocator = getLogsLocatorFromUrlService(url)!; const views: CustomAssetsAccordionProps['views'] = [ { name: i18n.translate('xpack.fleet.assets.customLogs.name', { defaultMessage: 'Logs' }), url: logsLocator.getRedirectUrl({}), description: i18n.translate('xpack.fleet.assets.customLogs.description', { - defaultMessage: 'View Custom logs data in Logs Explorer', + defaultMessage: 'View custom logs data in Discover', }), }, ]; diff --git a/x-pack/platform/plugins/shared/logs_shared/common/index.ts b/x-pack/platform/plugins/shared/logs_shared/common/index.ts index d05a52e90f419..d5287460b5de8 100644 --- a/x-pack/platform/plugins/shared/logs_shared/common/index.ts +++ b/x-pack/platform/plugins/shared/logs_shared/common/index.ts @@ -62,10 +62,12 @@ export { // Locators export { + type LogsLocatorParams, LOGS_LOCATOR_ID, - TRACE_LOGS_LOCATOR_ID, - NODE_LOGS_LOCATOR_ID, - getLogsLocatorsFromUrlService, + getLogsLocatorFromUrlService, + getTimeRange, + getTimeRangeEndFromTime, + getTimeRangeStartFromTime, + getNodeQuery, + getTraceQuery, } from './locators'; -export type { LogsLocatorParams, NodeLogsLocatorParams, TraceLogsLocatorParams } from './locators'; -export { createNodeLogsQuery } from './locators/helpers'; diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/get_logs_locators.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/get_logs_locators.ts index 5c403c2bcb5b0..4cc588b045d6c 100644 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/get_logs_locators.ts +++ b/x-pack/platform/plugins/shared/logs_shared/common/locators/get_logs_locators.ts @@ -5,21 +5,9 @@ * 2.0. */ -import { UrlService } from '@kbn/share-plugin/common/url_service'; +import { type UrlService } from '@kbn/share-plugin/common/url_service'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from './logs_locator'; -import { LogsLocatorParams, NodeLogsLocatorParams, TraceLogsLocatorParams } from './types'; -import { LOGS_LOCATOR_ID } from './logs_locator'; -import { NODE_LOGS_LOCATOR_ID } from './node_logs_locator'; -import { TRACE_LOGS_LOCATOR_ID } from './trace_logs_locator'; - -export const getLogsLocatorsFromUrlService = (urlService: UrlService) => { - const logsLocator = urlService.locators.get(LOGS_LOCATOR_ID)!; - const nodeLogsLocator = urlService.locators.get(NODE_LOGS_LOCATOR_ID)!; - const traceLogsLocator = urlService.locators.get(TRACE_LOGS_LOCATOR_ID)!; - - return { - logsLocator, - traceLogsLocator, - nodeLogsLocator, - }; -}; +export function getLogsLocatorFromUrlService(urlService: UrlService) { + return urlService.locators.get(LOGS_LOCATOR_ID); +} diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/helpers.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/helpers.ts index ae25c8abb3c18..057ff1a11ac21 100644 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/helpers.ts +++ b/x-pack/platform/plugins/shared/logs_shared/common/locators/helpers.ts @@ -6,26 +6,29 @@ */ import moment, { DurationInputObject } from 'moment'; -import { LogsLocatorParams, NodeLogsLocatorParams, TraceLogsLocatorParams } from './types'; +import type { LogsLocatorParams } from './logs_locator'; -export const getLogsQuery = (params: LogsLocatorParams) => { - const { filter } = params; +export interface NodeLogsParams { + nodeField: string; + nodeId: string; + filter?: string; +} - return filter ? { language: 'kuery', query: filter } : undefined; -}; - -export const createNodeLogsQuery = (params: NodeLogsLocatorParams) => { +export const getNodeQuery = (params: NodeLogsParams): LogsLocatorParams['query'] => { const { nodeField, nodeId, filter } = params; const nodeFilter = `${nodeField}: ${nodeId}`; - return filter ? `(${nodeFilter}) and (${filter})` : nodeFilter; -}; + const query = filter ? `(${nodeFilter}) and (${filter})` : nodeFilter; -export const getNodeQuery = (params: NodeLogsLocatorParams) => { - return { language: 'kuery', query: createNodeLogsQuery(params) }; + return { language: 'kuery', query }; }; -export const getTraceQuery = (params: TraceLogsLocatorParams) => { +export interface TraceLogsParams { + traceId: string; + filter?: string; +} + +export const getTraceQuery = (params: TraceLogsParams): LogsLocatorParams['query'] => { const { traceId, filter } = params; const traceFilter = `trace.id:"${traceId}" OR (not trace.id:* AND "${traceId}")`; @@ -36,6 +39,16 @@ export const getTraceQuery = (params: TraceLogsLocatorParams) => { const defaultTimeRangeFromPositionOffset: DurationInputObject = { hours: 1 }; +export function getTimeRange(time: number | undefined): LogsLocatorParams['timeRange'] { + if (time === undefined) { + return undefined; + } + return { + from: getTimeRangeStartFromTime(time), + to: getTimeRangeEndFromTime(time), + }; +} + export const getTimeRangeStartFromTime = (time: number): string => moment(time).subtract(defaultTimeRangeFromPositionOffset).toISOString(); diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/index.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/index.ts index edf8b6a433cbb..ad0bb5af615b2 100644 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/index.ts +++ b/x-pack/platform/plugins/shared/logs_shared/common/locators/index.ts @@ -6,13 +6,6 @@ */ export * from './logs_locator'; -export * from './trace_logs_locator'; -export * from './node_logs_locator'; export * from './get_logs_locators'; - -export type { - LogsSharedLocators, - LogsLocatorParams, - NodeLogsLocatorParams, - TraceLogsLocatorParams, -} from './types'; +export * from './helpers'; +export * from './types'; diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/logs_locator.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/logs_locator.ts index c46c2ddd08406..b0637e5da6874 100644 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/logs_locator.ts +++ b/x-pack/platform/plugins/shared/logs_shared/common/locators/logs_locator.ts @@ -5,34 +5,48 @@ * 2.0. */ -import { ALL_DATASETS_LOCATOR_ID, AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { LocatorDefinition } from '@kbn/share-plugin/common'; import { LocatorClient } from '@kbn/share-plugin/common/url_service'; +import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import type { DataViewSpec } from '@kbn/data-views-plugin/common'; -import { LogsLocatorParams } from './types'; -import { getLogsQuery, getTimeRangeEndFromTime, getTimeRangeStartFromTime } from './helpers'; - +/** + * Locator used to link to all log sources in Discover. + */ export const LOGS_LOCATOR_ID = 'LOGS_LOCATOR'; +/** + * Accepts the same parameters as `DiscoverAppLocatorParams`, but automatically sets the `dataViewSpec` param to all log sources. + */ +export type LogsLocatorParams = DiscoverAppLocatorParams; + export class LogsLocatorDefinition implements LocatorDefinition { public readonly id = LOGS_LOCATOR_ID; - constructor(private readonly locators: LocatorClient) {} + constructor( + private readonly deps: { + locators: LocatorClient; + getLogSourcesService(): Promise; + } + ) {} public readonly getLocation = async (params: LogsLocatorParams) => { - const allDatasetsLocator = - this.locators.get(ALL_DATASETS_LOCATOR_ID)!; - const { time } = params; - return allDatasetsLocator.getLocation({ - query: getLogsQuery(params), - ...(time - ? { - timeRange: { - from: getTimeRangeStartFromTime(time), - to: getTimeRangeEndFromTime(time), - }, - } - : {}), + const discoverAppLocator = + this.deps.locators.get('DISCOVER_APP_LOCATOR')!; + + return discoverAppLocator.getLocation({ + dataViewSpec: params.dataViewSpec ?? (await this.getLogSourcesDataViewSpec()), + ...params, }); }; + + private async getLogSourcesDataViewSpec(): Promise { + const logSourcesService = await this.deps.getLogSourcesService(); + const logSources = await logSourcesService.getLogSources(); + return { + title: logSources.map((logSource) => logSource.indexPattern).join(','), + timeFieldName: '@timestamp', + }; + } } diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/node_logs_locator.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/node_logs_locator.ts deleted file mode 100644 index 4e7ddf55e8f9e..0000000000000 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/node_logs_locator.ts +++ /dev/null @@ -1,40 +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 { - AllDatasetsLocatorParams, - ALL_DATASETS_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; -import { LocatorClient, LocatorDefinition } from '@kbn/share-plugin/common/url_service'; - -import { NodeLogsLocatorParams } from './types'; -import { getNodeQuery, getTimeRangeStartFromTime, getTimeRangeEndFromTime } from './helpers'; - -export const NODE_LOGS_LOCATOR_ID = 'NODE_LOGS_LOCATOR'; - -export class NodeLogsLocatorDefinition implements LocatorDefinition { - public readonly id = NODE_LOGS_LOCATOR_ID; - - constructor(private readonly locators: LocatorClient) {} - - public readonly getLocation = async (params: NodeLogsLocatorParams) => { - const allDatasetsLocator = - this.locators.get(ALL_DATASETS_LOCATOR_ID)!; - const { time } = params; - return allDatasetsLocator.getLocation({ - query: getNodeQuery(params), - ...(time - ? { - timeRange: { - from: getTimeRangeStartFromTime(time), - to: getTimeRangeEndFromTime(time), - }, - } - : {}), - }); - }; -} diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/trace_logs_locator.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/trace_logs_locator.ts deleted file mode 100644 index 52be4bcd40b1c..0000000000000 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/trace_logs_locator.ts +++ /dev/null @@ -1,38 +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 { ALL_DATASETS_LOCATOR_ID, AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; -import { LocatorDefinition } from '@kbn/share-plugin/common'; -import { LocatorClient } from '@kbn/share-plugin/common/url_service'; -import { TraceLogsLocatorParams } from './types'; - -import { getTraceQuery, getTimeRangeEndFromTime, getTimeRangeStartFromTime } from './helpers'; - -export const TRACE_LOGS_LOCATOR_ID = 'TRACE_LOGS_LOCATOR'; - -export class TraceLogsLocatorDefinition implements LocatorDefinition { - public readonly id = TRACE_LOGS_LOCATOR_ID; - - constructor(private readonly locators: LocatorClient) {} - - public readonly getLocation = async (params: TraceLogsLocatorParams) => { - const { time } = params; - const allDatasetsLocator = - this.locators.get(ALL_DATASETS_LOCATOR_ID)!; - return allDatasetsLocator.getLocation({ - query: getTraceQuery(params), - ...(time - ? { - timeRange: { - from: getTimeRangeStartFromTime(time), - to: getTimeRangeEndFromTime(time), - }, - } - : {}), - }); - }; -} diff --git a/x-pack/platform/plugins/shared/logs_shared/common/locators/types.ts b/x-pack/platform/plugins/shared/logs_shared/common/locators/types.ts index 07c50590b3efb..4f27f45689ece 100644 --- a/x-pack/platform/plugins/shared/logs_shared/common/locators/types.ts +++ b/x-pack/platform/plugins/shared/logs_shared/common/locators/types.ts @@ -5,33 +5,9 @@ * 2.0. */ -import { SerializableRecord } from '@kbn/utility-types'; import { LocatorPublic } from '@kbn/share-plugin/common'; -import { LogViewReference } from '../log_views/types'; -import { TimeRange } from './time_range'; - -export interface LogsLocatorParams extends SerializableRecord { - /** Defines log position */ - time?: number; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - filter?: string; - logView?: LogViewReference; -} - -export interface TraceLogsLocatorParams extends LogsLocatorParams { - traceId: string; -} - -export interface NodeLogsLocatorParams extends LogsLocatorParams { - nodeField: string; - nodeId: string; -} +import type { LogsLocatorParams } from './logs_locator'; export interface LogsSharedLocators { logsLocator: LocatorPublic; - nodeLogsLocator: LocatorPublic; - traceLogsLocator: LocatorPublic; } diff --git a/x-pack/platform/plugins/shared/logs_shared/public/components/open_in_logs_explorer_button.tsx b/x-pack/platform/plugins/shared/logs_shared/public/components/open_in_logs_explorer_button.tsx index 4e19d3dc48d1e..174bc41f4788e 100644 --- a/x-pack/platform/plugins/shared/logs_shared/public/components/open_in_logs_explorer_button.tsx +++ b/x-pack/platform/plugins/shared/logs_shared/public/components/open_in_logs_explorer_button.tsx @@ -15,13 +15,13 @@ type OpenInLogsExplorerButtonProps = Pick { return ( ); diff --git a/x-pack/platform/plugins/shared/logs_shared/public/plugin.tsx b/x-pack/platform/plugins/shared/logs_shared/public/plugin.tsx index e6ec419ae8b0f..da05546c3aea0 100644 --- a/x-pack/platform/plugins/shared/logs_shared/public/plugin.tsx +++ b/x-pack/platform/plugins/shared/logs_shared/public/plugin.tsx @@ -6,11 +6,7 @@ */ import { CoreStart } from '@kbn/core/public'; -import { - LogsLocatorDefinition, - NodeLogsLocatorDefinition, - TraceLogsLocatorDefinition, -} from '../common/locators'; +import { LogsLocatorDefinition } from '../common/locators'; import { createLogAIAssistant, createLogsAIAssistantRenderer } from './components/log_ai_assistant'; import { createLogsOverview } from './components/logs_overview'; import { LogViewsService } from './services/log_views'; @@ -28,24 +24,21 @@ export class LogsSharedPlugin implements LogsSharedClientPluginClass { this.logViews = new LogViewsService(); } - public setup(_: LogsSharedClientCoreSetup, pluginsSetup: LogsSharedClientSetupDeps) { + public setup(coreSetup: LogsSharedClientCoreSetup, pluginsSetup: LogsSharedClientSetupDeps) { const logViews = this.logViews.setup(); const logsLocator = pluginsSetup.share.url.locators.create( - new LogsLocatorDefinition(pluginsSetup.share.url.locators) - ); - const nodeLogsLocator = pluginsSetup.share.url.locators.create( - new NodeLogsLocatorDefinition(pluginsSetup.share.url.locators) - ); - - const traceLogsLocator = pluginsSetup.share.url.locators.create( - new TraceLogsLocatorDefinition(pluginsSetup.share.url.locators) + new LogsLocatorDefinition({ + locators: pluginsSetup.share.url.locators, + getLogSourcesService: async () => { + const [_, pluginsStart] = await coreSetup.getStartServices(); + return pluginsStart.logsDataAccess.services.logSourcesService; + }, + }) ); const locators = { logsLocator, - nodeLogsLocator, - traceLogsLocator, }; return { logViews, locators }; diff --git a/x-pack/platform/plugins/shared/logs_shared/public/types.ts b/x-pack/platform/plugins/shared/logs_shared/public/types.ts index 90bbd89f2481d..bed32ff4de3e0 100644 --- a/x-pack/platform/plugins/shared/logs_shared/public/types.ts +++ b/x-pack/platform/plugins/shared/logs_shared/public/types.ts @@ -8,9 +8,15 @@ import type { ChartsPluginStart } from '@kbn/charts-plugin/public'; import type { CoreSetup, CoreStart, Plugin as PluginClass } from '@kbn/core/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import type { + DataViewsPublicPluginSetup, + DataViewsPublicPluginStart, +} from '@kbn/data-views-plugin/public'; import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; -import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import type { + LogsDataAccessPluginSetup, + LogsDataAccessPluginStart, +} from '@kbn/logs-data-access-plugin/public'; import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public'; import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; @@ -36,6 +42,8 @@ export interface LogsSharedClientStartExports { export interface LogsSharedClientSetupDeps { share: SharePluginSetup; + dataViews: DataViewsPublicPluginSetup; + logsDataAccess: LogsDataAccessPluginSetup; } export interface LogsSharedClientStartDeps { diff --git a/x-pack/platform/plugins/shared/logs_shared/server/types.ts b/x-pack/platform/plugins/shared/logs_shared/server/types.ts index 770229ec035d8..8c9a4fc476048 100644 --- a/x-pack/platform/plugins/shared/logs_shared/server/types.ts +++ b/x-pack/platform/plugins/shared/logs_shared/server/types.ts @@ -13,6 +13,7 @@ import { import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/server'; +import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/server'; import { LogsSharedDomainLibs } from './lib/logs_shared_types'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; @@ -32,10 +33,12 @@ export interface LogsSharedPluginStart { } export interface LogsSharedServerPluginSetupDeps { + share: SharePluginSetup; data: DataPluginSetup; } export interface LogsSharedServerPluginStartDeps { + share: SharePluginStart; data: DataPluginStart; dataViews: DataViewsPluginStart; logsDataAccess: LogsDataAccessPluginStart; diff --git a/x-pack/platform/plugins/shared/logs_shared/tsconfig.json b/x-pack/platform/plugins/shared/logs_shared/tsconfig.json index 63a176154b4ea..3ac23f16c6024 100644 --- a/x-pack/platform/plugins/shared/logs_shared/tsconfig.json +++ b/x-pack/platform/plugins/shared/logs_shared/tsconfig.json @@ -53,6 +53,7 @@ "@kbn/field-formats-plugin", "@kbn/embeddable-plugin", "@kbn/saved-search-plugin", + "@kbn/discover-plugin", "@kbn/spaces-plugin", "@kbn/analytics", "@kbn/usage-collection-plugin", diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts index 010790e3c5e15..ada2a110541f3 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts @@ -21,7 +21,6 @@ import { apiService } from '../../../public/application/lib/api'; import { breadcrumbService } from '../../../public/application/lib/breadcrumbs'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; -import { OBS_LOGS_EXPLORER_DATA_VIEW_LOCATOR_ID } from '@kbn/deeplinks-observability'; const data = dataPluginMock.createStartContract(); const dataViews = { ...data.dataViews }; @@ -52,7 +51,6 @@ const servicesMock = { const idToUrlMap = { SNAPSHOT_RESTORE_LOCATOR: 'snapshotAndRestoreUrl', DISCOVER_APP_LOCATOR: 'discoverUrl', - [OBS_LOGS_EXPLORER_DATA_VIEW_LOCATOR_ID]: 'logsExplorerUrl', }; type IdKey = keyof typeof idToUrlMap; diff --git a/x-pack/plugins/upgrade_assistant/tsconfig.json b/x-pack/plugins/upgrade_assistant/tsconfig.json index d78cafa21551b..78f8e447b0b3d 100644 --- a/x-pack/plugins/upgrade_assistant/tsconfig.json +++ b/x-pack/plugins/upgrade_assistant/tsconfig.json @@ -39,11 +39,10 @@ "@kbn/shared-ux-link-redirect-app", "@kbn/react-kibana-context-render", "@kbn/data-views-plugin", - "@kbn/deeplinks-observability", "@kbn/core-logging-server-mocks", "@kbn/core-http-server-mocks", "@kbn/core-http-server-utils", - "@kbn/core-elasticsearch-server" + "@kbn/core-elasticsearch-server", ], "exclude": [ "target/**/*", diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx index ea3c24281d20b..b50e712920433 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/index.tsx @@ -15,9 +15,7 @@ import { SectionSubtitle, SectionTitle, } from '@kbn/observability-shared-plugin/public'; -import type { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators'; -import { ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability/locators'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorFromUrlService } from '@kbn/logs-shared-plugin/common'; import { ASSET_DETAILS_LOCATOR_ID, type AssetDetailsLocatorParams, @@ -54,9 +52,7 @@ export function InstanceActionsMenu({ serviceName, serviceNodeName, kuery, onClo const metricOverviewHref = useMetricOverviewHref(serviceName); const history = useHistory(); - const allDatasetsLocator = - share.url.locators.get(ALL_DATASETS_LOCATOR_ID)!; - const { nodeLogsLocator } = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url)!; const assetDetailsLocator = share.url.locators.get(ASSET_DETAILS_LOCATOR_ID); @@ -97,8 +93,7 @@ export function InstanceActionsMenu({ serviceName, serviceNodeName, kuery, onClo basePath: core.http.basePath, onFilterByInstanceClick: handleFilterByInstanceClick, metricsHref, - allDatasetsLocator, - nodeLogsLocator, + logsLocator, assetDetailsLocator, }); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts index 4e08572c9cb4e..d333210212e69 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/app/service_overview/service_overview_instances_table/instance_actions_menu/menu_sections.ts @@ -8,9 +8,8 @@ import { i18n } from '@kbn/i18n'; import type { IBasePath } from '@kbn/core/public'; import moment from 'moment'; -import type { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability/locators'; import type { LocatorPublic } from '@kbn/share-plugin/public'; -import type { NodeLogsLocatorParams } from '@kbn/logs-shared-plugin/common'; +import { type LogsLocatorParams, getNodeQuery, getTimeRange } from '@kbn/logs-shared-plugin/common'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { type AssetDetailsLocator } from '@kbn/observability-shared-plugin/common'; import type { APIReturnType } from '../../../../../services/rest/create_call_apm_api'; @@ -41,16 +40,14 @@ export function getMenuSections({ basePath, onFilterByInstanceClick, metricsHref, - allDatasetsLocator, - nodeLogsLocator, + logsLocator, assetDetailsLocator, }: { instanceDetails: InstaceDetails; basePath: IBasePath; onFilterByInstanceClick: () => void; metricsHref: string; - allDatasetsLocator: LocatorPublic; - nodeLogsLocator: LocatorPublic; + logsLocator: LocatorPublic; assetDetailsLocator?: AssetDetailsLocator; }) { const podId = instanceDetails.kubernetes?.pod?.uid; @@ -60,16 +57,20 @@ export function getMenuSections({ : undefined; const infraMetricsQuery = getInfraMetricsQuery(instanceDetails['@timestamp']); - const podLogsHref = nodeLogsLocator.getRedirectUrl({ - nodeField: findInventoryFields('pod').id, - nodeId: podId!, - time, + const podLogsHref = logsLocator.getRedirectUrl({ + query: getNodeQuery({ + nodeField: findInventoryFields('pod').id, + nodeId: podId!, + }), + timeRange: getTimeRange(time), }); - const containerLogsHref = nodeLogsLocator.getRedirectUrl({ - nodeField: findInventoryFields('container').id, - nodeId: containerId!, - time, + const containerLogsHref = logsLocator.getRedirectUrl({ + query: getNodeQuery({ + nodeField: findInventoryFields('container').id, + nodeId: containerId!, + }), + timeRange: getTimeRange(time), }); const hasPodLink = !!podId && !!assetDetailsLocator; diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/explore_logs_button.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/explore_logs_button.tsx index ece78351deed6..6491b586f7759 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/explore_logs_button.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/explore_logs_button.tsx @@ -13,10 +13,9 @@ */ import React from 'react'; -import type { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; -import { ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability'; import { EuiButtonEmpty } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { buildLogsExplorerLocatorConfig } from './logs_explorer_locator_config'; @@ -31,15 +30,14 @@ export function ExploreLogsButton({ }) { const { share } = useApmPluginContext(); - const logsExplorerLocator = - share.url.locators.get(ALL_DATASETS_LOCATOR_ID)!; + const logsLocator = share.url.locators.get(LOGS_LOCATOR_ID); - if (!logsExplorerLocator) { + if (!logsLocator) { return null; } const { logsExplorerLinkProps } = buildLogsExplorerLocatorConfig({ - locator: logsExplorerLocator, + locator: logsLocator, from: start, to: end, kuery, diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/logs_explorer_locator_config.ts b/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/logs_explorer_locator_config.ts index 39279f46e6587..c28b1bef7c375 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/logs_explorer_locator_config.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/explore_logs_button/logs_explorer_locator_config.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; import type { LocatorPublic } from '@kbn/share-plugin/common'; import { getRouterLinkProps } from '@kbn/router-utils'; import type { RouterLinkProps } from '@kbn/router-utils/src/get_router_link_props'; +import type { LogsLocatorParams } from '@kbn/logs-shared-plugin/common'; export const buildLogsExplorerLocatorConfig = ({ locator, @@ -16,14 +16,14 @@ export const buildLogsExplorerLocatorConfig = ({ from, to, }: { - locator: LocatorPublic; + locator: LocatorPublic; kuery?: string; from: string; to: string; }): { logsExplorerLinkProps: RouterLinkProps; } => { - const params: AllDatasetsLocatorParams = { + const params: LogsLocatorParams = { timeRange: { from, to, diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts index 3f93870bc696d..0496c373bfef4 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.test.ts @@ -10,9 +10,8 @@ import rison from '@kbn/rison'; import type { IBasePath } from '@kbn/core/public'; import type { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import { getSections } from './sections'; -import type { ApmRouter } from '../../routing/apm_route_config'; -import { apmRouter as apmRouterBase } from '../../routing/apm_route_config'; -import { logsLocatorsMock } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { apmRouter as apmRouterBase, type ApmRouter } from '../../routing/apm_route_config'; +import { logsLocatorMock } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; import type { AssetDetailsLocatorParams, @@ -24,7 +23,6 @@ const apmRouter = { link: (...args: [any]) => `some-basepath/app/apm${apmRouterBase.link(...args)}`, } as ApmRouter; -const { nodeLogsLocator, traceLogsLocator } = logsLocatorsMock; const uptimeLocator = sharePluginMock.createLocator(); const mockAssetDetailsLocator = { @@ -36,9 +34,8 @@ const mockAssetDetailsLocator = { ), } as unknown as jest.Mocked; -const expectLogsLocatorsToBeCalled = () => { - expect(nodeLogsLocator.getRedirectUrl).toBeCalledTimes(3); - expect(traceLogsLocator.getRedirectUrl).toBeCalledTimes(1); +const expectLogsLocatorToBeCalled = () => { + expect(logsLocatorMock.getRedirectUrl).toBeCalledTimes(4); }; const expectUptimeLocatorToBeCalled = () => { @@ -77,7 +74,7 @@ describe('Transaction action menu', () => { basePath, location, apmRouter, - logsLocators: logsLocatorsMock, + logsLocator: logsLocatorMock, uptimeLocator, infraLinksAvailable: false, rangeFrom: 'now-24h', @@ -129,7 +126,7 @@ describe('Transaction action menu', () => { ], ]); expectUptimeLocatorToBeCalled(); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); it('shows pod and required sections only', () => { @@ -147,7 +144,7 @@ describe('Transaction action menu', () => { location, apmRouter, uptimeLocator, - logsLocators: logsLocatorsMock, + logsLocator: logsLocatorMock, infraLinksAvailable: true, rangeFrom: 'now-24h', rangeTo: 'now', @@ -216,7 +213,7 @@ describe('Transaction action menu', () => { ], ]); expectUptimeLocatorToBeCalled(); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); it('shows host and required sections only', () => { @@ -234,7 +231,7 @@ describe('Transaction action menu', () => { location, apmRouter, uptimeLocator, - logsLocators: logsLocatorsMock, + logsLocator: logsLocatorMock, infraLinksAvailable: true, rangeFrom: 'now-24h', rangeTo: 'now', @@ -303,6 +300,6 @@ describe('Transaction action menu', () => { ], ]); expectUptimeLocatorToBeCalled(); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); }); diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.ts b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.ts index 8721c1293ac2a..7cadfbc990191 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.ts +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/sections.ts @@ -10,7 +10,12 @@ import type { Location } from 'history'; import type { IBasePath } from '@kbn/core/public'; import { isEmpty, pickBy } from 'lodash'; import moment from 'moment'; -import type { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { + type LogsLocatorParams, + getNodeQuery, + getTraceQuery, + getTimeRange, +} from '@kbn/logs-shared-plugin/common'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import type { ProfilingLocators } from '@kbn/observability-shared-plugin/public'; import type { AssetDetailsLocator } from '@kbn/observability-shared-plugin/common'; @@ -46,7 +51,7 @@ export const getSections = ({ rangeFrom, rangeTo, environment, - logsLocators, + logsLocator, dataViewId, assetDetailsLocator, }: { @@ -60,7 +65,7 @@ export const getSections = ({ rangeFrom: string; rangeTo: string; environment: Environment; - logsLocators: ReturnType; + logsLocator: LocatorPublic; dataViewId?: string; assetDetailsLocator?: AssetDetailsLocator; }) => { @@ -85,25 +90,31 @@ export const getSections = ({ ); // Logs hrefs - const podLogsHref = logsLocators.nodeLogsLocator.getRedirectUrl({ - nodeField: findInventoryFields('pod').id, - nodeId: podId!, - time, + const podLogsHref = logsLocator.getRedirectUrl({ + query: getNodeQuery({ + nodeField: findInventoryFields('pod').id, + nodeId: podId!, + }), + timeRange: getTimeRange(time), }); - const containerLogsHref = logsLocators.nodeLogsLocator.getRedirectUrl({ - nodeField: findInventoryFields('container').id, - nodeId: containerId!, - time, + const containerLogsHref = logsLocator.getRedirectUrl({ + query: getNodeQuery({ + nodeField: findInventoryFields('container').id, + nodeId: containerId!, + }), + timeRange: getTimeRange(time), }); - const hostLogsHref = logsLocators.nodeLogsLocator.getRedirectUrl({ - nodeField: findInventoryFields('host').id, - nodeId: hostName!, - time, + const hostLogsHref = logsLocator.getRedirectUrl({ + query: getNodeQuery({ + nodeField: findInventoryFields('host').id, + nodeId: hostName!, + }), + timeRange: getTimeRange(time), }); - const traceLogsHref = logsLocators.traceLogsLocator.getRedirectUrl({ - traceId: transaction.trace.id!, - time, + const traceLogsHref = logsLocator.getRedirectUrl({ + query: getTraceQuery({ traceId: transaction.trace.id! }), + timeRange: getTimeRange(time), }); const hasPodLink = !!podId && infraLinksAvailable && !!assetDetailsLocator; diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx index 2c9722e89f91f..62e6e7492576e 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.test.tsx @@ -11,17 +11,13 @@ import { MemoryRouter } from 'react-router-dom'; import { createMemoryHistory } from 'history'; import { License } from '@kbn/licensing-plugin/common/license'; import rison from '@kbn/rison'; -import { - LOGS_LOCATOR_ID, - NODE_LOGS_LOCATOR_ID, - TRACE_LOGS_LOCATOR_ID, -} from '@kbn/logs-shared-plugin/common'; +import { LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import type { Transaction } from '../../../../typings/es_schemas/ui/transaction'; import type { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, - logsLocatorsMock, + logsLocatorMock, } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { LicenseContext } from '../../../context/license/license_context'; import * as hooks from '../../../hooks/use_fetcher'; @@ -59,15 +55,7 @@ const apmContextMock = { locators: { get: (id: string) => { if (id === LOGS_LOCATOR_ID) { - return logsLocatorsMock.logsLocator; - } - - if (id === NODE_LOGS_LOCATOR_ID) { - return logsLocatorsMock.nodeLogsLocator; - } - - if (id === TRACE_LOGS_LOCATOR_ID) { - return logsLocatorsMock.traceLogsLocator; + return logsLocatorMock; } if (id === uptimeOverviewLocatorID) { return { @@ -131,9 +119,8 @@ const renderTransaction = async (transaction: Record) => { return rendered; }; -const expectLogsLocatorsToBeCalled = () => { - expect(logsLocatorsMock.nodeLogsLocator.getRedirectUrl).toBeCalled(); - expect(logsLocatorsMock.traceLogsLocator.getRedirectUrl).toBeCalled(); +const expectLogsLocatorToBeCalled = () => { + expect(logsLocatorMock.getRedirectUrl).toBeCalled(); }; let useAdHocApmDataViewSpy: jest.SpyInstance; @@ -175,7 +162,7 @@ describe('TransactionActionMenu ', () => { it('should call logs locators getRedirectUrl function', async () => { await renderTransaction(Transactions.transactionWithMinimalData); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); describe('when there is no pod id', () => { @@ -196,7 +183,7 @@ describe('TransactionActionMenu ', () => { it('should call logs locators getRedirectUrl function', async () => { await renderTransaction(Transactions.transactionWithKubernetesData); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); it('renders the pod metrics link', async () => { @@ -226,7 +213,7 @@ describe('TransactionActionMenu ', () => { it('renders the Container logs link', async () => { await renderTransaction(Transactions.transactionWithContainerData); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); it('renders the Container metrics link', async () => { @@ -256,7 +243,7 @@ describe('TransactionActionMenu ', () => { it('should call logs locators getRedirectUrl function', async () => { await renderTransaction(Transactions.transactionWithHostData); - expectLogsLocatorsToBeCalled(); + expectLogsLocatorToBeCalled(); }); it('renders the Host metrics link', async () => { diff --git a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx index 60532c4ed2310..2ce3bfe67cfeb 100644 --- a/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/components/shared/transaction_action_menu/transaction_action_menu.tsx @@ -26,7 +26,7 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import type { ProfilingLocators } from '@kbn/observability-shared-plugin/public'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorFromUrlService } from '@kbn/logs-shared-plugin/common'; import { uptimeOverviewLocatorID } from '@kbn/observability-plugin/common'; import { useAnyOfApmParams } from '../../../hooks/use_apm_params'; import { ApmFeatureFlagName } from '../../../../common/apm_feature_flags'; @@ -125,7 +125,7 @@ function ActionMenuSections({ const apmRouter = useApmRouter(); const { dataView } = useAdHocApmDataView(); - const logsLocators = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url)!; const uptimeLocator = share.url.locators.get(uptimeOverviewLocatorID); @@ -154,7 +154,7 @@ function ActionMenuSections({ rangeFrom, rangeTo, environment, - logsLocators, + logsLocator, dataViewId: dataView?.id, assetDetailsLocator, }); diff --git a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 759cfa8a8c819..10c5643c7ac1e 100644 --- a/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx +++ b/x-pack/solutions/observability/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -8,11 +8,7 @@ import { coreMock } from '@kbn/core/public/mocks'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; -import type { - LogsLocatorParams, - NodeLogsLocatorParams, - TraceLogsLocatorParams, -} from '@kbn/logs-shared-plugin/common'; +import type { LogsLocatorParams } from '@kbn/logs-shared-plugin/common'; import { MlLocatorDefinition } from '@kbn/ml-plugin/public'; import { apmEnableProfilingIntegration } from '@kbn/observability-plugin/common'; import { @@ -162,11 +158,7 @@ export const observabilityLogsExplorerLocatorsMock = { singleDatasetLocator: sharePluginMock.createLocator(), }; -export const logsLocatorsMock = { - logsLocator: sharePluginMock.createLocator(), - nodeLogsLocator: sharePluginMock.createLocator(), - traceLogsLocator: sharePluginMock.createLocator(), -}; +export const logsLocatorMock = sharePluginMock.createLocator(); const mockCorePlugins = { embeddable: {}, diff --git a/x-pack/solutions/observability/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx b/x-pack/solutions/observability/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx index 8c9e209b7f716..897eee17fa34a 100644 --- a/x-pack/solutions/observability/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.tsx @@ -7,10 +7,12 @@ import { i18n } from '@kbn/i18n'; import type { UrlService } from '@kbn/share-plugin/common/url_service'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorFromUrlService } from '@kbn/logs-shared-plugin/common'; import type { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; -import type { PartialRuleParams } from '../../../common/alerting/logs/log_threshold'; -import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../../../common/alerting/logs/log_threshold'; +import { + LOG_DOCUMENT_COUNT_RULE_TYPE_ID, + type PartialRuleParams, +} from '../../../common/alerting/logs/log_threshold'; import { createLazyComponentWithKibanaContext } from '../../hooks/use_kibana'; import type { InfraClientCoreSetup } from '../../types'; import { createRuleFormatter } from './rule_data_formatters'; @@ -54,7 +56,7 @@ export function createLogThresholdRuleType( () => import('./components/alert_details_app_section') ); - const { logsLocator } = getLogsLocatorsFromUrlService(urlService); + const logsLocator = getLogsLocatorFromUrlService(urlService)!; return { id: LOG_DOCUMENT_COUNT_RULE_TYPE_ID, diff --git a/x-pack/solutions/observability/plugins/infra/public/apps/logs_app.tsx b/x-pack/solutions/observability/plugins/infra/public/apps/logs_app.tsx index 0147e8ebca600..3873ef919b038 100644 --- a/x-pack/solutions/observability/plugins/infra/public/apps/logs_app.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/apps/logs_app.tsx @@ -12,8 +12,7 @@ import ReactDOM from 'react-dom'; import { Router, Routes, Route } from '@kbn/shared-ux-router'; import type { AppMountParameters } from '@kbn/core/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; -import type { AllDatasetsLocatorParams } from '@kbn/deeplinks-observability'; -import { ALL_DATASETS_LOCATOR_ID } from '@kbn/deeplinks-observability'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { LinkToLogsPage } from '../pages/link_to/link_to_logs'; import { LogsPage } from '../pages/logs'; import type { InfraClientStartDeps, InfraClientStartExports } from '../types'; @@ -93,7 +92,7 @@ const LogsApp: React.FC<{ exact render={() => { plugins.share.url.locators - .get(ALL_DATASETS_LOCATOR_ID) + .get(LOGS_LOCATOR_ID) ?.navigate({}); return null; diff --git a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx index c8000a2f7f5c6..8423374e04fbc 100644 --- a/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/components/asset_details/tabs/logs/logs.tsx @@ -11,10 +11,15 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiFieldSearch, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { LogStream } from '@kbn/logs-shared-plugin/public'; -import type { LogViewReference } from '@kbn/logs-shared-plugin/common'; -import { DEFAULT_LOG_VIEW, getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { + DEFAULT_LOG_VIEW, + getLogsLocatorFromUrlService, + getNodeQuery, + type LogViewReference, +} from '@kbn/logs-shared-plugin/common'; import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; import { OpenInLogsExplorerButton } from '@kbn/logs-shared-plugin/public'; +import moment from 'moment'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { InfraLoadingPanel } from '../../../loading'; import { useAssetDetailsRenderPropsContext } from '../../hooks/use_asset_details_render_props'; @@ -35,7 +40,7 @@ export const Logs = () => { const { loading: logViewLoading, reference: logViewReference } = logs ?? {}; const { services } = useKibanaContextForPlugin(); - const { nodeLogsLocator } = getLogsLocatorsFromUrlService(services.share.url); + const logsLocator = getLogsLocatorFromUrlService(services.share.url)!; const [textQuery, setTextQuery] = useState(urlState?.logsSearch ?? ''); const [textQueryDebounced, setTextQueryDebounced] = useState(urlState?.logsSearch ?? ''); @@ -78,14 +83,27 @@ export const Logs = () => { ); const logsUrl = useMemo(() => { - return nodeLogsLocator.getRedirectUrl({ - nodeField: findInventoryFields(asset.type).id, - nodeId: asset.id, - time: state.startTimestamp, - filter: textQueryDebounced, + return logsLocator.getRedirectUrl({ + query: getNodeQuery({ + nodeField: findInventoryFields(asset.type).id, + nodeId: asset.id, + filter: textQueryDebounced, + }), + timeRange: { + from: moment(state.startTimestamp).toISOString(), + to: moment(state.currentTimestamp).toISOString(), + }, logView, }); - }, [nodeLogsLocator, asset.id, asset.type, state.startTimestamp, textQueryDebounced, logView]); + }, [ + logsLocator, + asset.id, + asset.type, + state.startTimestamp, + state.currentTimestamp, + textQueryDebounced, + logView, + ]); return ( diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index f5590bd5442aa..275800bcfbf47 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -7,7 +7,7 @@ import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorFromUrlService, getTimeRange } from '@kbn/logs-shared-plugin/common'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; @@ -17,7 +17,7 @@ export const RedirectToLogs = () => { const { services: { share }, } = useKibanaContextForPlugin(); - const { logsLocator } = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url)!; const filter = getFilterFromLocation(location); const time = getTimeFromLocation(location); @@ -25,8 +25,8 @@ export const RedirectToLogs = () => { useEffect(() => { logsLocator.navigate( { - time, - filter, + query: { language: 'kuery', query: filter }, + timeRange: getTimeRange(time), }, { replace: true } ); diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 151152694f44e..79ccce31f0866 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -7,9 +7,15 @@ import { useEffect } from 'react'; import type { RouteComponentProps } from 'react-router-dom'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; -import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; -import { findInventoryFields } from '@kbn/metrics-data-access-plugin/common'; +import { + getLogsLocatorFromUrlService, + getNodeQuery, + getTimeRange, +} from '@kbn/logs-shared-plugin/common'; +import { + findInventoryFields, + type InventoryItemType, +} from '@kbn/metrics-data-access-plugin/common'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; @@ -29,22 +35,24 @@ export const RedirectToNodeLogs = ({ const { services: { share }, } = useKibanaContextForPlugin(); - const { nodeLogsLocator } = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url)!; const filter = getFilterFromLocation(location); const time = getTimeFromLocation(location); useEffect(() => { - nodeLogsLocator.navigate( + logsLocator.navigate( { - nodeField: findInventoryFields(nodeType).id, - nodeId, - time, - filter, + query: getNodeQuery({ + nodeField: findInventoryFields(nodeType).id, + nodeId, + filter, + }), + timeRange: getTimeRange(time), }, { replace: true } ); - }, [filter, nodeLogsLocator, nodeId, nodeType, time]); + }, [filter, logsLocator, nodeId, nodeType, time]); return null; }; diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/logs/page_content.tsx index cd28fe24c390c..3eb3c9fae53de 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/logs/page_content.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/logs/page_content.tsx @@ -12,16 +12,13 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { HeaderMenuPortal, useLinkProps } from '@kbn/observability-shared-plugin/public'; import type { SharePublicStart } from '@kbn/share-plugin/public/plugin'; -import type { - ObservabilityOnboardingLocatorParams, - AllDatasetsLocatorParams, -} from '@kbn/deeplinks-observability'; import { + type ObservabilityOnboardingLocatorParams, OBSERVABILITY_ONBOARDING_LOCATOR, - ALL_DATASETS_LOCATOR_ID, } from '@kbn/deeplinks-observability'; import { dynamic } from '@kbn/shared-ux-utility'; import { isDevMode } from '@kbn/xstate-utils'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { LazyAlertDropdownWrapper } from '../../alerting/log_threshold'; import { HelpCenterContent } from '../../components/help_center_content'; import { useReadOnlyBadge } from '../../hooks/use_readonly_badge'; @@ -94,7 +91,7 @@ export const LogsPageContent: React.FunctionComponent = () => { path="/stream" exact render={() => { - share.url.locators.get(ALL_DATASETS_LOCATOR_ID)?.navigate({}); + share.url.locators.get(LOGS_LOCATOR_ID)?.navigate({}); return null; }} diff --git a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx index d594f02d36ce5..e5edfabcb7572 100644 --- a/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx +++ b/x-pack/solutions/observability/plugins/infra/public/pages/metrics/hosts/components/tabs/logs/logs_link_to_stream.tsx @@ -5,9 +5,12 @@ * 2.0. */ import React from 'react'; -import type { LogViewReference } from '@kbn/logs-shared-plugin/common'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { + getLogsLocatorFromUrlService, + type LogViewReference, +} from '@kbn/logs-shared-plugin/common'; import { OpenInLogsExplorerButton } from '@kbn/logs-shared-plugin/public'; +import moment from 'moment'; import { useKibanaContextForPlugin } from '../../../../../../hooks/use_kibana'; interface LogsLinkToStreamProps { @@ -20,15 +23,14 @@ interface LogsLinkToStreamProps { export const LogsLinkToStream = ({ startTime, endTime, query, logView }: LogsLinkToStreamProps) => { const { services } = useKibanaContextForPlugin(); const { share } = services; - const { logsLocator } = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url)!; return ( { showOnHomePage: false, category: 'data', }); - - homePlugin.featureCatalogue.register({ - id: 'observability-logs-explorer', - title: i18n.translate('xpack.infra.registerFeatures.logsTitle', { - defaultMessage: 'Logs', - }), - description: i18n.translate('xpack.infra.registerFeatures.logsDescription', { - defaultMessage: - 'Stream logs in real time or scroll through historical views in a console-like experience.', - }), - icon: 'logsApp', - path: `/app/observability-logs-explorer`, - showOnHomePage: false, - category: 'data', - }); }; diff --git a/x-pack/solutions/observability/plugins/infra/public/utils/logs_overview_fetchers.ts b/x-pack/solutions/observability/plugins/infra/public/utils/logs_overview_fetchers.ts index e30fd7d48a98e..70fd808c4f810 100644 --- a/x-pack/solutions/observability/plugins/infra/public/utils/logs_overview_fetchers.ts +++ b/x-pack/solutions/observability/plugins/infra/public/utils/logs_overview_fetchers.ts @@ -11,7 +11,8 @@ import type { FetchDataParams, LogsFetchDataResponse, } from '@kbn/observability-plugin/public'; -import { DEFAULT_LOG_VIEW, getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { DEFAULT_LOG_VIEW, getLogsLocatorFromUrlService } from '@kbn/logs-shared-plugin/common'; +import moment from 'moment'; import { TIMESTAMP_FIELD } from '../../common/constants'; import type { InfraClientStartDeps, InfraClientStartServicesAccessor } from '../types'; @@ -67,13 +68,13 @@ export function getLogsOverviewDataFetcher( params, data ); - const { logsLocator } = getLogsLocatorsFromUrlService(share.url); + const logsLocator = getLogsLocatorFromUrlService(share.url)!; const timeSpanInMinutes = (params.absoluteTime.end - params.absoluteTime.start) / (1000 * 60); const appLink = logsLocator.getRedirectUrl({ timeRange: { - startTime: params.absoluteTime.start, - endTime: params.absoluteTime.end, + from: moment(params.absoluteTime.start).toISOString(), + to: moment(params.absoluteTime.end).toISOString(), }, }); diff --git a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/index.ts b/x-pack/solutions/observability/plugins/logs_explorer/common/locators/index.ts deleted file mode 100644 index 1e8e57138699b..0000000000000 --- a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/index.ts +++ /dev/null @@ -1,14 +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 { LogsExplorerLocator } from './logs_explorer/logs_explorer_locator'; - -export * from './logs_explorer'; - -export interface LogsExplorerLocators { - logsExplorerLocator: LogsExplorerLocator; -} diff --git a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/index.ts b/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/index.ts deleted file mode 100644 index 85834bf50ac64..0000000000000 --- a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export * from './logs_explorer_locator'; diff --git a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.test.ts b/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.test.ts deleted file mode 100644 index 6d11192063558..0000000000000 --- a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.test.ts +++ /dev/null @@ -1,58 +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 { sharePluginMock } from '@kbn/share-plugin/public/mocks'; -import { LogsExplorerLocatorDefinition } from './logs_explorer_locator'; -import { LogsExplorerLocatorDependencies } from './types'; - -const setup = async () => { - const logsExplorerLocatorDependencies: LogsExplorerLocatorDependencies = { - discoverAppLocator: sharePluginMock.createLocator(), - }; - const logsExplorerLocator = new LogsExplorerLocatorDefinition(logsExplorerLocatorDependencies); - - return { - logsExplorerLocator, - discoverGetLocation: logsExplorerLocatorDependencies.discoverAppLocator?.getLocation, - }; -}; - -describe('Logs Explorer Locators', () => { - const dataset = 'logs-*-*'; - it('should call discover locator with empty params', async () => { - const { logsExplorerLocator, discoverGetLocation } = await setup(); - await logsExplorerLocator.getLocation({}); - - expect(discoverGetLocation).toBeCalledWith({}); - }); - - it('should call discover locator with correct dataViewId if dataset is sent', async () => { - const { logsExplorerLocator, discoverGetLocation } = await setup(); - await logsExplorerLocator.getLocation({ dataset }); - - expect(discoverGetLocation).toBeCalledWith( - expect.objectContaining({ - dataViewId: 'logs-*-*', - }) - ); - }); - - it('should call discover locator with correct dataViewSpec if dataset is sent', async () => { - const { logsExplorerLocator, discoverGetLocation } = await setup(); - await logsExplorerLocator.getLocation({ dataset }); - - expect(discoverGetLocation).toBeCalledWith( - expect.objectContaining({ - dataViewId: 'logs-*-*', - dataViewSpec: { - id: 'logs-*-*', - title: 'logs-*-*', - }, - }) - ); - }); -}); diff --git a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.ts b/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.ts deleted file mode 100644 index c60b0ca74dddb..0000000000000 --- a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/logs_explorer_locator.ts +++ /dev/null @@ -1,43 +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 type { DataViewSpec } from '@kbn/data-views-plugin/common'; -import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { - LogsExplorerLocatorParams, - LOGS_EXPLORER_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; -import { LogsExplorerLocatorDependencies } from './types'; - -export type LogsExplorerLocator = LocatorPublic; - -export class LogsExplorerLocatorDefinition implements LocatorDefinition { - public readonly id = LOGS_EXPLORER_LOCATOR_ID; - - constructor(protected readonly deps: LogsExplorerLocatorDependencies) {} - - public readonly getLocation = (params: LogsExplorerLocatorParams) => { - const { dataset, columns } = params; - const dataViewSpec: DataViewSpec | undefined = dataset - ? { - id: dataset, - title: dataset, - } - : undefined; - - const discoverColumns = columns?.map((column) => { - return column.type === 'document-field' ? column.field : column.smartField; - }); - - return this.deps.discoverAppLocator?.getLocation({ - ...params, - columns: discoverColumns, - dataViewId: dataset, - dataViewSpec, - })!; - }; -} diff --git a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/types.ts b/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/types.ts deleted file mode 100644 index 6b0e497758862..0000000000000 --- a/x-pack/solutions/observability/plugins/logs_explorer/common/locators/logs_explorer/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; -import { LocatorPublic } from '@kbn/share-plugin/common'; - -export interface LogsExplorerLocatorDependencies { - discoverAppLocator?: LocatorPublic; -} diff --git a/x-pack/solutions/observability/plugins/logs_explorer/public/plugin.ts b/x-pack/solutions/observability/plugins/logs_explorer/public/plugin.ts index 97463d03ab489..ce2e6c0a5c05a 100644 --- a/x-pack/solutions/observability/plugins/logs_explorer/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/logs_explorer/public/plugin.ts @@ -6,8 +6,6 @@ */ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; -import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; -import { LogsExplorerLocatorDefinition, LogsExplorerLocators } from '../common/locators'; import { createLogsExplorer } from './components/logs_explorer'; import { createLogsExplorerControllerLazyFactory } from './controller/lazy_create_controller'; import type { @@ -20,29 +18,10 @@ import type { export class LogsExplorerPlugin implements Plugin { - private locators?: LogsExplorerLocators; - constructor(context: PluginInitializerContext) {} public setup(core: CoreSetup, plugins: LogsExplorerSetupDeps) { - const { share } = plugins; - const discoverAppLocator = - share.url.locators.get(DISCOVER_APP_LOCATOR); - - // Register Locators - const logsExplorerLocator = share.url.locators.create( - new LogsExplorerLocatorDefinition({ - discoverAppLocator, - }) - ); - - this.locators = { - logsExplorerLocator, - }; - - return { - locators: this.locators, - }; + return {}; } public start(core: CoreStart, plugins: LogsExplorerStartDeps) { diff --git a/x-pack/solutions/observability/plugins/logs_explorer/public/types.ts b/x-pack/solutions/observability/plugins/logs_explorer/public/types.ts index 6e4c5fdd4c66b..1a0bf6690b2e3 100644 --- a/x-pack/solutions/observability/plugins/logs_explorer/public/types.ts +++ b/x-pack/solutions/observability/plugins/logs_explorer/public/types.ts @@ -13,13 +13,12 @@ import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; -import type { LogsExplorerLocators } from '../common/locators'; import type { LogsExplorerProps } from './components/logs_explorer'; import type { CreateLogsExplorerController } from './controller'; -export interface LogsExplorerPluginSetup { - locators: LogsExplorerLocators; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface LogsExplorerPluginSetup {} + export interface LogsExplorerPluginStart { LogsExplorer: ComponentType; createLogsExplorerController: CreateLogsExplorerController; diff --git a/x-pack/solutions/observability/plugins/logs_explorer/server/plugin.ts b/x-pack/solutions/observability/plugins/logs_explorer/server/plugin.ts index 9c1262868b186..575dc019c5643 100644 --- a/x-pack/solutions/observability/plugins/logs_explorer/server/plugin.ts +++ b/x-pack/solutions/observability/plugins/logs_explorer/server/plugin.ts @@ -6,36 +6,14 @@ */ import { Plugin, CoreSetup } from '@kbn/core/server'; -import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; -import { LogsExplorerLocatorDefinition, LogsExplorerLocators } from '../common/locators'; import { uiSettings } from '../common/ui_settings'; import type { LogsExplorerSetupDeps } from './types'; export class LogsExplorerServerPlugin implements Plugin { - private locators?: LogsExplorerLocators; - setup(core: CoreSetup, plugins: LogsExplorerSetupDeps) { - const { share } = plugins; - core.uiSettings.register(uiSettings); - const discoverAppLocator = - share.url.locators.get(DISCOVER_APP_LOCATOR); - - // Register Locators - const logsExplorerLocator = share.url.locators.create( - new LogsExplorerLocatorDefinition({ - discoverAppLocator, - }) - ); - - this.locators = { - logsExplorerLocator, - }; - - return { - locators: this.locators, - }; + return {}; } start() {} diff --git a/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.test.ts b/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.test.ts index e275a89e18b3a..c15f3b29a7213 100644 --- a/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.test.ts +++ b/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.test.ts @@ -7,13 +7,13 @@ import { Aggregators } from './types'; import { LocatorPublic } from '@kbn/share-plugin/common'; -import { LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; +import type { LogsLocatorParams } from '@kbn/logs-shared-plugin/common'; import { getViewInAppUrl, GetViewInAppUrlArgs } from './get_view_in_app_url'; describe('getViewInAppUrl', () => { - const logsExplorerLocator = { + const logsLocator = { getRedirectUrl: jest.fn(() => 'mockedGetRedirectUrl'), - } as unknown as LocatorPublic; + } as unknown as LocatorPublic; const startedAt = '2023-12-07T16:30:15.403Z'; const endedAt = '2023-12-07T20:30:15.403Z'; const returnedTimeRange = { @@ -26,7 +26,7 @@ describe('getViewInAppUrl', () => { jest.clearAllMocks(); }); - it('Should return empty string if logsExplorerLocator is not provided', () => { + it('Should return empty string if logsLocator is not provided', () => { const args: GetViewInAppUrlArgs = { metrics: [], startedAt, @@ -45,7 +45,7 @@ describe('getViewInAppUrl', () => { filter: 'mockedCountFilter', }, ], - logsExplorerLocator, + logsLocator, startedAt, endedAt, searchConfiguration: { @@ -59,9 +59,10 @@ describe('getViewInAppUrl', () => { }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { - dataset: args.dataViewId, + dataViewId: args.dataViewId, + dataViewSpec: {}, timeRange: returnedTimeRange, filters: [], query: { @@ -82,13 +83,13 @@ describe('getViewInAppUrl', () => { filter: 'mockedCountFilter', }, ], - logsExplorerLocator, + logsLocator, startedAt, endedAt, }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { dataset: undefined, timeRange: returnedTimeRange, @@ -104,7 +105,7 @@ describe('getViewInAppUrl', () => { it('should call getRedirectUrl with only filter', () => { const args: GetViewInAppUrlArgs = { - logsExplorerLocator, + logsLocator, startedAt, endedAt, searchConfiguration: { @@ -117,9 +118,10 @@ describe('getViewInAppUrl', () => { }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { - dataset: undefined, + dataViewId: undefined, + dataViewSpec: {}, timeRange: returnedTimeRange, filters: [], query: { @@ -133,13 +135,13 @@ describe('getViewInAppUrl', () => { it('should call getRedirectUrl with empty query if metrics and filter are not not provided', () => { const args: GetViewInAppUrlArgs = { - logsExplorerLocator, + logsLocator, startedAt, endedAt, }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { dataset: undefined, timeRange: returnedTimeRange, @@ -167,13 +169,13 @@ describe('getViewInAppUrl', () => { field: 'mockedAvgField', }, ], - logsExplorerLocator, + logsLocator, startedAt, endedAt, }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { dataset: undefined, timeRange: returnedTimeRange, @@ -201,7 +203,7 @@ describe('getViewInAppUrl', () => { field: 'mockedAvgField', }, ], - logsExplorerLocator, + logsLocator, startedAt, endedAt, searchConfiguration: { @@ -232,9 +234,10 @@ describe('getViewInAppUrl', () => { }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { - dataset: undefined, + dataViewId: undefined, + dataViewSpec: {}, timeRange: returnedTimeRange, filters: [ { @@ -275,14 +278,14 @@ describe('getViewInAppUrl', () => { filter: 'mockedCountFilter', }, ], - logsExplorerLocator, + logsLocator, startedAt, endedAt, spaceId, }; expect(getViewInAppUrl(args)).toBe('mockedGetRedirectUrl'); - expect(logsExplorerLocator.getRedirectUrl).toHaveBeenCalledWith( + expect(logsLocator.getRedirectUrl).toHaveBeenCalledWith( { dataset: undefined, timeRange: returnedTimeRange, diff --git a/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts b/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts index 0d6095f6b520f..8aa4079be516c 100644 --- a/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts +++ b/x-pack/solutions/observability/plugins/observability/common/custom_threshold_rule/get_view_in_app_url.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; import type { TimeRange } from '@kbn/es-query'; import { getPaddedAlertTimeRange } from '@kbn/observability-get-padded-alert-time-range-util'; import type { LocatorPublic } from '@kbn/share-plugin/common'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { getGroupFilters } from './helpers/get_group'; import { SearchConfigurationWithExtractedReferenceType } from './types'; import type { CustomThresholdExpressionMetric } from './types'; @@ -19,7 +19,7 @@ export interface GetViewInAppUrlArgs { dataViewId?: string; endedAt?: string; groups?: Group[]; - logsExplorerLocator?: LocatorPublic; + logsLocator?: LocatorPublic; metrics?: CustomThresholdExpressionMetric[]; startedAt?: string; spaceId?: string; @@ -29,15 +29,14 @@ export const getViewInAppUrl = ({ dataViewId, endedAt, groups, - logsExplorerLocator, + logsLocator, metrics = [], searchConfiguration, startedAt = new Date().toISOString(), spaceId, }: GetViewInAppUrlArgs) => { - if (!logsExplorerLocator) return ''; + if (!logsLocator) return ''; - const dataset = searchConfiguration?.index.title ?? dataViewId; const searchConfigurationQuery = searchConfiguration?.query.query; const searchConfigurationFilters = searchConfiguration?.filter || []; const groupFilters = getGroupFilters(groups); @@ -58,9 +57,10 @@ export const getViewInAppUrl = ({ query.query = searchConfigurationQuery; } - return logsExplorerLocator?.getRedirectUrl( + return logsLocator.getRedirectUrl( { - dataset, + dataViewId, + dataViewSpec: searchConfiguration?.index, timeRange, query, filters: [...searchConfigurationFilters, ...groupFilters], diff --git a/x-pack/solutions/observability/plugins/observability/kibana.jsonc b/x-pack/solutions/observability/plugins/observability/kibana.jsonc index 3a888ce14e5ef..161e388fe29bb 100644 --- a/x-pack/solutions/observability/plugins/observability/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability/kibana.jsonc @@ -61,6 +61,7 @@ ], "requiredBundles": [ "data", + "discover", "kibanaReact", "kibanaUtils", "unifiedSearch", @@ -71,4 +72,4 @@ "common" ] } -} +} \ No newline at end of file diff --git a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index cc2f8364ce5b6..525959710d289 100644 --- a/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -35,7 +35,7 @@ import type { RangeEventAnnotationConfig, } from '@kbn/event-annotation-common'; import moment from 'moment'; -import { LOGS_EXPLORER_LOCATOR_ID, LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; +import { DISCOVER_APP_LOCATOR, DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { TimeRange } from '@kbn/es-query'; import { getGroupFilters } from '../../../../../common/custom_threshold_rule/helpers/get_group'; import { useLicense } from '../../../../hooks/use_license'; @@ -137,7 +137,7 @@ export default function AlertDetailsAppSection({ alert }: AppSectionProps) { const appUrl = getViewInAppUrl({ dataViewId: dataView?.id, groups, - logsExplorerLocator: locators.get(LOGS_EXPLORER_LOCATOR_ID), + logsLocator: locators.get(DISCOVER_APP_LOCATOR), metrics: criterion?.metrics, searchConfiguration: ruleParams.searchConfiguration as SearchConfigurationWithExtractedReferenceType, diff --git a/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts b/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts index 2413a81b32f1c..619e9df7b7642 100644 --- a/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts +++ b/x-pack/solutions/observability/plugins/observability/public/navigation_tree.ts @@ -37,21 +37,7 @@ function createNavTree({ streamsAvailable }: { streamsAvailable?: boolean }) { title: i18n.translate('xpack.observability.obltNav.discover', { defaultMessage: 'Discover', }), - // 'last-used-logs-viewer' is wrapper app to handle the navigation between observability-log-explorer and discover - link: 'last-used-logs-viewer', - breadcrumbStatus: 'hidden', // avoid duplicate "Discover" breadcrumbs - renderAs: 'item', - children: [ - { - link: 'discover', - children: [ - { - // This is to show "observability-log-explorer" breadcrumbs when navigating from "discover" to "log explorer" - link: 'observability-logs-explorer', - }, - ], - }, - ], + link: 'discover', }, { link: 'dashboards', diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/landing/landing.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/landing/landing.tsx index cbeebf0edab1e..82f11b4702abf 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/landing/landing.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/landing/landing.tsx @@ -5,10 +5,7 @@ * 2.0. */ import React, { useEffect } from 'react'; -import { - AllDatasetsLocatorParams, - ALL_DATASETS_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { useHasData } from '../../hooks/use_has_data'; import { useKibana } from '../../utils/kibana_react'; @@ -27,10 +24,8 @@ export function LandingPage() { const hasLogsData = logs?.hasData; if (hasLogsData) { - const allDataSetsLocator = - url.locators.get(ALL_DATASETS_LOCATOR_ID); - - allDataSetsLocator?.navigate({}); + const logsLocator = url.locators.get(LOGS_LOCATOR_ID); + logsLocator?.navigate({}); } else if (hasApmData) { navigateToUrl(basePath.prepend('/app/apm/services')); } else { diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/observability_status/content.ts b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/observability_status/content.ts index 0fa5b71933186..4f3ffc6653b23 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/observability_status/content.ts +++ b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/observability_status/content.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { HttpSetup, DocLinksStart } from '@kbn/core/public'; import { BrowserUrlService } from '@kbn/share-plugin/public'; -import { getLogsLocatorsFromUrlService } from '@kbn/logs-shared-plugin/common'; +import { getLogsLocatorFromUrlService } from '@kbn/logs-shared-plugin/common'; import { ObservabilityFetchDataPlugins } from '../../../../typings/fetch_overview_data'; import { paths } from '../../../../../common/locators/paths'; @@ -28,7 +28,7 @@ export const getContent = ( docLinks: DocLinksStart, urlService: BrowserUrlService ): ObservabilityStatusContent[] => { - const { logsLocator } = getLogsLocatorsFromUrlService(urlService); + const logsLocator = getLogsLocatorFromUrlService(urlService)!; return [ { id: 'infra_logs', @@ -45,7 +45,7 @@ export const getContent = ( addLink: http.basePath.prepend('/app/integrations/browse?q=logs'), learnMoreLink: docLinks.links.observability.monitorLogs, goToAppTitle: i18n.translate('xpack.observability.statusVisualization.logs.goToAppTitle', { - defaultMessage: 'Show Logs Explorer', + defaultMessage: 'Show logs', }), goToAppLink: logsLocator.getRedirectUrl({}), weight: 1, diff --git a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/sections/logs/logs_section.tsx b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/sections/logs/logs_section.tsx index 1182e0cbdcb1a..9e46b01037a65 100644 --- a/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/sections/logs/logs_section.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/pages/overview/components/sections/logs/logs_section.tsx @@ -101,7 +101,7 @@ export function LogsSection({ bucketSize }: Props) { appLink={{ href: appLink, label: i18n.translate('xpack.observability.overview.logs.appLink', { - defaultMessage: 'Show Logs Explorer', + defaultMessage: 'Show logs', }), prependBasePath: false, }} diff --git a/x-pack/solutions/observability/plugins/observability/public/plugin.ts b/x-pack/solutions/observability/plugins/observability/public/plugin.ts index c37f3cc2f624a..8e80216a000a3 100644 --- a/x-pack/solutions/observability/plugins/observability/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/observability/public/plugin.ts @@ -25,7 +25,7 @@ import { import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; -import { LOGS_EXPLORER_LOCATOR_ID, LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; +import { DISCOVER_APP_LOCATOR, type DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { EmbeddableStart } from '@kbn/embeddable-plugin/public'; import type { FieldFormatsSetup, FieldFormatsStart } from '@kbn/field-formats-plugin/public'; @@ -252,8 +252,8 @@ export class Plugin new RuleDetailsLocatorDefinition() ); - const logsExplorerLocator = - pluginsSetup.share.url.locators.get(LOGS_EXPLORER_LOCATOR_ID); + const logsLocator = + pluginsSetup.share.url.locators.get(DISCOVER_APP_LOCATOR); const mount = async (params: AppMountParameters) => { // Load application bundle @@ -314,7 +314,7 @@ export class Plugin registerObservabilityRuleTypes( this.observabilityRuleTypeRegistry, coreSetup.uiSettings, - logsExplorerLocator + logsLocator ); if (pluginsSetup.home) { diff --git a/x-pack/solutions/observability/plugins/observability/public/rules/register_observability_rule_types.ts b/x-pack/solutions/observability/plugins/observability/public/rules/register_observability_rule_types.ts index 2fd8407f39a5f..9667d9fb2b16e 100644 --- a/x-pack/solutions/observability/plugins/observability/public/rules/register_observability_rule_types.ts +++ b/x-pack/solutions/observability/plugins/observability/public/rules/register_observability_rule_types.ts @@ -16,7 +16,7 @@ import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, } from '@kbn/rule-data-utils'; import type { LocatorPublic } from '@kbn/share-plugin/common'; -import { LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { IUiSettingsClient } from '@kbn/core-ui-settings-browser'; import type { CustomMetricExpressionParams, @@ -59,7 +59,7 @@ const getDataViewId = (searchConfiguration?: SearchConfigurationWithExtractedRef export const registerObservabilityRuleTypes = ( observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry, uiSettings: IUiSettingsClient, - logsExplorerLocator?: LocatorPublic + logsLocator?: LocatorPublic ) => { const validateCustomThresholdWithUiSettings = ({ criteria, @@ -102,7 +102,7 @@ export const registerObservabilityRuleTypes = ( link: getViewInAppUrl({ dataViewId, groups, - logsExplorerLocator, + logsLocator, metrics, searchConfiguration, startedAt: fields[ALERT_START], diff --git a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts index 72c9795122dc8..9e3b16dc6c48c 100644 --- a/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts +++ b/x-pack/solutions/observability/plugins/observability/server/lib/rules/custom_threshold/custom_threshold_executor.ts @@ -6,7 +6,6 @@ */ import { isEqual } from 'lodash'; -import { LogsExplorerLocatorParams } from '@kbn/deeplinks-observability'; import { ALERT_EVALUATION_VALUES, ALERT_EVALUATION_THRESHOLD, @@ -18,6 +17,7 @@ import { RecoveredActionGroup } from '@kbn/alerting-plugin/common'; import { IBasePath, Logger } from '@kbn/core/server'; import { AlertsClientError, RuleExecutorOptions } from '@kbn/alerting-plugin/server'; import { getEcsGroups } from '@kbn/observability-alerting-rule-utils'; +import type { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { getEsQueryConfig } from '../../../utils/get_es_query_config'; import { AlertsLocatorParams, getAlertDetailsUrl } from '../../../../common'; import { getViewInAppUrl } from '../../../../common/custom_threshold_rule/get_view_in_app_url'; @@ -50,14 +50,14 @@ import { MissingGroupsRecord } from './lib/check_missing_group'; export interface CustomThresholdLocators { alertsLocator?: LocatorPublic; - logsExplorerLocator?: LocatorPublic; + logsLocator?: LocatorPublic; } export const createCustomThresholdExecutor = ({ basePath, logger, config, - locators: { logsExplorerLocator }, + locators: { logsLocator }, }: { basePath: IBasePath; logger: Logger; @@ -281,7 +281,7 @@ export const createCustomThresholdExecutor = ({ viewInAppUrl: getViewInAppUrl({ dataViewId: params.searchConfiguration?.index?.title ?? dataViewId, groups, - logsExplorerLocator, + logsLocator, metrics: alertResults.length === 1 ? alertResults[0][group].metrics : [], searchConfiguration: params.searchConfiguration, startedAt: indexedStartedAt, @@ -317,7 +317,7 @@ export const createCustomThresholdExecutor = ({ viewInAppUrl: getViewInAppUrl({ dataViewId, groups: group, - logsExplorerLocator, + logsLocator, metrics: params.criteria[0]?.metrics, searchConfiguration: params.searchConfiguration, startedAt: indexedStartedAt, diff --git a/x-pack/solutions/observability/plugins/observability/server/plugin.ts b/x-pack/solutions/observability/plugins/observability/server/plugin.ts index 53c91feb3b842..29219d94213c0 100644 --- a/x-pack/solutions/observability/plugins/observability/server/plugin.ts +++ b/x-pack/solutions/observability/plugins/observability/server/plugin.ts @@ -19,7 +19,7 @@ import { Plugin, PluginInitializerContext, } from '@kbn/core/server'; -import { LogsExplorerLocatorParams, LOGS_EXPLORER_LOCATOR_ID } from '@kbn/deeplinks-observability'; +import { DISCOVER_APP_LOCATOR, type DiscoverAppLocatorParams } from '@kbn/discover-plugin/common'; import { FeaturesPluginSetup } from '@kbn/features-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; import { i18n } from '@kbn/i18n'; @@ -94,8 +94,8 @@ export class ObservabilityPlugin implements Plugin { const alertsLocator = plugins.share.url.locators.create(new AlertsLocatorDefinition()); - const logsExplorerLocator = - plugins.share.url.locators.get(LOGS_EXPLORER_LOCATOR_ID); + const logsLocator = + plugins.share.url.locators.get(DISCOVER_APP_LOCATOR); const alertDetailsContextualInsightsService = new AlertDetailsContextualInsightsService(); @@ -177,7 +177,7 @@ export class ObservabilityPlugin implements Plugin { registerRuleTypes(plugins.alerting, core.http.basePath, config, this.logger, { alertsLocator, - logsExplorerLocator, + logsLocator, }); void core.getStartServices().then(([coreStart, pluginStart]) => { diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/index.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/index.ts index fe0712ce338c2..57fc199f8d022 100644 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/index.ts +++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/index.ts @@ -5,11 +5,6 @@ * 2.0. */ -export { - type ObservabilityLogsExplorerLocators, - SingleDatasetLocatorDefinition, - AllDatasetsLocatorDefinition, -} from './locators'; export { OBSERVABILITY_LOGS_EXPLORER_URL_STATE_KEY, logsExplorerUrlSchemaV1, diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/all_datasets_locator.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/all_datasets_locator.ts deleted file mode 100644 index cbcfb5802c15c..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/all_datasets_locator.ts +++ /dev/null @@ -1,33 +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 type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { - AllDatasetsLocatorParams, - ALL_DATASETS_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; -import { AllDatasetSelection } from '@kbn/logs-explorer-plugin/common'; -import { ObsLogsExplorerLocatorDependencies } from './types'; -import { constructLocatorPath } from './utils'; - -export type AllDatasetsLocator = LocatorPublic; - -export class AllDatasetsLocatorDefinition implements LocatorDefinition { - public readonly id = ALL_DATASETS_LOCATOR_ID; - - constructor(protected readonly deps: ObsLogsExplorerLocatorDependencies) {} - - public readonly getLocation = (params: AllDatasetsLocatorParams) => { - const { useHash } = this.deps; - - return constructLocatorPath({ - dataSourceSelection: AllDatasetSelection.getLocatorPlainSelection(), - locatorParams: params, - useHash, - }); - }; -} diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/data_view_locator.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/data_view_locator.ts deleted file mode 100644 index 2830fe4f6c334..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/data_view_locator.ts +++ /dev/null @@ -1,40 +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 type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { DataViewSelection } from '@kbn/logs-explorer-plugin/common'; -import { - ObsLogsExplorerDataViewLocatorParams, - OBS_LOGS_EXPLORER_DATA_VIEW_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; -import { constructLocatorPath } from './utils'; -import { ObsLogsExplorerLocatorDependencies } from './types'; - -export type DataViewLocator = LocatorPublic; - -export class DataViewLocatorDefinition - implements LocatorDefinition -{ - public readonly id = OBS_LOGS_EXPLORER_DATA_VIEW_LOCATOR_ID; - - constructor(protected readonly deps: ObsLogsExplorerLocatorDependencies) {} - - public readonly getLocation = (params: ObsLogsExplorerDataViewLocatorParams) => { - const { useHash } = this.deps; - const { id, ...locatorParams } = params; - - const dataViewSelection = DataViewSelection.fromSelection({ - dataView: { id }, - }); - - return constructLocatorPath({ - dataSourceSelection: dataViewSelection.toPlainSelection(), - locatorParams, - useHash, - }); - }; -} diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/index.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/index.ts deleted file mode 100644 index 6692c6d30cea9..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/index.ts +++ /dev/null @@ -1,21 +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. - */ - -export type { ObservabilityLogsExplorerLocationState } from '@kbn/deeplinks-observability/locators'; -import { AllDatasetsLocator } from './all_datasets_locator'; -import { DataViewLocator } from './data_view_locator'; -import { SingleDatasetLocator } from './single_dataset_locator'; - -export * from './single_dataset_locator'; -export * from './all_datasets_locator'; -export * from './utils'; - -export interface ObservabilityLogsExplorerLocators { - allDatasetsLocator: AllDatasetsLocator; - dataViewLocator: DataViewLocator; - singleDatasetLocator: SingleDatasetLocator; -} diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/locators.test.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/locators.test.ts deleted file mode 100644 index 0c38eed85694d..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/locators.test.ts +++ /dev/null @@ -1,426 +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 { OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability'; -import { - AllDatasetsLocatorParams, - SingleDatasetLocatorParams, - ObsLogsExplorerDataViewLocatorParams, -} from '@kbn/deeplinks-observability/locators'; -import { AllDatasetsLocatorDefinition } from './all_datasets_locator'; -import { DataViewLocatorDefinition } from './data_view_locator'; -import { SingleDatasetLocatorDefinition } from './single_dataset_locator'; -import { ObsLogsExplorerLocatorDependencies } from './types'; - -const setup = async () => { - const dep: ObsLogsExplorerLocatorDependencies = { - useHash: false, - }; - const allDatasetsLocator = new AllDatasetsLocatorDefinition(dep); - const dataViewLocator = new DataViewLocatorDefinition(dep); - const singleDatasetLocator = new SingleDatasetLocatorDefinition(dep); - - return { - allDatasetsLocator, - dataViewLocator, - singleDatasetLocator, - }; -}; - -describe('Observability Logs Explorer Locators', () => { - const timeRange = { to: 'now', from: 'now-30m' }; - - describe('All Dataset Locator', () => { - it('should create a link with no state', async () => { - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation({}); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: '/?pageState=(dataSourceSelection:(selectionType:all),v:2)', - state: {}, - }); - }); - - it('should allow specifying time range', async () => { - const params: AllDatasetsLocatorParams = { - timeRange, - }; - - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: '/?pageState=(dataSourceSelection:(selectionType:all),time:(from:now-30m,to:now),v:2)', - state: {}, - }); - }); - it('should allow specifying query', async () => { - const params: AllDatasetsLocatorParams = { - query: { - language: 'kuery', - query: 'foo', - }, - }; - - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: '/?pageState=(dataSourceSelection:(selectionType:all),query:(language:kuery,query:foo),v:2)', - state: {}, - }); - }); - - it('should allow specifying refresh interval', async () => { - const params: AllDatasetsLocatorParams = { - refreshInterval: { - pause: false, - value: 666, - }, - }; - - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: '/?pageState=(dataSourceSelection:(selectionType:all),refreshInterval:(pause:!f,value:666),v:2)', - state: {}, - }); - }); - - it('should allow specifying breakdown field', async () => { - const params: AllDatasetsLocatorParams = { - breakdownField: 'service.name', - }; - - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: '/?pageState=(breakdownField:service.name,dataSourceSelection:(selectionType:all),v:2)', - state: {}, - }); - }); - - it('should allow specifying columns', async () => { - const params: AllDatasetsLocatorParams = { - columns: [{ field: '_source', type: 'document-field' }], - }; - - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: '/?pageState=(columns:!((field:_source,type:document-field)),dataSourceSelection:(selectionType:all),v:2)', - state: {}, - }); - }); - - it('should allow specifying filters', async () => { - const params: AllDatasetsLocatorParams = { - filters: [ - { - meta: { - alias: 'foo', - disabled: false, - negate: false, - }, - }, - { - meta: { - alias: 'bar', - disabled: false, - negate: false, - }, - }, - ], - }; - - const { allDatasetsLocator } = await setup(); - const location = await allDatasetsLocator.getLocation(params); - - expect(location.path).toMatchInlineSnapshot( - `"/?pageState=(dataSourceSelection:(selectionType:all),filters:!((meta:(alias:foo,disabled:!f,negate:!f)),(meta:(alias:bar,disabled:!f,negate:!f))),v:2)"` - ); - }); - }); - - describe('Data View Locator', () => { - it('should create a link with correct index', async () => { - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation({ - id: 'data-view-id', - }); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),v:2)`, - state: {}, - }); - }); - - it('should allow specifying time range', async () => { - const params: ObsLogsExplorerDataViewLocatorParams = { - id: 'data-view-id', - timeRange, - }; - - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),time:(from:now-30m,to:now),v:2)`, - state: {}, - }); - }); - - it('should allow specifying query', async () => { - const params: ObsLogsExplorerDataViewLocatorParams = { - id: 'data-view-id', - query: { - language: 'kuery', - query: 'foo', - }, - }; - - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),query:(language:kuery,query:foo),v:2)`, - state: {}, - }); - }); - - it('should allow specifying refresh interval', async () => { - const params: ObsLogsExplorerDataViewLocatorParams = { - id: 'data-view-id', - refreshInterval: { - pause: false, - value: 666, - }, - }; - - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),refreshInterval:(pause:!f,value:666),v:2)`, - state: {}, - }); - }); - - it('should allow specifying breakdown field', async () => { - const params: ObsLogsExplorerDataViewLocatorParams = { - id: 'data-view-id', - breakdownField: 'service.name', - }; - - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(breakdownField:service.name,dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),v:2)`, - state: {}, - }); - }); - - it('should allow specifying columns', async () => { - const params: ObsLogsExplorerDataViewLocatorParams = { - id: 'data-view-id', - columns: [{ field: '_source', type: 'document-field' }], - }; - - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(columns:!((field:_source,type:document-field)),dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),v:2)`, - state: {}, - }); - }); - - it('should allow specifying filters', async () => { - const params: ObsLogsExplorerDataViewLocatorParams = { - id: 'data-view-id', - filters: [ - { - meta: { - alias: 'foo', - disabled: false, - negate: false, - }, - }, - { - meta: { - alias: 'bar', - disabled: false, - negate: false, - }, - }, - ], - }; - - const { dataViewLocator } = await setup(); - const location = await dataViewLocator.getLocation(params); - - expect(location.path).toMatchInlineSnapshot( - `"/?pageState=(dataSourceSelection:(selection:(dataView:(dataType:unresolved,id:data-view-id)),selectionType:dataView),filters:!((meta:(alias:foo,disabled:!f,negate:!f)),(meta:(alias:bar,disabled:!f,negate:!f))),v:2)"` - ); - }); - }); - - describe('Single Dataset Locator', () => { - const integration = 'Test'; - const dataset = 'test-*'; - it('should create a link with correct index', async () => { - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation({ - integration, - dataset, - }); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),v:2)`, - state: {}, - }); - }); - - it('should allow specifying time range', async () => { - const params: SingleDatasetLocatorParams = { - integration, - dataset, - timeRange, - }; - - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),time:(from:now-30m,to:now),v:2)`, - state: {}, - }); - }); - - it('should allow specifying query', async () => { - const params: SingleDatasetLocatorParams = { - integration, - dataset, - query: { - language: 'kuery', - query: 'foo', - }, - }; - - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),query:(language:kuery,query:foo),v:2)`, - state: {}, - }); - }); - - it('should allow specifying refresh interval', async () => { - const params: SingleDatasetLocatorParams = { - integration, - dataset, - refreshInterval: { - pause: false, - value: 666, - }, - }; - - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),refreshInterval:(pause:!f,value:666),v:2)`, - state: {}, - }); - }); - - it('should allow specifying breakdown field', async () => { - const params: SingleDatasetLocatorParams = { - integration, - dataset, - breakdownField: 'service.name', - }; - - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(breakdownField:service.name,dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),v:2)`, - state: {}, - }); - }); - - it('should allow specifying columns', async () => { - const params: SingleDatasetLocatorParams = { - integration, - dataset, - columns: [{ field: '_source', type: 'document-field' }], - }; - - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation(params); - - expect(location).toMatchObject({ - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path: `/?pageState=(columns:!((field:_source,type:document-field)),dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),v:2)`, - state: {}, - }); - }); - - it('should allow specifying filters', async () => { - const params: SingleDatasetLocatorParams = { - integration, - dataset, - filters: [ - { - meta: { - alias: 'foo', - disabled: false, - negate: false, - }, - }, - { - meta: { - alias: 'bar', - disabled: false, - negate: false, - }, - }, - ], - }; - - const { singleDatasetLocator } = await setup(); - const location = await singleDatasetLocator.getLocation(params); - - expect(location.path).toMatchInlineSnapshot( - `"/?pageState=(dataSourceSelection:(selection:(dataset:(name:'logs-test-*-*',title:test),name:Test),selectionType:unresolved),filters:!((meta:(alias:foo,disabled:!f,negate:!f)),(meta:(alias:bar,disabled:!f,negate:!f))),v:2)"` - ); - }); - }); -}); diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/single_dataset_locator.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/single_dataset_locator.ts deleted file mode 100644 index 4187b9067d667..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/single_dataset_locator.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; -import { UnresolvedDatasetSelection } from '@kbn/logs-explorer-plugin/common'; -import type { IndexPattern } from '@kbn/io-ts-utils'; -import { - SingleDatasetLocatorParams, - SINGLE_DATASET_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; -import { ObsLogsExplorerLocatorDependencies } from './types'; -import { constructLocatorPath } from './utils'; - -export type SingleDatasetLocator = LocatorPublic; - -export class SingleDatasetLocatorDefinition - implements LocatorDefinition -{ - public readonly id = SINGLE_DATASET_LOCATOR_ID; - - constructor(protected readonly deps: ObsLogsExplorerLocatorDependencies) {} - - public readonly getLocation = (params: SingleDatasetLocatorParams) => { - const { useHash } = this.deps; - const { integration, dataset } = params; - - const unresolvedDatasetSelection = UnresolvedDatasetSelection.fromSelection({ - name: integration, - dataset: { - name: this.composeIndexPattern(dataset), - }, - }); - - return constructLocatorPath({ - dataSourceSelection: unresolvedDatasetSelection.toPlainSelection(), - locatorParams: params, - useHash, - }); - }; - - private composeIndexPattern(datasetName: SingleDatasetLocatorParams['dataset']) { - return `logs-${datasetName}-*` as IndexPattern; - } -} diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/types.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/types.ts deleted file mode 100644 index 4c80bcf31c2a6..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/types.ts +++ /dev/null @@ -1,9 +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. - */ -export interface ObsLogsExplorerLocatorDependencies { - useHash: boolean; -} diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/construct_locator_path.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/construct_locator_path.ts deleted file mode 100644 index fd1a0249b32f8..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/construct_locator_path.ts +++ /dev/null @@ -1,105 +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 { - DatasetLocatorParams, - FilterControls, - ListFilterControl, -} from '@kbn/deeplinks-observability/locators'; -import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/common'; -import { - AvailableControlPanels, - availableControlsPanels, - DataSourceSelectionPlain, - SMART_FALLBACK_FIELDS, -} from '@kbn/logs-explorer-plugin/common'; -import { OBSERVABILITY_LOGS_EXPLORER_APP_ID } from '@kbn/deeplinks-observability'; -import { - OBSERVABILITY_LOGS_EXPLORER_URL_STATE_KEY, - logsExplorerUrlSchemaV2, -} from '../../url_schema'; -import { deepCompactObject } from '../../utils/deep_compact_object'; - -type ControlsPageState = NonNullable; - -interface LocatorPathConstructionParams { - dataSourceSelection: DataSourceSelectionPlain; - locatorParams: DatasetLocatorParams; - useHash: boolean; -} - -export const constructLocatorPath = async (params: LocatorPathConstructionParams) => { - const { - dataSourceSelection, - locatorParams: { - filterControls, - filters, - query, - refreshInterval, - timeRange, - columns, - origin, - breakdownField, - }, - useHash, - } = params; - - const pageState = logsExplorerUrlSchemaV2.urlSchemaRT.encode( - deepCompactObject({ - v: 2, - dataSourceSelection, - filters, - query, - refreshInterval, - time: timeRange, - breakdownField, - columns: columns?.map((column) => { - return column.type === 'smart-field' ? SMART_FALLBACK_FIELDS[column.smartField] : column; - }), - controls: getControlsPageStateFromFilterControlsParams(filterControls ?? {}), - }) - ); - - const path = setStateToKbnUrl( - OBSERVABILITY_LOGS_EXPLORER_URL_STATE_KEY, - pageState, - { useHash, storeInHashQuery: false }, - '/' - ); - - return { - app: OBSERVABILITY_LOGS_EXPLORER_APP_ID, - path, - state: { - ...(origin ? { origin } : {}), - }, - }; -}; - -const getControlsPageStateFromFilterControlsParams = ( - filterControls: FilterControls -): ControlsPageState => ({ - ...(filterControls.namespace != null - ? getFilterControlPageStateFromListFilterControlsParams( - availableControlsPanels.NAMESPACE, - filterControls.namespace - ) - : {}), -}); - -const getFilterControlPageStateFromListFilterControlsParams = ( - controlId: AvailableControlPanels[keyof AvailableControlPanels], - listFilterControl: ListFilterControl -): ControlsPageState => ({ - [controlId]: { - mode: listFilterControl.mode, - selection: { - type: 'options', - selectedOptions: listFilterControl.values, - }, - }, -}); diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/index.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/index.ts deleted file mode 100644 index 6e5aad44fbe9a..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/common/locators/utils/index.ts +++ /dev/null @@ -1,8 +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. - */ - -export * from './construct_locator_path'; diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/kibana.jsonc b/x-pack/solutions/observability/plugins/observability_logs_explorer/kibana.jsonc index 40f678ac0a15e..23d118588d215 100644 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/kibana.jsonc @@ -36,10 +36,7 @@ "lens", "observabilityAIAssistant" ], - "requiredBundles": [ - "kibanaReact", - "triggersActionsUi" - ], + "requiredBundles": [], "extraPublicDirs": [ "common" ] diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/applications/redirect_to_discover.tsx b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/applications/redirect_to_discover.tsx new file mode 100644 index 0000000000000..3f0cb1114a575 --- /dev/null +++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/applications/redirect_to_discover.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { useLocation } from 'react-router-dom'; +import { Router } from '@kbn/shared-ux-router'; +import { AppMountParameters, CoreStart } from '@kbn/core/public'; +import { DISCOVER_APP_ID } from '@kbn/deeplinks-analytics'; + +export const renderDiscoverRedirect = (core: CoreStart, appParams: AppMountParameters) => { + ReactDOM.render( + + + , + appParams.element + ); + + return () => { + ReactDOM.unmountComponentAtNode(appParams.element); + }; +}; + +export const DiscoverRedirect = ({ core }: { core: CoreStart }) => { + const location = useLocation(); + const path = `${location.pathname}${location.search}`; + core.application.navigateToApp(DISCOVER_APP_ID, { replace: true, path }); + return <>; +}; diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx index 0d0adc23f77c8..50a6b5fdb7230 100644 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx +++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx @@ -17,7 +17,6 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; import { toMountPoint } from '@kbn/react-kibana-mount'; -import { LogsExplorerTabs } from '@kbn/discover-plugin/public'; import React, { useEffect, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { filter, take } from 'rxjs'; @@ -45,18 +44,12 @@ export const LogsExplorerTopNavMenu = () => { const ProjectTopNav = () => { const { euiTheme } = useEuiTheme(); - const { services } = useKibanaContextForPlugin(); return ( - - - - - { - private locators?: ObservabilityLogsExplorerLocators; - private appStateUpdater = new BehaviorSubject(() => ({})); - constructor(context: PluginInitializerContext) {} public setup( core: CoreSetup, _pluginsSetup: ObservabilityLogsExplorerSetupDeps ) { - const { discover, share } = _pluginsSetup; - const useHash = core.uiSettings.get('state:storeInSessionStorage'); - + // App used solely to redirect from "/app/observability-logs-explorer" to "/app/discover" core.application.register({ id: OBSERVABILITY_LOGS_EXPLORER_APP_ID, title: logsExplorerAppTitle, - category: DEFAULT_APP_CATEGORIES.observability, - euiIconType: 'logoLogging', - visibleIn: ['globalSearch'], - keywords: ['logs', 'log', 'explorer', 'logs explorer'], - updater$: this.appStateUpdater, - mount: async (appMountParams: ObservabilityLogsExplorerAppMountParameters) => { - const [coreStart, pluginsStart, ownPluginStart] = await core.getStartServices(); - const { renderObservabilityLogsExplorer } = await import( - './applications/observability_logs_explorer' - ); - - return renderObservabilityLogsExplorer( - coreStart, - pluginsStart, - ownPluginStart, - appMountParams - ); - }, - }); - - // ensure the tabs are shown when in the observability nav mode - discover.configureInlineTopNav('oblt', { - enabled: true, - showLogsExplorerTabs: true, - }); - - // App used solely to redirect from "/app/observability-log-explorer" to "/app/observability-logs-explorer" - core.application.register({ - id: 'observability-log-explorer', - title: logsExplorerAppTitle, visibleIn: [], - mount: async (appMountParams: AppMountParameters) => { + mount: async (appMountParams: ObservabilityLogsExplorerAppMountParameters) => { const [coreStart] = await core.getStartServices(); - const { renderObservabilityLogsExplorerRedirect } = await import( - './applications/redirect_to_observability_logs_explorer' - ); - - return renderObservabilityLogsExplorerRedirect(coreStart, appMountParams); + const { renderDiscoverRedirect } = await import('./applications/redirect_to_discover'); + return renderDiscoverRedirect(coreStart, appMountParams); }, }); - // App used solely to redirect to either "/app/observability-logs-explorer" or "/app/discover" - // based on the last used app value in localStorage + // App used solely to redirect from "/app/observability-log-explorer" to "/app/discover" core.application.register({ - id: 'last-used-logs-viewer', + id: 'observability-log-explorer', title: logsExplorerAppTitle, visibleIn: [], mount: async (appMountParams: AppMountParameters) => { const [coreStart] = await core.getStartServices(); - const { renderLastUsedLogsViewerRedirect } = await import( - './applications/last_used_logs_viewer' - ); - - return renderLastUsedLogsViewerRedirect(coreStart, appMountParams); + const { renderDiscoverRedirect } = await import('./applications/redirect_to_discover'); + return renderDiscoverRedirect(coreStart, appMountParams); }, }); core.analytics.registerEventType(DATA_RECEIVED_TELEMETRY_EVENT); - // Register Locators - const allDatasetsLocator = share.url.locators.create( - new AllDatasetsLocatorDefinition({ - useHash, - }) - ); - - const dataViewLocator = share.url.locators.create( - new DataViewLocatorDefinition({ - useHash, - }) - ); - const singleDatasetLocator = share.url.locators.create( - new SingleDatasetLocatorDefinition({ - useHash, - }) - ); - - this.locators = { - allDatasetsLocator, - dataViewLocator, - singleDatasetLocator, - }; - - return { - locators: this.locators, - }; + return {}; } public start(core: CoreStart, _pluginsStart: ObservabilityLogsExplorerStartDeps) { - const { discover, fleet, logs } = core.application.capabilities; - - if (!(discover?.show && fleet?.read && logs?.show)) { - this.appStateUpdater.next(() => ({ - status: AppStatus.inaccessible, - visibleIn: [], - })); - } - return {}; } } diff --git a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/types.ts b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/types.ts index 7106395d47b27..93c7c36b3ad5b 100644 --- a/x-pack/solutions/observability/plugins/observability_logs_explorer/public/types.ts +++ b/x-pack/solutions/observability/plugins/observability_logs_explorer/public/types.ts @@ -27,14 +27,10 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { LensPublicStart } from '@kbn/lens-plugin/public'; import { SLOPublicStart } from '@kbn/slo-plugin/public'; import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; -import { - ObservabilityLogsExplorerLocators, - ObservabilityLogsExplorerLocationState, -} from '../common/locators'; +import type { ObservabilityLogsExplorerLocationState } from '@kbn/deeplinks-observability/locators'; -export interface ObservabilityLogsExplorerPluginSetup { - locators: ObservabilityLogsExplorerLocators; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ObservabilityLogsExplorerPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityLogsExplorerPluginStart {} diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts b/x-pack/solutions/observability/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts index b4707350948f0..3b9248dc3c9e0 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts +++ b/x-pack/solutions/observability/plugins/observability_onboarding/e2e/cypress/e2e/logs/custom_logs/install_elastic_agent.cy.ts @@ -488,10 +488,7 @@ describe('[Logs onboarding] Custom logs - install elastic agent', () => { cy.wait('@checkOnboardingProgress'); cy.getByTestSubj('obltOnboardingExploreLogs').should('exist').click(); - cy.url().should('include', '/app/observability-logs-explorer'); - cy.get('[data-test-subj="dataSourceSelectorPopoverButton"]') - .contains('[Mylogs] mylogs', { matchCase: false }) - .should('exist'); + cy.url().should('include', '/app/discover'); }); }); }); diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc b/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc index b1c3856839f99..361451b1ce009 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc +++ b/x-pack/solutions/observability/plugins/observability_onboarding/kibana.jsonc @@ -1,7 +1,9 @@ { "type": "plugin", "id": "@kbn/observability-onboarding-plugin", - "owner": ["@elastic/obs-ux-logs-team"], + "owner": [ + "@elastic/obs-ux-logs-team" + ], "group": "observability", "visibility": "private", "plugin": { @@ -16,6 +18,7 @@ "data", "observability", "observabilityShared", + "logsShared", "discover", "share", "fleet", diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/auto_detect/auto_detect_panel.tsx b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/auto_detect/auto_detect_panel.tsx index 2891d4c47834d..420a788bd5f2a 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/auto_detect/auto_detect_panel.tsx +++ b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/auto_detect/auto_detect_panel.tsx @@ -14,23 +14,20 @@ import { EuiSpacer, EuiSkeletonText, EuiText, + EuiButtonEmpty, useGeneratedHtmlId, EuiIcon, } from '@elastic/eui'; -import { - type SingleDatasetLocatorParams, - SINGLE_DATASET_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; import { ASSET_DETAILS_LOCATOR_ID } from '@kbn/observability-shared-plugin/common'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { getAutoDetectCommand } from './get_auto_detect_command'; import { DASHBOARDS, useOnboardingFlow } from './use_onboarding_flow'; import { ProgressIndicator } from '../shared/progress_indicator'; import { AccordionWithIcon } from '../shared/accordion_with_icon'; import { EmptyPrompt } from '../shared/empty_prompt'; import { CopyToClipboardButton } from '../shared/copy_to_clipboard_button'; -import { LocatorButtonEmpty } from '../shared/locator_button_empty'; import { GetStartedPanel } from '../shared/get_started_panel'; import { isSupportedLogo, LogoIcon } from '../../shared/logo_icon'; import { FeedbackButtons } from '../shared/feedback_buttons'; @@ -66,6 +63,7 @@ export const AutoDetectPanel: FunctionComponent = () => { const customIntegrations = installedIntegrations.filter( (integration) => integration.installSource === 'custom' ); + const logsLocator = share.url.locators.get(LOGS_LOCATOR_ID); const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR); const assetDetailsLocator = share.url.locators.get(ASSET_DETAILS_LOCATOR_ID); @@ -295,12 +293,15 @@ export const AutoDetectPanel: FunctionComponent = () => { {customIntegrations.map((integration) => integration.dataStreams.map((datastream) => (
  • - - locator={SINGLE_DATASET_LOCATOR_ID} - params={{ - integration: integration.pkgName, - dataset: datastream.dataset, - }} + { size="s" > {integration.pkgName} - +
  • )) )} diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx index 004fe70efb02c..e297bd2076536 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx +++ b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/custom_logs/install_elastic_agent.tsx @@ -9,10 +9,7 @@ import { EuiButton, EuiCallOut, EuiHorizontalRule, EuiSpacer, EuiText } from '@e import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { default as React, useCallback, useEffect, useState } from 'react'; -import { - SingleDatasetLocatorParams, - SINGLE_DATASET_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { ObservabilityOnboardingPluginSetupDeps } from '../../../plugin'; import { useWizard } from '.'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; @@ -38,8 +35,7 @@ export function InstallElasticAgent() { services: { share }, } = useKibana(); - const singleDatasetLocator = - share.url.locators.get(SINGLE_DATASET_LOCATOR_ID); + const logsLocator = share.url.locators.get(LOGS_LOCATOR_ID); const { getState, setState } = useWizard(); const wizardState = getState(); @@ -52,10 +48,11 @@ export function InstallElasticAgent() { (integration === dataset ? dataset : `${integration}.${dataset}`) ?? defaultDatasetName; async function onContinue() { - await singleDatasetLocator!.navigate({ - integration, - dataset: enforcedDatasetName, - origin: { id: 'application-log-onboarding' }, + await logsLocator!.navigate({ + dataViewSpec: { + title: `logs-${enforcedDatasetName}-*`, + timeFieldName: '@timestamp', + }, }); } diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts index 13273475c7697..11945572c7b4a 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts +++ b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/firehose/use_aws_service_get_started_list.ts @@ -7,10 +7,9 @@ import { useCallback, useMemo } from 'react'; import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics'; -import { SINGLE_DATASET_LOCATOR_ID } from '@kbn/deeplinks-observability'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { DISCOVER_APP_LOCATOR } from '@kbn/discover-plugin/common'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { AWSIndexName } from '../../../../common/aws_firehose'; import { ObservabilityOnboardingContextValue } from '../../../plugin'; @@ -33,8 +32,7 @@ export function useAWSServiceGetStartedList(): AWSServiceGetStartedConfig[] { services: { share }, } = useKibana(); const dashboardLocator = share.url.locators.get(DASHBOARD_APP_LOCATOR); - const singleDatasetLocator = share.url.locators.get(SINGLE_DATASET_LOCATOR_ID); - const discoverLocator = share.url.locators.get(DISCOVER_APP_LOCATOR); + const logsLocator = share.url.locators.get(LOGS_LOCATOR_ID); const generateMetricsDashboardActionLink = useCallback( (dashboardId: string, name?: string) => ({ @@ -88,12 +86,14 @@ export function useAWSServiceGetStartedList(): AWSServiceGetStartedConfig[] { defaultMessage: 'Explore', }), href: - singleDatasetLocator?.getRedirectUrl({ - integration: 'AWS', - dataset, + logsLocator?.getRedirectUrl({ + dataViewSpec: { + title: `logs-aws.${dataset}-*`, + timeFieldName: '@timestamp', + }, }) ?? '', }), - [singleDatasetLocator] + [logsLocator] ); const generateMetricsDiscoverActionLink = useCallback( @@ -107,7 +107,7 @@ export function useAWSServiceGetStartedList(): AWSServiceGetStartedConfig[] { defaultMessage: 'Explore', }), href: - discoverLocator?.getRedirectUrl({ + logsLocator?.getRedirectUrl({ dataViewId: `metrics-*`, query: { query: `aws.cloudwatch.namespace: ${namespace}`, @@ -115,7 +115,7 @@ export function useAWSServiceGetStartedList(): AWSServiceGetStartedConfig[] { }, }) ?? '', }), - [discoverLocator] + [logsLocator] ); return useMemo( diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx index 156f980de58df..522a59b5e12f4 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx +++ b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/otel_logs/index.tsx @@ -21,14 +21,11 @@ import { EuiImage, EuiCallOut, } from '@elastic/eui'; -import { - AllDatasetsLocatorParams, - ALL_DATASETS_LOCATOR_ID, -} from '@kbn/deeplinks-observability/locators'; import { i18n } from '@kbn/i18n'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import useAsyncFn from 'react-use/lib/useAsyncFn'; import { FormattedMessage } from '@kbn/i18n-react'; +import { type LogsLocatorParams, LOGS_LOCATOR_ID } from '@kbn/logs-shared-plugin/common'; import { ObservabilityOnboardingAppServices } from '../../..'; import { useFetcher } from '../../../hooks/use_fetcher'; import { MultiIntegrationInstallBanner } from './multi_integration_install_banner'; @@ -73,19 +70,15 @@ export const OtelLogsPanel: React.FC = () => { isServerless && setup ? setup.elasticAgentVersionInfo.agentVersion : stackVersion; const urlEncodedAgentVersion = encodeURIComponent(agentVersion); - const allDatasetsLocator = - share.url.locators.get(ALL_DATASETS_LOCATOR_ID); - + const logsLocator = share.url.locators.get(LOGS_LOCATOR_ID); const hostsLocator = share.url.locators.get('HOSTS_LOCATOR'); const [{ value: deeplinks }, getDeeplinks] = useAsyncFn(async () => { return { - logs: allDatasetsLocator?.getRedirectUrl({ - type: 'logs', - }), + logs: logsLocator?.getRedirectUrl({}), metrics: hostsLocator?.getRedirectUrl({}), }; - }, [allDatasetsLocator]); + }, [logsLocator]); useEffect(() => { getDeeplinks(); @@ -280,7 +273,7 @@ rm ./otel.yml && cp ./otel_samples/platformlogs_hostmetrics.yml ./otel.yml && mk {i18n.translate( 'xpack.observability_onboarding.otelLogsPanel.exploreLogs', { - defaultMessage: 'Open Logs Explorer', + defaultMessage: 'Explore logs', } )} diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/shared/locator_button_empty.tsx b/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/shared/locator_button_empty.tsx deleted file mode 100644 index 73fe406c46e6e..0000000000000 --- a/x-pack/solutions/observability/plugins/observability_onboarding/public/application/quickstart_flows/shared/locator_button_empty.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { type AnchorHTMLAttributes } from 'react'; -import { EuiButtonEmpty, type EuiButtonEmptyProps } from '@elastic/eui'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import type { SerializableRecord } from '@kbn/utility-types'; -import { type LocatorPublic } from '@kbn/share-plugin/common'; -import { type ObservabilityOnboardingContextValue } from '../../../plugin'; - -type EuiButtonEmptyPropsForAnchor = Extract< - EuiButtonEmptyProps, - AnchorHTMLAttributes ->; - -export interface LocatorButtonEmptyProps - extends Omit { - locator: string | LocatorPublic; - params: Params; -} - -/** - * Same as `EuiButtonEmpty` but uses locators to navigate instead of URLs. - * - * Accepts the following props instead of an `href`: - * - `locator`: Either the URL locator public contract or the ID of the locator if previously registered. - * - `params`: The params to pass to the locator. - * - * Get type safety for `params` by passing the correct type to the generic component. - * - * Example 1: - * - * ```ts - * - * View dashboard - * - * ``` - * - * Example 2: - * - * ```ts - * import { type SingleDatasetLocatorParams, SINGLE_DATASET_LOCATOR_ID } from '@kbn/deeplinks-observability/locators'; - * - * - * locator={SINGLE_DATASET_LOCATOR_ID} - * params={{ - * integration: 'system', - * dataset: 'system.syslog', - * }} - * > - * View in Logs Explorer - * - * ``` - */ -export const LocatorButtonEmpty = ({ - locator, - params, - ...rest -}: LocatorButtonEmptyProps) => { - const { - services: { share }, - } = useKibana(); - - const locatorObj = - typeof locator === 'string' ? share.url.locators.get(locator) : locator; - - return ( - - ); -}; diff --git a/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json b/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json index 23526b5fdcb56..391f1d6fee64f 100644 --- a/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability_onboarding/tsconfig.json @@ -37,13 +37,13 @@ "@kbn/react-kibana-context-render", "@kbn/react-kibana-context-theme", "@kbn/discover-plugin", - "@kbn/utility-types", "@kbn/spaces-plugin", "@kbn/deeplinks-analytics", "@kbn/custom-integrations-plugin", "@kbn/server-route-repository-utils", "@kbn/core-application-browser", - "@kbn/core-plugins-server" + "@kbn/core-plugins-server", + "@kbn/logs-shared-plugin" ], "exclude": [ "target/**/*" diff --git a/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts b/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts index e6fb3c9107306..803349493b1d6 100644 --- a/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts +++ b/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.ts @@ -29,20 +29,7 @@ export const createNavigationTree = ({ title: i18n.translate('xpack.serverlessObservability.nav.discover', { defaultMessage: 'Discover', }), - link: 'last-used-logs-viewer', - // avoid duplicate "Discover" breadcrumbs - breadcrumbStatus: 'hidden', - renderAs: 'item', - children: [ - { - link: 'discover', - children: [ - { - link: 'observability-logs-explorer', - }, - ], - }, - ], + link: 'discover', }, { title: i18n.translate('xpack.serverlessObservability.nav.dashboards', { diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts index f8f4a8a7df66b..a7aa6a53c3731 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_fired.ts @@ -276,9 +276,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { expect(viewInAppUrlPathName).contain(`/s/${SPACE_ID}/app/r`); - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); + expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('DISCOVER_APP_LOCATOR'); expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_ID, + dataViewId: DATA_VIEW_ID, + dataViewSpec: DATA_VIEW_ID, timeRange: { to: 'now' }, query: { query: '', language: 'kuery' }, filters: [], diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts index f439bb2fedca9..1b9f71581c9c0 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_pct_no_data.ts @@ -232,9 +232,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search ); - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); + expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('DISCOVER_APP_LOCATOR'); expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_ID, + dataViewId: DATA_VIEW_ID, + dataViewSpec: DATA_VIEW_ID, timeRange: { to: 'now' }, query: { query: '', language: 'kuery' }, filters: [], diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_ticks_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_ticks_fired.ts index 852363b0d8593..9a28d5681ed3f 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_ticks_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/avg_ticks_fired.ts @@ -274,9 +274,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { .pathname; expect(viewInAppUrlPathName).contain(`/s/${SPACE_ID}/app/r`); - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); + expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('DISCOVER_APP_LOCATOR'); expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_ID, + dataViewId: DATA_VIEW_ID, + dataViewSpec: DATA_VIEW_ID, timeRange: { to: 'now' }, query: { query: '', language: 'kuery' }, filters: [], diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts index 97451b1930afc..0e2f95bd92b12 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/documents_count_fired.ts @@ -263,9 +263,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search ); - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); + expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('DISCOVER_APP_LOCATOR'); expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_ID, + dataViewId: DATA_VIEW_ID, + dataViewSpec: DATA_VIEW_ID, timeRange: { to: 'now' }, query: { query: 'host.name:* and container.id:*', language: 'kuery' }, filters: [], diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts index 5d6cfb49f86f0..1e1a26e8abefa 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/alerting/custom_threshold/p99_pct_fired.ts @@ -258,9 +258,20 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { new URL(resp.hits.hits[0]._source?.viewInAppUrl || '').search ); - expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('LOGS_EXPLORER_LOCATOR'); + expect(resp.hits.hits[0]._source?.viewInAppUrl).contain('DISCOVER_APP_LOCATOR'); expect(omit(parsedViewInAppUrl.params, 'timeRange.from')).eql({ - dataset: DATA_VIEW_TITLE, + dataViewId: 'kbn-data-forge-fake_hosts.fake_hosts-*', + dataViewSpec: { + allowHidden: false, + allowNoIndex: false, + fieldFormats: {}, + id: DATA_VIEW_ID, + name: 'ad-hoc-data-view-name', + runtimeFieldMap: {}, + sourceFilters: [], + timeFieldName: '@timestamp', + title: 'kbn-data-forge-fake_hosts.fake_hosts-*', + }, timeRange: { to: 'now' }, query: { query: '', language: 'kuery' }, filters: [], diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_details.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_details.ts index 28a7cc9a9963d..47c246bc0f7a8 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_details.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_details.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import originalExpect from 'expect'; import { DatasetQualityFtrProviderContext } from './config'; import { createDegradedFieldsRecord, @@ -25,6 +26,7 @@ const integrationActions = { export default function ({ getService, getPageObjects }: DatasetQualityFtrProviderContext) { const PageObjects = getPageObjects([ 'common', + 'discover', 'navigationalSearch', 'observabilityLogsExplorer', 'datasetQuality', @@ -327,9 +329,8 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid await logExplorerButton.click(); // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.eql(regularDatasetName); + const datasetSelectorText = await PageObjects.discover.getCurrentDataViewId(); + originalExpect(datasetSelectorText).toMatch(regularDatasetName); }); it('should go log explorer for degraded docs when the button next to breakdown selector is clicked', async () => { @@ -342,9 +343,8 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid ); // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.contain(apacheAccessDatasetName); + const datasetSelectorText = await PageObjects.discover.getCurrentDataViewId(); + originalExpect(datasetSelectorText).toMatch(apacheAccessDatasetName); }); }); diff --git a/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts b/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts index fb6c6ed9b519f..07c201316ae0b 100644 --- a/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts +++ b/x-pack/test/functional/apps/dataset_quality/dataset_quality_table.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import originalExpect from 'expect'; import { DatasetQualityFtrProviderContext } from './config'; import { datasetNames, @@ -19,6 +20,7 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid const PageObjects = getPageObjects([ 'common', 'navigationalSearch', + 'discover', 'observabilityLogsExplorer', 'datasetQuality', ]); @@ -139,9 +141,9 @@ export default function ({ getService, getPageObjects }: DatasetQualityFtrProvid await (await actionsCol.getCellChildren('a'))[rowIndexToOpen].click(); // Click "Open" // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.eql(datasetName); + const datasetSelectorText = await PageObjects.discover.getCurrentDataViewId(); + + originalExpect(datasetSelectorText).toMatch(datasetName); // Return to Dataset Quality Page await PageObjects.datasetQuality.navigateTo(); diff --git a/x-pack/test/functional/apps/infra/logs/link_to.ts b/x-pack/test/functional/apps/infra/logs/link_to.ts index 120eae8d85790..b6c3a8de6b14b 100644 --- a/x-pack/test/functional/apps/infra/logs/link_to.ts +++ b/x-pack/test/functional/apps/infra/logs/link_to.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from 'expect'; import { URL } from 'url'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -47,13 +47,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); - expect(parsedUrl.pathname).to.be('/app/observability-logs-explorer/'); - expect(parsedUrl.searchParams.get('pageState')).to.contain( - `query:(language:kuery,query:\'trace.id:${traceId}')` - ); - expect(parsedUrl.searchParams.get('pageState')).to.contain( - `time:(from:'${startDate}',to:'${endDate}')` - ); + expect(parsedUrl.pathname).toEqual('/app/discover'); + expect(parsedUrl.hash).toMatch(`query:(language:kuery,query:%27trace.id:${traceId}%27))`); + expect(parsedUrl.hash).toMatch(`time:(from:%27${startDate}%27,to:%27${endDate}%27)`); }); }); }); @@ -79,13 +75,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const currentUrl = await browser.getCurrentUrl(); const parsedUrl = new URL(currentUrl); - expect(parsedUrl.pathname).to.be('/app/observability-logs-explorer/'); - expect(parsedUrl.searchParams.get('pageState')).to.contain( - `query:(language:kuery,query:\'(kubernetes.pod.uid: 1234) and (trace.id:${traceId})\')` - ); - expect(parsedUrl.searchParams.get('pageState')).to.contain( - `time:(from:'${startDate}',to:'${endDate}')` + expect(parsedUrl.pathname).toEqual('/app/discover'); + expect(parsedUrl.hash).toMatch( + `query:(language:kuery,query:%27(kubernetes.pod.uid:%20${nodeId})%20and%20(trace.id:${traceId})%27))` ); + expect(parsedUrl.hash).toMatch(`time:(from:%27${startDate}%27,to:%27${endDate}%27)`); }); }); }); diff --git a/x-pack/test/functional/apps/observability_logs_explorer/app.ts b/x-pack/test/functional/apps/observability_logs_explorer/app.ts deleted file mode 100644 index 87da77752e58d..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/app.ts +++ /dev/null @@ -1,58 +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 expect from '@kbn/expect'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from './config'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'navigationalSearch', 'observabilityLogsExplorer']); - const testSubjects = getService('testSubjects'); - const synthtrace = getService('logSynthtraceEsClient'); - const dataGrid = getService('dataGrid'); - - describe('Application', () => { - it('is shown in the global search', async () => { - await PageObjects.common.navigateToApp('home'); - await PageObjects.navigationalSearch.searchFor('logs explorer'); - - const results = await PageObjects.navigationalSearch.getDisplayedResults(); - expect(results[0].label).to.eql('Logs Explorer'); - }); - - it('is shown in the observability side navigation', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - await testSubjects.existOrFail('observability-nav-observability-logs-explorer-explorer'); - }); - - it('should load logs', async () => { - const from = '2023-08-03T10:24:14.035Z'; - const to = '2023-08-03T10:24:14.091Z'; - const COUNT = 5; - await synthtrace.index(generateLogsData({ from, to, count: COUNT })); - await PageObjects.observabilityLogsExplorer.navigateTo(); - const docCount = await dataGrid.getDocCount(); - - expect(docCount).to.be(COUNT); - await synthtrace.clean(); - }); - }); -} - -function generateLogsData({ from, to, count = 1 }: { from: string; to: string; count: number }) { - const range = timerange(from, to); - - return range - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().message('A sample log').timestamp(timestamp); - }) - ); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts b/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts deleted file mode 100644 index 295c06682af30..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/columns_selection.ts +++ /dev/null @@ -1,344 +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 expect from '@kbn/expect'; -import moment from 'moment/moment'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from './config'; - -const defaultLogColumns = ['@timestamp', 'Summary']; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer']); - const synthtrace = getService('logSynthtraceEsClient'); - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const from = '2023-12-27T10:24:14.035Z'; - const to = '2023-12-27T10:25:14.091Z'; - const TEST_TIMEOUT = 10 * 1000; // 10 secs - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe('When the logs explorer loads', () => { - before(async () => { - await synthtrace.index(generateLogsData({ to })); - await navigateToLogsExplorer(); - }); - - after(async () => { - await synthtrace.clean(); - }); - - describe('columns selection initialization and update', () => { - it("should initialize the table columns to logs' default selection", async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - expect(await PageObjects.discover.getColumnHeaders()).to.eql(defaultLogColumns); - }); - }); - - it('should restore the table columns from the URL state if exists', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - columns: [{ field: 'data_stream.namespace', type: 'document-field' }], - }, - }); - - await retry.tryForTime(TEST_TIMEOUT, async () => { - expect(await PageObjects.discover.getColumnHeaders()).to.eql([ - '@timestamp', - 'data_stream.namespace', - ]); - }); - }); - }); - - describe.skip('render content virtual column properly', () => { - it('should render log level and log message when present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - expect(cellValue.includes('A sample log')).to.be(true); - }); - }); - - it('should render log message when present and skip log level when missing', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(false); - expect(cellValue.includes('A sample log')).to.be(true); - }); - }); - - it('should render message from error object when top level message not present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - expect(cellValue.includes('error.message')).to.be(true); - expect(cellValue.includes('message in error object')).to.be(true); - }); - }); - - it('should render message from event.original when top level message and error.message not present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - expect(cellValue.includes('event.original')).to.be(true); - expect(cellValue.includes('message in event original')).to.be(true); - }); - }); - - it('should render the whole JSON when neither message, error.message and event.original are present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - - expect(cellValue.includes('error.message')).to.be(false); - expect(cellValue.includes('event.original')).to.be(false); - - const cellAttribute = await cellElement.findByTestSubject('discoverCellDescriptionList'); - expect(cellAttribute).not.to.be.empty(); - }); - }); - - it('on cell expansion with no message field should open JSON Viewer', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); - await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); - }); - }); - - it('on cell expansion with message field should open regular popover', async () => { - await navigateToLogsExplorer(); - await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); - await testSubjects.existOrFail('euiDataGridExpansionPopover'); - }); - }); - }); - - describe.skip('render resource virtual column properly', () => { - it('should render service name and host name when present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('synth-service')).to.be(true); - expect(cellValue.includes('synth-host')).to.be(true); - }); - }); - }); - - describe.skip('virtual column cell actions', () => { - beforeEach(async () => { - await navigateToLogsExplorer(); - }); - it('should render a popover with cell actions when a chip on content column is clicked', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - await logLevelChip.click(); - // Check Filter In button is present - await testSubjects.existOrFail('dataTableCellAction_addToFilterAction_log.level'); - // Check Filter Out button is present - await testSubjects.existOrFail('dataTableCellAction_removeFromFilterAction_log.level'); - // Check Copy button is present - await testSubjects.existOrFail('dataTableCellAction_copyToClipboardAction_log.level'); - }); - }); - - it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - - const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; - // Open popover if not already open - if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { - await logLevelChip.click(); - } - - // Find Filter In button - const filterInButton = await testSubjects.find(actionSelector); - - await filterInButton.click(); - const rowWithLogLevelInfo = await testSubjects.findAll('*logLevelBadge-'); - - expect(rowWithLogLevelInfo.length).to.be(4); - }); - }); - - it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - - const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; - // Open popover if not already open - if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { - await logLevelChip.click(); - } - - // Find Filter Out button - const filterOutButton = await testSubjects.find(actionSelector); - - await filterOutButton.click(); - await testSubjects.missingOrFail('*logLevelBadge-'); - }); - }); - - it('should render the table filtered where service.name value is selected', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); - const serviceNameChip = await cellElement.findByTestSubject( - 'dataTableCellActionsPopover_service.name' - ); - - const actionSelector = 'dataTableCellAction_addToFilterAction_service.name'; - // Open popover if not already open - if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { - await serviceNameChip.click(); - } - - // Find Filter In button - const filterInButton = await testSubjects.find(actionSelector); - - await filterInButton.click(); - const rowWithLogLevelInfo = await testSubjects.findAll( - 'dataTableCellActionsPopover_service.name' - ); - - expect(rowWithLogLevelInfo.length).to.be(2); - }); - }); - }); - }); -} - -function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { - const logs = timerange(moment(to).subtract(1, 'second'), moment(to)) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsWithNoLogLevel = timerange( - moment(to).subtract(2, 'second'), - moment(to).subtract(1, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsWithErrorMessage = timerange( - moment(to).subtract(3, 'second'), - moment(to).subtract(2, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.message': 'message in error object', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithEventOriginal = timerange( - moment(to).subtract(4, 'second'), - moment(to).subtract(3, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'event.original': 'message in event original', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithNoMessage = timerange( - moment(to).subtract(5, 'second'), - moment(to).subtract(4, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp); - }) - ); - - const logWithNoMessageNoLogLevel = timerange( - moment(to).subtract(6, 'second'), - moment(to).subtract(5, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().timestamp(timestamp); - }) - ); - - return [ - logs, - logsWithNoLogLevel, - logsWithErrorMessage, - logsWithEventOriginal, - logsWithNoMessage, - logWithNoMessageNoLogLevel, - ]; -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/common/package_registry_config.yml b/x-pack/test/functional/apps/observability_logs_explorer/common/package_registry_config.yml deleted file mode 100644 index 1885fa5c2ebe5..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/common/package_registry_config.yml +++ /dev/null @@ -1,2 +0,0 @@ -package_paths: - - /packages/package-storage diff --git a/x-pack/test/functional/apps/observability_logs_explorer/config.ts b/x-pack/test/functional/apps/observability_logs_explorer/config.ts deleted file mode 100644 index 74c54a9c9e953..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/config.ts +++ /dev/null @@ -1,90 +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 { - FtrConfigProviderContext, - GenericFtrProviderContext, - defineDockerServersConfig, -} from '@kbn/test'; -import { createLogger, LogLevel, LogsSynthtraceEsClient } from '@kbn/apm-synthtrace'; -import path from 'path'; -import { dockerImage } from '../../../fleet_api_integration/config.base'; -import { FtrProviderContext as InheritedFtrProviderContext } from '../../ftr_provider_context'; - -export type InheritedServices = InheritedFtrProviderContext extends GenericFtrProviderContext< - infer TServices, - {} -> - ? TServices - : {}; - -export type InheritedPageObjects = InheritedFtrProviderContext extends GenericFtrProviderContext< - infer TServices, - infer TPageObjects -> - ? TPageObjects - : {}; - -interface ObsLogExplorerConfig { - services: InheritedServices & { - logSynthtraceEsClient: ( - context: InheritedFtrProviderContext - ) => Promise; - }; -} - -export default async function createTestConfig({ - readConfigFile, -}: FtrConfigProviderContext): Promise { - const functionalConfig = await readConfigFile(require.resolve('../../config.base.js')); - const services = functionalConfig.get('services'); - - const packageRegistryConfig = path.join(__dirname, './common/package_registry_config.yml'); - const dockerArgs: string[] = ['-v', `${packageRegistryConfig}:/package-registry/config.yml`]; - - /** - * This is used by CI to set the docker registry port - * you can also define this environment variable locally when running tests which - * will spin up a local docker package registry locally for you - * if this is defined it takes precedence over the `packageRegistryOverride` variable - */ - const dockerRegistryPort: string | undefined = process.env.FLEET_PACKAGE_REGISTRY_PORT; - - return { - ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], - dockerServers: defineDockerServersConfig({ - registry: { - enabled: !!dockerRegistryPort, - image: dockerImage, - portInContainer: 8080, - port: dockerRegistryPort, - args: dockerArgs, - waitForLogLine: 'package manifests loaded', - waitForLogLineTimeoutMs: 60 * 2 * 1000, // 2 minutes - }, - }), - services: { - ...services, - logSynthtraceEsClient: (context: InheritedFtrProviderContext) => { - return new LogsSynthtraceEsClient({ - client: context.getService('es'), - logger: createLogger(LogLevel.info), - refreshAfterIndex: true, - }); - }, - }, - }; -} - -export type ObsLogsExplorerServices = ObsLogExplorerConfig['services']; -export type ObsLogsExplorerPageObject = InheritedPageObjects; - -export type FtrProviderContext = GenericFtrProviderContext< - ObsLogsExplorerServices, - ObsLogsExplorerPageObject ->; diff --git a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts b/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts deleted file mode 100644 index 1ce3a9aa7b9df..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/custom_control_columns.ts +++ /dev/null @@ -1,186 +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 { log, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import moment from 'moment'; -import { FtrProviderContext } from './config'; - -const MORE_THAN_1024_CHARS = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer']); - const synthtrace = getService('logSynthtraceEsClient'); - const dataGrid = getService('dataGrid'); - const from = '2024-02-06T10:24:14.035Z'; - const to = '2024-02-06T10:25:14.091Z'; - const TEST_TIMEOUT = 10 * 1000; // 10 secs - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe('When the logs explorer loads', () => { - before(async () => { - await synthtrace.index(generateLogsData({ to })); - await navigateToLogsExplorer(); - }); - - after(async () => { - await synthtrace.clean(); - }); - - describe('should render custom control columns properly', () => { - it('should render control column with proper header', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); - }); - }); - - it('should render the expand icon in the leading control column', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); - const expandButton = await cellElement.findByTestSubject('docTableExpandToggleColumn'); - expect(expandButton).to.not.be.empty(); - }); - }); - - it('should render the degraded icon in the leading control column if degraded doc exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 2); - const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); - expect(degradedButton).to.not.be.empty(); - }); - }); - - it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 2); - const degradedDisableButton = await cellElement.findByTestSubject( - 'docTableDegradedDocDoesNotExist' - ); - expect(degradedDisableButton).to.not.be.empty(); - }); - }); - - it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 3); - const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); - expect(stacktraceButton).to.not.be.empty(); - }); - }); - - it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 3); - const stacktraceButton = await cellElement.findByTestSubject( - 'docTableStacktraceDoesNotExist' - ); - expect(stacktraceButton).to.not.be.empty(); - }); - }); - }); - }); -} - -function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { - const logs = timerange(moment(to).subtract(1, 'second'), moment(to)) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const degradedDocs = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A degraded doc') - .logLevel(MORE_THAN_1024_CHARS) - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsWithErrorMessage = timerange( - moment(to).subtract(3, 'second'), - moment(to).subtract(2, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.stack_trace': 'Error message in error.stack_trace', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithErrorException = timerange( - moment(to).subtract(4, 'second'), - moment(to).subtract(3, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.exception.stacktrace': 'Error message in error.exception.stacktrace', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithErrorInLog = timerange( - moment(to).subtract(5, 'second'), - moment(to).subtract(4, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.log.stacktrace': 'Error message in error.log.stacktrace', - 'service.name': 'node-service', - }); - }) - ); - - return [logs, degradedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/data_source_selection_state.ts b/x-pack/test/functional/apps/observability_logs_explorer/data_source_selection_state.ts deleted file mode 100644 index cd84fd514798c..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/data_source_selection_state.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import expect from '@kbn/expect'; -import { decodeOrThrow, indexPatternRt } from '@kbn/io-ts-utils'; -import { DataSourceSelectionPlain } from '@kbn/logs-explorer-plugin/common'; -import { FtrProviderContext } from './config'; - -const azureActivityDatasetSelection: DataSourceSelectionPlain = { - selection: { - dataset: { - name: decodeOrThrow(indexPatternRt)('logs-azure.activitylogs-*'), - title: 'activitylogs', - }, - name: 'azure', - title: 'Azure Logs', - version: '1.5.23', - }, - selectionType: 'single', -}; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'observabilityLogsExplorer']); - - describe('dataSourceSelection initialization and update', () => { - describe('when no dataSourceSelection is given', () => { - it('should initialize the "All logs" selection', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - const dataSourceSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - - expect(dataSourceSelectionTitle).to.be('All logs'); - }); - }); - - describe('when a dataSourceSelection is given', () => { - it('should restore the selection from a valid parameter', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - dataSourceSelection: azureActivityDatasetSelection, - }, - }); - - const dataSourceSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - - expect(dataSourceSelectionTitle).to.be('[Azure Logs] activitylogs'); - }); - - it('should fallback to the "All logs" selection and notify the user of an invalid parameter', async () => { - await PageObjects.observabilityLogsExplorer.navigateToWithUncheckedState({ - pageState: { - v: 2, - dataSourceSelection: { - selectionType: 'invalid', - }, - }, - }); - - const dataSourceSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - - await PageObjects.observabilityLogsExplorer.assertRestoreFailureToastExist(); - expect(dataSourceSelectionTitle).to.be('All logs'); - }); - }); - - describe('when navigating back and forth on the page history', () => { - it('should decode and restore the selection for the current index', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - const allDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(allDatasetSelectionTitle).to.be('All logs'); - - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - dataSourceSelection: azureActivityDatasetSelection, - }, - }); - const azureDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(azureDatasetSelectionTitle).to.be('[Azure Logs] activitylogs'); - - // Go back to previous page selection - await retry.tryForTime(30 * 1000, async () => { - await browser.goBack(); - const backNavigationDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(backNavigationDatasetSelectionTitle).to.be('All logs'); - }); - // Go forward to previous page selection - await retry.tryForTime(30 * 1000, async () => { - await browser.goForward(); - const forwardNavigationDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(forwardNavigationDatasetSelectionTitle).to.be('[Azure Logs] activitylogs'); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts b/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts deleted file mode 100644 index 3e8ad397e3c4c..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/data_source_selector.ts +++ /dev/null @@ -1,930 +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 expect from '@kbn/expect'; -import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; -import { FtrProviderContext } from './config'; - -const initialPackageMap = { - apache: 'Apache HTTP Server', - aws: 'AWS', - system: 'System', -}; -const initialPackagesTexts = Object.values(initialPackageMap); - -const expectedDataViews = ['logs-*', 'logstash-*', 'metrics-*']; -const sortedExpectedDataViews = expectedDataViews.slice().sort(); - -const uncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; -const expectedUncategorized = uncategorized.map((dataset) => dataset.split('-')[1]); - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const retry = getService('retry'); - const dataViews = getService('dataViews'); - const PageObjects = getPageObjects(['common', 'discover', 'observabilityLogsExplorer']); - - const noIntegrationsTitle = 'No integrations found'; - const noUncategorizedTitle = 'No data streams found'; - - describe('DataSourceSelector', () => { - before(async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); - }); - - after(async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - }); - - describe('as consistent behavior', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should always display the Integrations Uncategorized and Data Views top level tabs', async () => { - const integrationsTab = await PageObjects.observabilityLogsExplorer.getIntegrationsTab(); - const uncategorizedTab = await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - const dataViewsTab = await PageObjects.observabilityLogsExplorer.getDataViewsTab(); - - expect(await integrationsTab.isDisplayed()).to.be(true); - expect(await integrationsTab.getVisibleText()).to.be('Integrations'); - expect(await uncategorizedTab.isDisplayed()).to.be(true); - expect(await uncategorizedTab.getVisibleText()).to.be('Uncategorized'); - expect(await dataViewsTab.isDisplayed()).to.be(true); - expect(await dataViewsTab.getVisibleText()).to.be('Data Views'); - }); - - it('should always display the "Show all logs" action', async () => { - const allLogsButton = await PageObjects.observabilityLogsExplorer.getAllLogsButton(); - - const allLogsTitle = await allLogsButton.getVisibleText(); - - expect(allLogsTitle).to.be('Show all logs'); - }); - - describe('when open on the integrations tab', () => { - it('should display an error prompt if could not retrieve the integrations', async function () { - // Skip the test in case network condition utils are not available - try { - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - - await PageObjects.common.sleep(5000); - await browser.setNetworkConditions('OFFLINE'); - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('a'); - - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusErrorPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - - await browser.restoreNetworkConditions(); - } catch (error) { - this.skip(); - } - }); - - it('should display an empty prompt for no integrations', async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(0); - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - }); - - describe('when open on the uncategorized tab', () => { - it('should display a loading skeleton while loading uncategorized datasets', async function () { - // Skip the test in case network condition utils are not available - try { - await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions - const uncategorizedTab = - await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - await uncategorizedTab.click(); - - await PageObjects.observabilityLogsExplorer.assertLoadingSkeletonExists(); - - await browser.restoreNetworkConditions(); - } catch (error) { - this.skip(); - } - }); - - it('should display an error prompt if could not retrieve the datasets', async function () { - const uncategorizedTab = - await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - await uncategorizedTab.click(); - - // Skip the test in case network condition utils are not available - try { - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noUncategorizedTitle - ); - }); - - await PageObjects.common.sleep(5000); - await browser.setNetworkConditions('OFFLINE'); - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('a'); - - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusErrorPromptExistsWithTitle( - noUncategorizedTitle - ); - }); - - await browser.restoreNetworkConditions(); - } catch (error) { - this.skip(); - } - }); - - it('should display an empty prompt for no uncategorized data streams', async () => { - const uncategorizedTab = - await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - await uncategorizedTab.click(); - - const uncategorizedEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(uncategorizedEntries.length).to.be(0); - - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noUncategorizedTitle - ); - }); - }); - }); - - describe('with installed integrations and uncategorized data streams', () => { - let cleanupIntegrationsSetup: () => Promise; - - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - cleanupIntegrationsSetup = - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - await cleanupIntegrationsSetup(); - }); - - describe('when open on the integrations tab', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should display a list of installed integrations', async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - - expect(integrations.length).to.be(3); - expect(integrations).to.eql(initialPackagesTexts); - }); - - it('should sort the integrations list by the clicked sorting option', async () => { - // Test ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql(initialPackagesTexts); - }); - - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql(initialPackagesTexts.slice().reverse()); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql(initialPackagesTexts); - }); - }); - - it('should filter the integrations list by the typed integration name', async () => { - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('system'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.system]); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('a'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.apache, initialPackageMap.aws]); - }); - }); - - it('should display an empty prompt when the search does not match any result', async () => { - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('no result search text'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations.length).to.be(0); - }); - - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - - it('should load more integrations by scrolling to the end of the list', async () => { - // Install more integrations and reload the page - const cleanupAdditionalSetup = - await PageObjects.observabilityLogsExplorer.setupAdditionalIntegrations(); - await browser.refresh(); - - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - - // Initially fetched integrations - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(nodes.length).to.be(15); - await nodes.at(-1)?.scrollIntoView(); - }); - - // Load more integrations - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(nodes.length).to.be(20); - await nodes.at(-1)?.scrollIntoView(); - }); - - // No other integrations to load after scrolling to last integration - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(nodes.length).to.be(20); - }); - - await cleanupAdditionalSetup(); - }); - - describe('clicking on integration and moving into the second navigation level', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should display a list of available datasets', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const [panelTitleNode, integrationDatasetEntries] = - await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await integrationDatasetEntries[0].getVisibleText()).to.be('access'); - expect(await integrationDatasetEntries[1].getVisibleText()).to.be('error'); - }); - }); - - it('should sort the datasets list by the clicked sorting option', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); - - // Test ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('error'); - expect(await menuEntries[1].getVisibleText()).to.be('access'); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - }); - - it('should filter the datasets list by the typed dataset name', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('err'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - }); - }); - - it('should update the current selection with the clicked dataset', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - await menuEntries[0].click(); - }); - - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButton(); - - expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); - }); - }); - }); - }); - - describe('when open on the uncategorized tab', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer - .getUncategorizedTab() - .then((tab: WebElementWrapper) => tab.click()); - }); - - it('should display a list of available datasets', async () => { - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - }); - - it('should sort the datasets list by the clicked sorting option', async () => { - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - }); - - // Test ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[0]); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - }); - - it('should filter the datasets list by the typed dataset name', async () => { - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('retail'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('retail'); - }); - }); - - it('should update the current selection with the clicked dataset', async () => { - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - await menuEntries[0].click(); - }); - - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButton(); - - expect(await selectorButton.getVisibleText()).to.be(expectedUncategorized[0]); - }); - }); - }); - - describe('when open on the data views tab', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer - .getDataViewsTab() - .then((tab: WebElementWrapper) => tab.click()); - }); - - it('should display a list of available data views', async () => { - await retry.try(async () => { - const menu = await PageObjects.observabilityLogsExplorer.getDataViewsContextMenu(); - const menuEntries = await PageObjects.observabilityLogsExplorer.getPanelEntries(menu); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedDataViews[2]); - }); - }); - - it('should filter the list of data views by type', async () => { - // Test descending order - await PageObjects.observabilityLogsExplorer.changeDataViewTypeFilter('Logs'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(2); - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); - }); - - // Test back all filter - await PageObjects.observabilityLogsExplorer.changeDataViewTypeFilter('All'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]); - expect(await menuEntries[2].getVisibleText()).to.be(sortedExpectedDataViews[2]); - }); - }); - - it('should sort the data views list by the clicked sorting option', async () => { - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[2]); - expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]); - expect(await menuEntries[2].getVisibleText()).to.be(sortedExpectedDataViews[0]); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]); - expect(await menuEntries[2].getVisibleText()).to.be(sortedExpectedDataViews[2]); - }); - }); - - it('should filter the data views list by the typed data view name', async () => { - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedDataViews[2]); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('logs'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(2); - expect(await menuEntries[0].getVisibleText()).to.be('logs-*'); - expect(await menuEntries[1].getVisibleText()).to.be('logstash-*'); - }); - }); - - it('should load a data view allowed by the settings upon click', async () => { - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]); - await menuEntries[0].click(); - }); - - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/app/observability-logs-explorer`); - }); - - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButton(); - - expect(await selectorButton.getVisibleText()).to.be(expectedDataViews[0]); - }); - }); - - it('should navigate to Discover and load a data view not allowed by the settings upon click', async () => { - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[2].getVisibleText()).to.be(expectedDataViews[2]); - await menuEntries[2].click(); - }); - - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/app/discover`); - }); - - await dataViews.waitForSwitcherToBe(expectedDataViews[2]); - }); - }); - - describe('when open/close the selector', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should restore the latest navigation panel', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - await PageObjects.observabilityLogsExplorer.closeDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - }); - - it('should restore the latest search results', async () => { - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('system'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.system]); - }); - - await PageObjects.observabilityLogsExplorer.closeDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.system]); - }); - }); - }); - - describe('when switching between tabs or integration panels', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - it('should remember the latest search and restore its results', async () => { - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.clearSearchField(); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('apache'); - - await retry.try(async () => { - const { nodes, integrations } = - await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.apache]); - await nodes[0].click(); - }); - - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('err'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - }); - - // Navigate back to integrations - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelTitle(menu) - ); - await panelTitleNode.click(); - - await retry.try(async () => { - const { nodes, integrations } = - await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.apache]); - - const searchValue = await PageObjects.observabilityLogsExplorer.getSearchFieldValue(); - expect(searchValue).to.eql('apache'); - - await nodes[0].click(); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - const searchValue = await PageObjects.observabilityLogsExplorer.getSearchFieldValue(); - expect(searchValue).to.eql('err'); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/field_list.ts b/x-pack/test/functional/apps/observability_logs_explorer/field_list.ts deleted file mode 100644 index 977c951b2fd02..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/field_list.ts +++ /dev/null @@ -1,105 +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 expect from '@kbn/expect'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from './config'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['observabilityLogsExplorer', 'unifiedFieldList']); - const synthtrace = getService('logSynthtraceEsClient'); - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const from = '2024-02-23T10:24:14.035Z'; - const to = '2024-02-23T10:25:14.091Z'; - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe.skip('When virtual columns loads', () => { - before(async () => { - await synthtrace.index(generateLogsData({ from, to })); - await navigateToLogsExplorer(); - }); - - after(async () => { - await synthtrace.clean(); - }); - - describe('field list initialisation', () => { - it('should display the virtual columns in the field list', async () => { - // Smart Field group should be present - await testSubjects.existOrFail('fieldListGroupedSmartFields'); - // Resource field should be present - await testSubjects.existOrFail('dscFieldListPanelField-resource'); - // Content field should be present - await testSubjects.existOrFail('dscFieldListPanelField-content'); - }); - - it('should allow toggling of fields', async () => { - const defaultLogColumns = ['@timestamp', 'resource', 'content']; - const headerFields = await dataGrid.getHeaderFields(); - expect(headerFields).to.eql(defaultLogColumns); - - // After toggling from Field List, the resource column must disappear - // from the table - const toggledLogColumns = ['@timestamp', 'content']; - await PageObjects.unifiedFieldList.clickFieldListItemToggle('resource'); - const updatedHeaderFields = await dataGrid.getHeaderFields(); - expect(updatedHeaderFields).to.eql(toggledLogColumns); - // Field should be removed from Selected Fields - const selectedField = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField).to.be(false); - - // After toggling again from Field List, the resource column must appear - // in the table but after content column - const reorderedLogColumns = ['@timestamp', 'content', 'resource']; - await PageObjects.unifiedFieldList.clickFieldListItemToggle('resource'); - const updatedHeaderFields2 = await dataGrid.getHeaderFields(); - expect(updatedHeaderFields2).to.eql(reorderedLogColumns); - // Field should be added in Selected Fields - const selectedField2 = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField2).to.be(true); - }); - - it('should update selected field list when column is removed from column header', async () => { - const selectedField = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField).to.be(true); - - await dataGrid.clickRemoveColumn('resource'); - const selectedField2 = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField2).to.be(false); - }); - }); - }); -} - -function generateLogsData({ from, to, count = 1 }: { from: string; to: string; count?: number }) { - return timerange(from, to) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/filter_controls.ts b/x-pack/test/functional/apps/observability_logs_explorer/filter_controls.ts deleted file mode 100644 index bb2885f723dc0..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/filter_controls.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import { FtrProviderContext } from './config'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['observabilityLogsExplorer']); - const testSubjects = getService('testSubjects'); - - describe('Filter controls customization', () => { - before('initialize tests', async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - }); - - after('clean up archives', async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - }); - - it('renders a filter controls section as part of the unified search bar', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - await testSubjects.existOrFail('dataSourceFiltersCustomization', { allowHidden: true }); - }); - }); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/flyout.ts b/x-pack/test/functional/apps/observability_logs_explorer/flyout.ts deleted file mode 100644 index 2316822db3c5b..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/flyout.ts +++ /dev/null @@ -1,76 +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 { FtrProviderContext } from '../../ftr_provider_context'; - -const DATASET_NAME = 'flyout'; -const NAMESPACE = 'default'; -const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`; -const NOW = Date.now(); - -const sharedDoc = { - logFilepath: '/flyout.log', - serviceName: DATASET_NAME, - datasetName: DATASET_NAME, - namespace: NAMESPACE, -}; - -const docs = [ - { - ...sharedDoc, - time: NOW + 1000, - message: 'full document', - logLevel: 'info', - }, - { - ...sharedDoc, - time: NOW, - }, -]; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['observabilityLogsExplorer']); - - describe('Flyout content customization', () => { - let cleanupDataStreamSetup: () => Promise; - - before('initialize tests', async () => { - cleanupDataStreamSetup = await PageObjects.observabilityLogsExplorer.setupDataStream( - DATASET_NAME, - NAMESPACE - ); - await PageObjects.observabilityLogsExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); - }); - - beforeEach(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from: new Date(NOW - 60_000).toISOString(), - to: new Date(NOW + 60_000).toISOString(), - mode: 'absolute', - }, - }, - }); - }); - - after('clean up DataStream', async () => { - if (cleanupDataStreamSetup) { - await cleanupDataStreamSetup(); - } - }); - - it('should display the logs overview tab', async () => { - await retry.try(async () => { - await dataGrid.clickRowToggle(); - }); - await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); - }); - }); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/header_menu.ts b/x-pack/test/functional/apps/observability_logs_explorer/header_menu.ts deleted file mode 100644 index feb2df2b6e40e..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/header_menu.ts +++ /dev/null @@ -1,105 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from './config'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const dataViews = getService('dataViews'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer', 'timePicker']); - - describe('Header menu', () => { - before(async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await esArchiver.load( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - after(async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - await esArchiver.unload( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - }); - - it('should inject the app header menu on the top navbar', async () => { - const headerMenu = await PageObjects.observabilityLogsExplorer.getHeaderMenu(); - expect(await headerMenu.isDisplayed()).to.be(true); - }); - - describe('Discover fallback link', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - it('should render a button link ', async () => { - const discoverLink = await PageObjects.observabilityLogsExplorer.getDiscoverFallbackLink(); - expect(await discoverLink.isDisplayed()).to.be(true); - }); - - it('should navigate to discover keeping the current filters/query/time/data view and use fallback columns for virtual columns', async () => { - await retry.try(async () => { - await testSubjects.existOrFail('superDatePickerstartDatePopoverButton'); - await testSubjects.existOrFail('superDatePickerendDatePopoverButton'); - }); - - const timeConfig = await PageObjects.timePicker.getTimeConfig(); - - // Set query bar value - await PageObjects.observabilityLogsExplorer.submitQuery('*favicon*'); - - const discoverLink = await PageObjects.observabilityLogsExplorer.getDiscoverFallbackLink(); - await discoverLink.click(); - - await PageObjects.discover.waitForDocTableLoadingComplete(); - - await dataViews.waitForSwitcherToBe('All logs'); - - await retry.try(async () => { - expect(await PageObjects.discover.getColumnHeaders()).to.eql(['@timestamp', 'Summary']); - }); - - await retry.try(async () => { - expect(await PageObjects.timePicker.getTimeConfig()).to.eql(timeConfig); - }); - - await retry.try(async () => { - expect(await PageObjects.observabilityLogsExplorer.getQueryBarValue()).to.eql( - '*favicon*' - ); - }); - }); - }); - - describe('Add data link', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - it('should render a button link ', async () => { - const onboardingLink = await PageObjects.observabilityLogsExplorer.getOnboardingLink(); - expect(await onboardingLink.isDisplayed()).to.be(true); - }); - - it('should navigate to the observability onboarding overview page', async () => { - const onboardingLink = await PageObjects.observabilityLogsExplorer.getOnboardingLink(); - await onboardingLink.click(); - - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/app/observabilityOnboarding`); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/index.ts b/x-pack/test/functional/apps/observability_logs_explorer/index.ts deleted file mode 100644 index 28d1ececb2802..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from './config'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Observability Logs Explorer', function () { - loadTestFile(require.resolve('./app')); - loadTestFile(require.resolve('./columns_selection')); - loadTestFile(require.resolve('./custom_control_columns')); - loadTestFile(require.resolve('./data_source_selection_state')); - loadTestFile(require.resolve('./data_source_selector')); - loadTestFile(require.resolve('./field_list')); - loadTestFile(require.resolve('./filter_controls')); - loadTestFile(require.resolve('./flyout')); - loadTestFile(require.resolve('./header_menu')); - loadTestFile(require.resolve('./navigation')); - }); -} diff --git a/x-pack/test/functional/apps/observability_logs_explorer/navigation.ts b/x-pack/test/functional/apps/observability_logs_explorer/navigation.ts deleted file mode 100644 index ded8998f24302..0000000000000 --- a/x-pack/test/functional/apps/observability_logs_explorer/navigation.ts +++ /dev/null @@ -1,126 +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 expect from '@kbn/expect'; -import moment from 'moment/moment'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; -import { FtrProviderContext } from './config'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer']); - const synthtrace = getService('logSynthtraceEsClient'); - const kibanaServer = getService('kibanaServer'); - const from = '2023-12-27T10:24:14.035Z'; - const to = '2023-12-27T10:25:14.091Z'; - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe('Navigation', () => { - before(async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await synthtrace.index(generateLogsData({ to })); - await navigateToLogsExplorer(); - }); - - after(async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - await synthtrace.clean(); - }); - - it('should correctly restore the previous selection and data when navigating back from another page', async () => { - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer - .getUncategorizedTab() - .then((tab: WebElementWrapper) => tab.click()); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('synth'); - await menuEntries[0].click(); - }); - - // Assert selection is loaded correctly - const rows = await PageObjects.discover.getDocTableRows(); - expect(rows.length).to.equal(1); - - // Navigate to Discover - const discoverLink = await PageObjects.observabilityLogsExplorer.getDiscoverFallbackLink(); - await discoverLink.click(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - - // Navigate back to Logs Explorer using browser navigation - await browser.goBack(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - - // Assert selection data is restored correctly - const restoredRows = await PageObjects.discover.getDocTableRows(); - expect(restoredRows.length).to.equal(1); - - // Change selection to all logs to assert its data are loaded - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - const allLogsButton = await PageObjects.observabilityLogsExplorer.getAllLogsButton(); - await allLogsButton.click(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - - // Assert new selection data is loaded correctly - const allLogsRows = await PageObjects.discover.getDocTableRows(); - expect(allLogsRows.length).to.equal(2); - }); - }); -} - -function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { - const logsSynth = timerange(moment(to).subtract(1, 'second'), moment(to)) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsSystem = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .dataset('system') - .message('A sample log') - .timestamp(timestamp) - .defaults({ 'service.name': 'system-service' }); - }) - ); - - return [logsSynth, logsSystem]; -} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 0a555d2258483..a591c2b6b4573 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -147,7 +147,6 @@ "@kbn/reporting-common", "@kbn/observability-logs-explorer-plugin", "@kbn/io-ts-utils", - "@kbn/logs-explorer-plugin", "@kbn/security-plugin-types-common", "@kbn/slo-schema", "@kbn/typed-react-router-config", diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_details.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_details.ts index f4f0c8172de49..4ffaa0c7fb77d 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_details.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_details.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import originalExpect from 'expect'; import { defaultNamespace } from '@kbn/test-suites-xpack/functional/apps/dataset_quality/data'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { @@ -25,6 +26,7 @@ const integrationActions = { export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects([ 'common', + 'discover', 'navigationalSearch', 'observabilityLogsExplorer', 'datasetQuality', @@ -333,9 +335,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await logExplorerButton.click(); // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.eql(regularDatasetName); + const datasetSelectorText = await PageObjects.discover.getCurrentDataViewId(); + originalExpect(datasetSelectorText).toMatch(regularDatasetName); }); it('should go log explorer for degraded docs when the button next to breakdown selector is clicked', async () => { @@ -348,9 +349,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.contain(apacheAccessDatasetName); + const datasetSelectorText = await PageObjects.discover.getCurrentDataViewId(); + originalExpect(datasetSelectorText).toMatch(apacheAccessDatasetName); }); }); diff --git a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts index 80214767c92d2..2cd5ef66ad138 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/dataset_quality/dataset_quality_table.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import originalExpect from 'expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { datasetNames, @@ -18,6 +19,7 @@ import { export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects([ 'common', + 'discover', 'navigationalSearch', 'observabilityLogsExplorer', 'datasetQuality', @@ -142,9 +144,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await (await actionsCol.getCellChildren('a'))[rowIndexToOpen].click(); // Click "Open" // Confirm dataset selector text in observability logs explorer - const datasetSelectorText = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(datasetSelectorText).to.eql(datasetName); + const datasetSelectorText = await PageObjects.discover.getCurrentDataViewId(); + + originalExpect(datasetSelectorText).toMatch(datasetName); // Return to Dataset Quality Page await PageObjects.datasetQuality.navigateTo(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/index.ts b/x-pack/test_serverless/functional/test_suites/observability/index.ts index 0885a319636b1..8e0c8215c2f2b 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/index.ts @@ -13,7 +13,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./landing_page')); loadTestFile(require.resolve('./navigation')); - loadTestFile(require.resolve('./observability_logs_explorer')); loadTestFile(require.resolve('./dataset_quality')); loadTestFile(require.resolve('./discover/context_awareness')); loadTestFile(require.resolve('./onboarding')); diff --git a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts index 428d346386b98..55fabe8557526 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/navigation.ts @@ -35,15 +35,14 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { await svlCommonNavigation.sidenav.expectSectionClosed('project_settings_project_nav'); // navigate to the logs explorer tab by default - // 'last-used-logs-viewer' is wrapper app to handle the navigation between logs explorer and discover - await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'last-used-logs-viewer' }); + await svlCommonNavigation.sidenav.clickLink({ deepLinkId: 'discover' }); await svlCommonNavigation.sidenav.expectLinkActive({ - deepLinkId: 'last-used-logs-viewer', + deepLinkId: 'discover', }); await svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ - deepLinkId: 'observability-logs-explorer', + deepLinkId: 'discover', }); - expect(await browser.getCurrentUrl()).contain('/app/observability-logs-explorer'); + expect(await browser.getCurrentUrl()).contain('/app/discover'); // check the aiops subsection await svlCommonNavigation.sidenav.clickLink({ navId: 'observabilityAIAssistant' }); // click on AI Assistant link diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/app.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/app.ts deleted file mode 100644 index 0d6c977281be2..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/app.ts +++ /dev/null @@ -1,65 +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 expect from '@kbn/expect'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects([ - 'observabilityLogsExplorer', - 'svlCommonNavigation', - 'svlCommonPage', - ]); - - const synthtrace = getService('svlLogsSynthtraceClient'); - const dataGrid = getService('dataGrid'); - - describe('Application', () => { - before(async () => { - await PageObjects.svlCommonPage.loginWithPrivilegedRole(); - }); - - it('is shown in the global search', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - - await PageObjects.svlCommonNavigation.search.showSearch(); - await PageObjects.svlCommonNavigation.search.searchFor('logs explorer'); - - const results = await PageObjects.svlCommonNavigation.search.getDisplayedResults(); - expect(results[0].label).to.eql('Logs Explorer'); - - await PageObjects.svlCommonNavigation.search.hideSearch(); - }); - - it('should load logs', async () => { - const from = '2023-08-03T10:24:14.035Z'; - const to = '2023-08-03T10:24:14.091Z'; - const COUNT = 5; - await synthtrace.index(generateLogsData({ from, to, count: COUNT })); - await PageObjects.observabilityLogsExplorer.navigateTo(); - const docCount = await dataGrid.getDocCount(); - - expect(docCount).to.be(COUNT); - await synthtrace.clean(); - }); - }); -} - -function generateLogsData({ from, to, count = 1 }: { from: string; to: string; count: number }) { - const range = timerange(from, to); - - return range - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().message('A sample log').timestamp(timestamp); - }) - ); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts deleted file mode 100644 index 1668cc0478b1c..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/columns_selection.ts +++ /dev/null @@ -1,345 +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 expect from '@kbn/expect'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import moment from 'moment'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -const defaultLogColumns = ['@timestamp', 'Summary']; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer', 'svlCommonPage']); - const synthtrace = getService('svlLogsSynthtraceClient'); - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const from = '2023-12-27T10:24:14.035Z'; - const to = '2023-12-27T10:25:14.091Z'; - const TEST_TIMEOUT = 10 * 1000; // 10 secs - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe('When the logs explorer loads', () => { - before(async () => { - await synthtrace.index(generateLogsData({ to })); - await PageObjects.svlCommonPage.loginWithPrivilegedRole(); - await navigateToLogsExplorer(); - }); - - after(async () => { - await synthtrace.clean(); - }); - - describe('columns selection initialization and update', () => { - it("should initialize the table columns to logs' default selection", async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - expect(await PageObjects.discover.getColumnHeaders()).to.eql(defaultLogColumns); - }); - }); - - it('should restore the table columns from the URL state if exists', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - columns: [{ field: 'data_stream.namespace', type: 'document-field' }], - }, - }); - - await retry.tryForTime(TEST_TIMEOUT, async () => { - expect(await PageObjects.discover.getColumnHeaders()).to.eql([ - '@timestamp', - 'data_stream.namespace', - ]); - }); - }); - }); - - describe.skip('render content virtual column properly', () => { - it('should render log level and log message when present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - expect(cellValue.includes('A sample log')).to.be(true); - }); - }); - - it('should render log message when present and skip log level when missing', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(1, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(false); - expect(cellValue.includes('A sample log')).to.be(true); - }); - }); - - it('should render message from error object when top level message not present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(2, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - expect(cellValue.includes('error.message')).to.be(true); - expect(cellValue.includes('message in error object')).to.be(true); - }); - }); - - it('should render message from event.original when top level message and error.message not present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(3, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - expect(cellValue.includes('event.original')).to.be(true); - expect(cellValue.includes('message in event original')).to.be(true); - }); - }); - - it('should render the whole JSON when neither message, error.message and event.original are present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(4, 2); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('info')).to.be(true); - - expect(cellValue.includes('error.message')).to.be(false); - expect(cellValue.includes('event.original')).to.be(false); - - const cellAttribute = await cellElement.findByTestSubject('discoverCellDescriptionList'); - expect(cellAttribute).not.to.be.empty(); - }); - }); - - it('on cell expansion with no message field should open JSON Viewer', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButtonExcludingControlColumns(4, 2); - await testSubjects.existOrFail('dataTableExpandCellActionJsonPopover'); - }); - }); - - it('on cell expansion with message field should open regular popover', async () => { - await navigateToLogsExplorer(); - await retry.tryForTime(TEST_TIMEOUT, async () => { - await dataGrid.clickCellExpandButtonExcludingControlColumns(3, 2); - await testSubjects.existOrFail('euiDataGridExpansionPopover'); - }); - }); - }); - - describe.skip('render resource virtual column properly', () => { - it('should render service name and host name when present', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); - const cellValue = await cellElement.getVisibleText(); - expect(cellValue.includes('synth-service')).to.be(true); - expect(cellValue.includes('synth-host')).to.be(true); - }); - }); - }); - - describe.skip('virtual column cell actions', () => { - beforeEach(async () => { - await navigateToLogsExplorer(); - }); - it('should render a popover with cell actions when a chip on content column is clicked', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - await logLevelChip.click(); - // Check Filter In button is present - await testSubjects.existOrFail('dataTableCellAction_addToFilterAction_log.level'); - // Check Filter Out button is present - await testSubjects.existOrFail('dataTableCellAction_removeFromFilterAction_log.level'); - // Check Copy button is present - await testSubjects.existOrFail('dataTableCellAction_copyToClipboardAction_log.level'); - }); - }); - - it('should render the table filtered where log.level value is info when filter in action is clicked', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - - const actionSelector = 'dataTableCellAction_addToFilterAction_log.level'; - // Open popover if not already open - if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { - await logLevelChip.click(); - } - - // Find Filter In button - const filterInButton = await testSubjects.find(actionSelector); - - await filterInButton.click(); - const rowWithLogLevelInfo = await testSubjects.findAll('*logLevelBadge-'); - - expect(rowWithLogLevelInfo.length).to.be(4); - }); - }); - - it('should render the table filtered where log.level value is not info when filter out action is clicked', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 2); - const logLevelChip = await cellElement.findByTestSubject('*logLevelBadge-'); - - const actionSelector = 'dataTableCellAction_removeFromFilterAction_log.level'; - // Open popover if not already open - if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { - await logLevelChip.click(); - } - - // Find Filter Out button - const filterOutButton = await testSubjects.find(actionSelector); - - await filterOutButton.click(); - await testSubjects.missingOrFail('*logLevelBadge-'); - }); - }); - - it('should render the table filtered where service.name value is selected', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElementExcludingControlColumns(0, 1); - const serviceNameChip = await cellElement.findByTestSubject( - 'dataTableCellActionsPopover_service.name' - ); - - const actionSelector = 'dataTableCellAction_addToFilterAction_service.name'; - // Open popover if not already open - if (!(await testSubjects.exists(actionSelector, { timeout: 0 }))) { - await serviceNameChip.click(); - } - - // Find Filter In button - const filterInButton = await testSubjects.find(actionSelector); - - await filterInButton.click(); - const rowWithLogLevelInfo = await testSubjects.findAll( - 'dataTableCellActionsPopover_service.name' - ); - - expect(rowWithLogLevelInfo.length).to.be(2); - }); - }); - }); - }); -} - -function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { - const logs = timerange(moment(to).subtract(1, 'second'), moment(to)) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsWithNoLogLevel = timerange( - moment(to).subtract(2, 'second'), - moment(to).subtract(1, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsWithErrorMessage = timerange( - moment(to).subtract(3, 'second'), - moment(to).subtract(2, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.message': 'message in error object', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithEventOriginal = timerange( - moment(to).subtract(4, 'second'), - moment(to).subtract(3, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'event.original': 'message in event original', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithNoMessage = timerange( - moment(to).subtract(5, 'second'), - moment(to).subtract(4, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp); - }) - ); - - const logWithNoMessageNoLogLevel = timerange( - moment(to).subtract(6, 'second'), - moment(to).subtract(5, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().timestamp(timestamp); - }) - ); - - return [ - logs, - logsWithNoLogLevel, - logsWithErrorMessage, - logsWithEventOriginal, - logsWithNoMessage, - logWithNoMessageNoLogLevel, - ]; -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts deleted file mode 100644 index dc59d56886e64..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/custom_control_columns.ts +++ /dev/null @@ -1,187 +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 { log, timerange } from '@kbn/apm-synthtrace-client'; -import expect from '@kbn/expect'; -import moment from 'moment'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -const MORE_THAN_1024_CHARS = - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer', 'svlCommonPage']); - const synthtrace = getService('svlLogsSynthtraceClient'); - const dataGrid = getService('dataGrid'); - const from = '2024-02-06T10:24:14.035Z'; - const to = '2024-02-06T10:25:14.091Z'; - const TEST_TIMEOUT = 10 * 1000; // 10 secs - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe('When the logs explorer loads', () => { - before(async () => { - await synthtrace.index(generateLogsData({ to })); - await PageObjects.svlCommonPage.loginAsViewer(); - await navigateToLogsExplorer(); - }); - - after(async () => { - await synthtrace.clean(); - }); - - describe('should render custom control columns properly', () => { - it('should render control column with proper header', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - // First control column has no title, so empty string, leading control column has title - expect(await dataGrid.getControlColumnHeaderFields()).to.eql(['', '', '', '']); - }); - }); - - it('should render the expand icon in the leading control column', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 1); - const expandButton = await cellElement.findByTestSubject('docTableExpandToggleColumn'); - expect(expandButton).to.not.be.empty(); - }); - }); - - it('should render the degraded icon in the leading control column if degraded doc exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 2); - const degradedButton = await cellElement.findByTestSubject('docTableDegradedDocExist'); - expect(degradedButton).to.not.be.empty(); - }); - }); - - it('should render the disabled degraded icon in the leading control column when degraded doc does not exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(0, 2); - const degradedDisableButton = await cellElement.findByTestSubject( - 'docTableDegradedDocDoesNotExist' - ); - expect(degradedDisableButton).to.not.be.empty(); - }); - }); - - it('should render the stacktrace icon in the leading control column when stacktrace exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(4, 3); - const stacktraceButton = await cellElement.findByTestSubject('docTableStacktraceExist'); - expect(stacktraceButton).to.not.be.empty(); - }); - }); - - it('should render the stacktrace icon disabled in the leading control column when stacktrace does not exists', async () => { - await retry.tryForTime(TEST_TIMEOUT, async () => { - const cellElement = await dataGrid.getCellElement(1, 3); - const stacktraceButton = await cellElement.findByTestSubject( - 'docTableStacktraceDoesNotExist' - ); - expect(stacktraceButton).to.not.be.empty(); - }); - }); - }); - }); -} - -function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { - const logs = timerange(moment(to).subtract(1, 'second'), moment(to)) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const degradedDocs = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A degraded doc') - .logLevel(MORE_THAN_1024_CHARS) - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsWithErrorMessage = timerange( - moment(to).subtract(3, 'second'), - moment(to).subtract(2, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.stack_trace': 'Error message in error.stack_trace', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithErrorException = timerange( - moment(to).subtract(4, 'second'), - moment(to).subtract(3, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.exception.stacktrace': 'Error message in error.exception.stacktrace', - 'service.name': 'node-service', - }); - }) - ); - - const logsWithErrorInLog = timerange( - moment(to).subtract(5, 'second'), - moment(to).subtract(4, 'second') - ) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log.create().logLevel('info').timestamp(timestamp).defaults({ - 'error.log.stacktrace': 'Error message in error.log.stacktrace', - 'service.name': 'node-service', - }); - }) - ); - - return [logs, degradedDocs, logsWithErrorMessage, logsWithErrorException, logsWithErrorInLog]; -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts deleted file mode 100644 index 509f90c1f768b..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selection_state.ts +++ /dev/null @@ -1,123 +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 expect from '@kbn/expect'; -import { decodeOrThrow, indexPatternRt } from '@kbn/io-ts-utils'; -import { DataSourceSelectionPlain } from '@kbn/logs-explorer-plugin/common'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -const azureActivityDatasetSelection: DataSourceSelectionPlain = { - selection: { - dataset: { - name: decodeOrThrow(indexPatternRt)('logs-azure.activitylogs-*'), - title: 'activitylogs', - }, - name: 'azure', - title: 'Azure Logs', - version: '1.5.23', - }, - selectionType: 'single', -}; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const retry = getService('retry'); - const PageObjects = getPageObjects([ - 'common', - 'observabilityLogsExplorer', - 'svlCommonPage', - 'header', - ]); - - describe('dataSourceSelection initialization and update', () => { - before(async () => { - await PageObjects.svlCommonPage.loginAsViewer(); - }); - - describe('when no dataSourceSelection is given', () => { - it('should initialize the "All logs" selection', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - await PageObjects.header.waitUntilLoadingHasFinished(); - const dataSourceSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - - expect(dataSourceSelectionTitle).to.be('All logs'); - }); - }); - - describe('when a dataSourceSelection is given', () => { - it('should decode and restore the selection from a valid parameter', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - dataSourceSelection: azureActivityDatasetSelection, - }, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - - const dataSourceSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - - expect(dataSourceSelectionTitle).to.be('[Azure Logs] activitylogs'); - }); - - it('should fallback to the "All logs" selection and notify the user of an invalid parameter', async () => { - await PageObjects.observabilityLogsExplorer.navigateToWithUncheckedState({ - pageState: { - v: 2, - dataSourceSelection: { - selectionType: 'invalid', - }, - }, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - - const dataSourceSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - - await PageObjects.observabilityLogsExplorer.assertRestoreFailureToastExist(); - expect(dataSourceSelectionTitle).to.be('All logs'); - }); - }); - - describe('when navigating back and forth on the page history', () => { - it('should decode and restore the selection for the current index', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - await PageObjects.header.waitUntilLoadingHasFinished(); - const allDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(allDatasetSelectionTitle).to.be('All logs'); - - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - dataSourceSelection: azureActivityDatasetSelection, - }, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - const azureDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(azureDatasetSelectionTitle).to.be('[Azure Logs] activitylogs'); - - // Go back to previous page selection - await retry.try(async () => { - await browser.goBack(); - await PageObjects.header.waitUntilLoadingHasFinished(); - const backNavigationDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(backNavigationDatasetSelectionTitle).to.be('All logs'); - }); - - // Go forward to previous page selection - await retry.try(async () => { - await browser.goForward(); - await PageObjects.header.waitUntilLoadingHasFinished(); - const forwardNavigationDatasetSelectionTitle = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButtonText(); - expect(forwardNavigationDatasetSelectionTitle).to.be('[Azure Logs] activitylogs'); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts deleted file mode 100644 index ba12ebc153ca8..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/data_source_selector.ts +++ /dev/null @@ -1,865 +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 expect from '@kbn/expect'; -import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -const initialPackageMap = { - apache: 'Apache HTTP Server', - aws: 'AWS', - system: 'System', -}; -const initialPackagesTexts = Object.values(initialPackageMap); - -const expectedDataViews = ['logs-*', 'metrics-*']; -const sortedExpectedDataViews = expectedDataViews.slice().sort(); - -const uncategorized = ['logs-gaming-*', 'logs-manufacturing-*', 'logs-retail-*']; -const expectedUncategorized = uncategorized.map((dataset) => dataset.split('-')[1]); - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const esArchiver = getService('esArchiver'); - const retry = getService('retry'); - const dataViews = getService('dataViews'); - const PageObjects = getPageObjects([ - 'common', - 'discover', - 'observabilityLogsExplorer', - 'svlCommonPage', - ]); - - const noIntegrationsTitle = 'No integrations found'; - const noUncategorizedTitle = 'No data streams found'; - - describe('DataSourceSelector', function () { - // TimeoutError: Waiting for element to be located By(css selector, [data-test-subj="dataSourceSelectorPopoverButton"]) - this.tags(['failsOnMKI']); - before(async () => { - await PageObjects.svlCommonPage.loginAsViewer(); - await PageObjects.observabilityLogsExplorer.removeInstalledPackages(); - }); - - describe('as consistent behavior', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should always display the Integrations, Uncategorized and Data Views top level tabs', async () => { - const integrationsTab = await PageObjects.observabilityLogsExplorer.getIntegrationsTab(); - const uncategorizedTab = await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - const dataViewsTab = await PageObjects.observabilityLogsExplorer.getDataViewsTab(); - - expect(await integrationsTab.isDisplayed()).to.be(true); - expect(await integrationsTab.getVisibleText()).to.be('Integrations'); - expect(await uncategorizedTab.isDisplayed()).to.be(true); - expect(await uncategorizedTab.getVisibleText()).to.be('Uncategorized'); - expect(await dataViewsTab.isDisplayed()).to.be(true); - expect(await dataViewsTab.getVisibleText()).to.be('Data Views'); - }); - - it('should always display the "Show all logs" action', async () => { - const allLogsButton = await PageObjects.observabilityLogsExplorer.getAllLogsButton(); - - const allLogsTitle = await allLogsButton.getVisibleText(); - - expect(allLogsTitle).to.be('Show all logs'); - }); - - describe('when open on the integrations tab', () => { - it('should display an error prompt if could not retrieve the integrations', async function () { - // Skip the test in case network condition utils are not available - try { - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - - await PageObjects.common.sleep(5000); - await browser.setNetworkConditions('OFFLINE'); - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('a'); - - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusErrorPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - - await browser.restoreNetworkConditions(); - } catch (error) { - this.skip(); - } - }); - - it('should display an empty prompt for no integrations', async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(menuEntries.length).to.be(0); - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - }); - - describe('when open on the uncategorized tab', () => { - it('should display a loading skeleton while loading uncategorized datasets', async function () { - // Skip the test in case network condition utils are not available - try { - await browser.setNetworkConditions('SLOW_3G'); // Almost stuck network conditions - const uncategorizedTab = - await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - await uncategorizedTab.click(); - - await PageObjects.observabilityLogsExplorer.assertLoadingSkeletonExists(); - - await browser.restoreNetworkConditions(); - } catch (error) { - this.skip(); - } - }); - - it('should display an error prompt if could not retrieve the datasets', async function () { - const uncategorizedTab = - await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - await uncategorizedTab.click(); - - // Skip the test in case network condition utils are not available - try { - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noUncategorizedTitle - ); - }); - - await PageObjects.common.sleep(5000); - await browser.setNetworkConditions('OFFLINE'); - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('a'); - - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.assertListStatusErrorPromptExistsWithTitle( - noUncategorizedTitle - ); - }); - - await browser.restoreNetworkConditions(); - } catch (error) { - this.skip(); - } - }); - - it('should display an empty prompt for no uncategorized data streams', async () => { - const uncategorizedTab = - await PageObjects.observabilityLogsExplorer.getUncategorizedTab(); - await uncategorizedTab.click(); - - const uncategorizedEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(uncategorizedEntries.length).to.be(0); - - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noUncategorizedTitle - ); - }); - }); - }); - - describe('with installed integrations and uncategorized data streams', () => { - let cleanupIntegrationsSetup: () => Promise; - - before(async () => { - await esArchiver.load( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - cleanupIntegrationsSetup = - await PageObjects.observabilityLogsExplorer.setupInitialIntegrations(); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - await cleanupIntegrationsSetup(); - }); - - describe('when open on the integrations tab', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should display a list of installed integrations', async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - - expect(integrations.length).to.be(3); - expect(integrations).to.eql(initialPackagesTexts); - }); - - it('should sort the integrations list by the clicked sorting option', async () => { - // Test ascending order - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql(initialPackagesTexts); - }); - - // Test descending order - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql(initialPackagesTexts.slice().reverse()); - }); - - // Test back ascending order - await retry.try(async () => { - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql(initialPackagesTexts); - }); - }); - - it('should filter the integrations list by the typed integration name', async () => { - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('system'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.system]); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('a'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.apache, initialPackageMap.aws]); - }); - }); - - it('should display an empty prompt when the search does not match any result', async () => { - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('no result search text'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations.length).to.be(0); - }); - - await PageObjects.observabilityLogsExplorer.assertListStatusEmptyPromptExistsWithTitle( - noIntegrationsTitle - ); - }); - - it('should load more integrations by scrolling to the end of the list', async () => { - // Install more integrations and reload the page - const cleanupAdditionalSetup = - await PageObjects.observabilityLogsExplorer.setupAdditionalIntegrations(); - await browser.refresh(); - - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - - // Initially fetched integrations - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(nodes.length).to.be(15); - await nodes.at(-1)?.scrollIntoView(); - }); - - // Load more integrations - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(nodes.length).to.be(20); - await nodes.at(-1)?.scrollIntoView(); - }); - - // No other integrations to load after scrolling to last integration - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(nodes.length).to.be(20); - }); - - await cleanupAdditionalSetup(); - }); - - describe('clicking on integration and moving into the second navigation level', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should display a list of available datasets', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const [panelTitleNode, integrationDatasetEntries] = - await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await integrationDatasetEntries[0].getVisibleText()).to.be('access'); - expect(await integrationDatasetEntries[1].getVisibleText()).to.be('error'); - }); - }); - - it('should sort the datasets list by the clicked sorting option', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); - - // Test ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be('error'); - expect(await menuEntries[1].getVisibleText()).to.be('access'); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - }); - - it('should filter the datasets list by the typed dataset name', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('err'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - }); - }); - - it('should update the current selection with the clicked dataset', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be('access'); - await menuEntries[0].click(); - }); - - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButton(); - - expect(await selectorButton.getVisibleText()).to.be('[Apache HTTP Server] access'); - }); - }); - }); - }); - - describe('when open on the uncategorized tab', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer - .getUncategorizedTab() - .then((tab) => tab.click()); - }); - - it('should display a list of available datasets', async () => { - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - }); - - it('should sort the datasets list by the clicked sorting option', async () => { - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - }); - - // Test ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[2]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[0]); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - }); - - it('should filter the datasets list by the typed dataset name', async () => { - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedUncategorized[1]); - expect(await menuEntries[2].getVisibleText()).to.be(expectedUncategorized[2]); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('retail'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('retail'); - }); - }); - - it('should update the current selection with the clicked dataset', async () => { - await retry.try(async () => { - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - - expect(await panelTitleNode.getVisibleText()).to.be('Uncategorized'); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedUncategorized[0]); - await menuEntries[0].click(); - }); - - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButton(); - - expect(await selectorButton.getVisibleText()).to.be(expectedUncategorized[0]); - }); - }); - }); - - describe('when open on the data views tab', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.getDataViewsTab().then((tab) => tab.click()); - }); - - it('should display a list of available data views', async () => { - const menu = await PageObjects.observabilityLogsExplorer.getDataViewsContextMenu(); - const menuEntries = await PageObjects.observabilityLogsExplorer.getPanelEntries(menu); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]); - }); - - it('should filter the list of data views by type', async () => { - await PageObjects.observabilityLogsExplorer.changeDataViewTypeFilter('Logs'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); - }); - - // Test back all filter - await PageObjects.observabilityLogsExplorer.changeDataViewTypeFilter('All'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]); - }); - }); - - it('should sort the data views list by the clicked sorting option', async () => { - // Test descending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('desc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[1]); - expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[0]); - }); - - // Test back ascending order - await PageObjects.observabilityLogsExplorer.clickSortButtonBy('asc'); - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(sortedExpectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(sortedExpectedDataViews[1]); - }); - }); - - it('should filter the data views list by the typed data view name', async () => { - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]); - expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('logs'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('logs-*'); - }); - }); - - it('should load a data view allowed by the settings upon click', async () => { - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be(expectedDataViews[0]); - await menuEntries[0].click(); - }); - - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/app/observability-logs-explorer`); - }); - - await retry.try(async () => { - const selectorButton = - await PageObjects.observabilityLogsExplorer.getDataSourceSelectorButton(); - - expect(await selectorButton.getVisibleText()).to.be(expectedDataViews[0]); - }); - }); - - it('should navigate to Discover and load a data view not allowed by the settings upon click', async () => { - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getDataViewsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(await menuEntries[1].getVisibleText()).to.be(expectedDataViews[1]); - await menuEntries[1].click(); - }); - - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/app/discover`); - }); - - await dataViews.waitForSwitcherToBe(expectedDataViews[1]); - }); - }); - - describe('when open/close the selector', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - beforeEach(async () => { - await browser.refresh(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - }); - - it('should restore the latest navigation panel', async () => { - await retry.try(async () => { - const { nodes } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - await nodes[0].click(); - }); - - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - await PageObjects.observabilityLogsExplorer.closeDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - }); - - it('should restore the latest search results', async () => { - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('system'); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.system]); - }); - - await PageObjects.observabilityLogsExplorer.closeDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - - await retry.try(async () => { - const { integrations } = await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.system]); - }); - }); - }); - - describe('when switching between tabs or integration panels', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - }); - - it('should remember the latest search and restore its results', async () => { - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer.clearSearchField(); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('apache'); - - await retry.try(async () => { - const { nodes, integrations } = - await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.apache]); - await nodes[0].click(); - }); - - await retry.try(async () => { - const [panelTitleNode, menuEntries] = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => - Promise.all([ - PageObjects.observabilityLogsExplorer.getPanelTitle(menu), - PageObjects.observabilityLogsExplorer.getPanelEntries(menu), - ]) - ); - - expect(await panelTitleNode.getVisibleText()).to.be('Apache HTTP Server'); - expect(await menuEntries[0].getVisibleText()).to.be('access'); - expect(await menuEntries[1].getVisibleText()).to.be('error'); - }); - - await PageObjects.observabilityLogsExplorer.typeSearchFieldWith('err'); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - }); - - // Navigate back to integrations - const panelTitleNode = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelTitle(menu)); - await panelTitleNode.click(); - - await retry.try(async () => { - const { nodes, integrations } = - await PageObjects.observabilityLogsExplorer.getIntegrations(); - expect(integrations).to.eql([initialPackageMap.apache]); - - const searchValue = await PageObjects.observabilityLogsExplorer.getSearchFieldValue(); - expect(searchValue).to.eql('apache'); - - await nodes[0].click(); - }); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getIntegrationsContextMenu() - .then((menu) => PageObjects.observabilityLogsExplorer.getPanelEntries(menu)); - - const searchValue = await PageObjects.observabilityLogsExplorer.getSearchFieldValue(); - expect(searchValue).to.eql('err'); - - expect(menuEntries.length).to.be(1); - expect(await menuEntries[0].getVisibleText()).to.be('error'); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts deleted file mode 100644 index 547ce7c86339c..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/field_list.ts +++ /dev/null @@ -1,110 +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 expect from '@kbn/expect'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects([ - 'observabilityLogsExplorer', - 'unifiedFieldList', - 'svlCommonPage', - ]); - const synthtrace = getService('svlLogsSynthtraceClient'); - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const from = '2024-02-23T10:24:14.035Z'; - const to = '2024-02-23T10:25:14.091Z'; - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe.skip('When virtual columns loads', () => { - before(async () => { - await synthtrace.index(generateLogsData({ from, to })); - await PageObjects.svlCommonPage.loginWithPrivilegedRole(); - await navigateToLogsExplorer(); - }); - - after(async () => { - await synthtrace.clean(); - }); - - describe('field list initialisation', () => { - it('should display the virtual columns in the field list', async () => { - // Smart Field group should be present - await testSubjects.existOrFail('fieldListGroupedSmartFields'); - // Resource field should be present - await testSubjects.existOrFail('dscFieldListPanelField-resource'); - // Content field should be present - await testSubjects.existOrFail('dscFieldListPanelField-content'); - }); - - it('should allow toggling of fields', async () => { - const defaultLogColumns = ['@timestamp', 'resource', 'content']; - const headerFields = await dataGrid.getHeaderFields(); - expect(headerFields).to.eql(defaultLogColumns); - - // After toggling from Field List, the resource column must disappear - // from the table - const toggledLogColumns = ['@timestamp', 'content']; - await PageObjects.unifiedFieldList.clickFieldListItemToggle('resource'); - const updatedHeaderFields = await dataGrid.getHeaderFields(); - expect(updatedHeaderFields).to.eql(toggledLogColumns); - // Field should be removed from Selected Fields - const selectedField = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField).to.be(false); - - // After toggling again from Field List, the resource column must appear - // in the table but after content column - const reorderedLogColumns = ['@timestamp', 'content', 'resource']; - await PageObjects.unifiedFieldList.clickFieldListItemToggle('resource'); - const updatedHeaderFields2 = await dataGrid.getHeaderFields(); - expect(updatedHeaderFields2).to.eql(reorderedLogColumns); - // Field should be added in Selected Fields - const selectedField2 = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField2).to.be(true); - }); - - it('should update selected field list when column is removed from column header', async () => { - const selectedField = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField).to.be(true); - - await dataGrid.clickRemoveColumn('resource'); - const selectedField2 = await PageObjects.unifiedFieldList.isFieldSelected('resource'); - expect(selectedField2).to.be(false); - }); - }); - }); -} - -function generateLogsData({ from, to, count = 1 }: { from: string; to: string; count?: number }) { - return timerange(from, to) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts deleted file mode 100644 index 2ba0c25cd19f4..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/filter_controls.ts +++ /dev/null @@ -1,29 +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 { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['observabilityLogsExplorer', 'svlCommonPage']); - - describe('Filter controls customization', () => { - before('initialize tests', async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await PageObjects.svlCommonPage.loginWithPrivilegedRole(); - }); - - after('clean up archives', async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - }); - - it('renders a filter controls section as part of the unified search bar', async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - await testSubjects.existOrFail('dataSourceFiltersCustomization', { allowHidden: true }); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts deleted file mode 100644 index 87d3ef61c5758..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/flyout.ts +++ /dev/null @@ -1,77 +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 { FtrProviderContext } from '../../../ftr_provider_context'; - -const DATASET_NAME = 'flyout'; -const NAMESPACE = 'default'; -const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`; -const NOW = Date.now(); - -const sharedDoc = { - logFilepath: '/flyout.log', - serviceName: DATASET_NAME, - datasetName: DATASET_NAME, - namespace: NAMESPACE, -}; - -const docs = [ - { - ...sharedDoc, - time: NOW + 1000, - message: 'full document', - logLevel: 'info', - }, - { - ...sharedDoc, - time: NOW, - }, -]; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const dataGrid = getService('dataGrid'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['observabilityLogsExplorer', 'svlCommonPage']); - - describe('Flyout content customization', () => { - let cleanupDataStreamSetup: () => Promise; - - before('initialize tests', async () => { - cleanupDataStreamSetup = await PageObjects.observabilityLogsExplorer.setupDataStream( - DATASET_NAME, - NAMESPACE - ); - await PageObjects.observabilityLogsExplorer.ingestLogEntries(DATA_STREAM_NAME, docs); - await PageObjects.svlCommonPage.loginAsViewer(); - }); - - beforeEach(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from: new Date(NOW - 60_000).toISOString(), - to: new Date(NOW + 60_000).toISOString(), - mode: 'absolute', - }, - }, - }); - }); - - after('clean up archives', async () => { - if (cleanupDataStreamSetup) { - await cleanupDataStreamSetup(); - } - }); - - it('should display the logs overview tab', async () => { - await retry.try(async () => { - await dataGrid.clickRowToggle(); - }); - await testSubjects.existOrFail('docViewerTab-doc_view_logs_overview'); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts deleted file mode 100644 index b9d514cd0b79d..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/header_menu.ts +++ /dev/null @@ -1,196 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const retry = getService('retry'); - const testSubjects = getService('testSubjects'); - const dataViews = getService('dataViews'); - const PageObjects = getPageObjects([ - 'discover', - 'observabilityLogsExplorer', - 'svlCommonPage', - 'timePicker', - 'header', - 'svlCommonNavigation', - ]); - - // Failing: See https://github.com/elastic/kibana/issues/173165 - describe.skip('Header menu', () => { - before(async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await esArchiver.load( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); - await PageObjects.svlCommonPage.loginAsViewer(); - await PageObjects.observabilityLogsExplorer.navigateTo(); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - after(async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - await esArchiver.unload( - 'x-pack/test/functional/es_archives/observability_logs_explorer/data_streams' - ); - await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); - }); - - it('should inject the app header menu on the top navbar', async () => { - const headerMenu = await PageObjects.observabilityLogsExplorer.getHeaderMenu(); - expect(await headerMenu.isDisplayed()).to.be(true); - }); - - describe('Discover fallback link', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - // avoid aligning with the test data, because that's what Discover - // does later in this test and we wouldn't be able to check the time - // range state transfer - time: { - from: '2023-08-03T00:00:00.000Z', - to: '2023-08-03T12:00:00.000Z', - mode: 'absolute', - }, - }, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - it('should render a button link ', async () => { - const discoverLink = await PageObjects.observabilityLogsExplorer.getDiscoverFallbackLink(); - expect(await discoverLink.isDisplayed()).to.be(true); - }); - - it('should navigate to discover keeping the current filters/query/time/data view and use fallback columns for virtual columns', async () => { - await retry.try(async () => { - await testSubjects.existOrFail('superDatePickerstartDatePopoverButton'); - await testSubjects.existOrFail('superDatePickerendDatePopoverButton'); - }); - const timeConfig = await PageObjects.timePicker.getTimeConfig(); - // Set query bar value - await PageObjects.observabilityLogsExplorer.submitQuery('*favicon*'); - - const discoverLink = await PageObjects.observabilityLogsExplorer.getDiscoverFallbackLink(); - await discoverLink.click(); - - await PageObjects.discover.waitForDocTableLoadingComplete(); - - await dataViews.waitForSwitcherToBe('All logs'); - - await retry.try(async () => { - expect(await PageObjects.discover.getColumnHeaders()).to.eql(['@timestamp', 'Summary']); - }); - await retry.try(async () => { - expect(await PageObjects.timePicker.getTimeConfig()).to.eql(timeConfig); - }); - await retry.try(async () => { - expect(await PageObjects.observabilityLogsExplorer.getQueryBarValue()).to.eql( - '*favicon*' - ); - }); - }); - }); - - describe('Discover tabs', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - // avoid aligning with the test data, because that's what Discover - // does later in this test and we wouldn't be able to check the time - // range state transfer - time: { - from: '2023-08-03T00:00:00.000Z', - to: '2023-08-03T12:00:00.000Z', - mode: 'absolute', - }, - }, - }); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - it('should navigate between discover tabs without keeping the current columns/filters/query/time/data view', async () => { - await retry.try(async () => { - await testSubjects.existOrFail('superDatePickerstartDatePopoverButton'); - await testSubjects.existOrFail('superDatePickerendDatePopoverButton'); - }); - - const timeConfig = await PageObjects.timePicker.getTimeConfig(); - - // Set query bar value - await PageObjects.observabilityLogsExplorer.submitQuery('*favicon*'); - - // go to discover tab - await testSubjects.click('discoverTab'); - await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ - deepLinkId: 'discover', - }); - await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbMissing({ - deepLinkId: 'observability-logs-explorer', - }); - expect(await browser.getCurrentUrl()).contain('/app/discover'); - - await dataViews.waitForSwitcherToBe('All logs'); - - await retry.try(async () => { - expect(await PageObjects.discover.getColumnHeaders()).not.to.eql([ - '@timestamp', - 'content', - 'resource', - ]); - }); - - await retry.try(async () => { - expect(await PageObjects.timePicker.getTimeConfig()).not.to.eql(timeConfig); - }); - - await retry.try(async () => { - expect(await PageObjects.observabilityLogsExplorer.getQueryBarValue()).not.to.eql( - '*favicon*' - ); - }); - - // go to logs explorer tab - await testSubjects.click('logsExplorerTab'); - await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ - deepLinkId: 'discover', - }); - await PageObjects.svlCommonNavigation.breadcrumbs.expectBreadcrumbExists({ - deepLinkId: 'observability-logs-explorer', - }); - expect(await browser.getCurrentUrl()).contain('/app/observability-logs-explorer'); - }); - }); - - describe('Add data link', () => { - before(async () => { - await PageObjects.observabilityLogsExplorer.navigateTo(); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - it('should render a button link ', async () => { - const onboardingLink = await PageObjects.observabilityLogsExplorer.getOnboardingLink(); - expect(await onboardingLink.isDisplayed()).to.be(true); - }); - - it('should navigate to the observability onboarding overview page', async () => { - const onboardingLink = await PageObjects.observabilityLogsExplorer.getOnboardingLink(); - await onboardingLink.click(); - - await retry.try(async () => { - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/app/observabilityOnboarding`); - }); - }); - }); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/index.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/index.ts deleted file mode 100644 index a2b0d4302c737..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Observability Logs Explorer', function () { - loadTestFile(require.resolve('./app')); - loadTestFile(require.resolve('./columns_selection')); - loadTestFile(require.resolve('./custom_control_columns')); - loadTestFile(require.resolve('./data_source_selection_state')); - loadTestFile(require.resolve('./data_source_selector')); - loadTestFile(require.resolve('./field_list')); - loadTestFile(require.resolve('./filter_controls')); - loadTestFile(require.resolve('./flyout')); - loadTestFile(require.resolve('./header_menu')); - loadTestFile(require.resolve('./navigation')); - }); -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts deleted file mode 100644 index 323e11d3ec540..0000000000000 --- a/x-pack/test_serverless/functional/test_suites/observability/observability_logs_explorer/navigation.ts +++ /dev/null @@ -1,127 +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 expect from '@kbn/expect'; -import moment from 'moment/moment'; -import { log, timerange } from '@kbn/apm-synthtrace-client'; -import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; -import { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const browser = getService('browser'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['discover', 'observabilityLogsExplorer', 'svlCommonPage']); - const synthtrace = getService('svlLogsSynthtraceClient'); - const kibanaServer = getService('kibanaServer'); - const from = '2023-12-27T10:24:14.035Z'; - const to = '2023-12-27T10:25:14.091Z'; - - const navigateToLogsExplorer = () => - PageObjects.observabilityLogsExplorer.navigateTo({ - pageState: { - time: { - from, - to, - mode: 'absolute', - }, - }, - }); - - describe('Navigation', () => { - before(async () => { - await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover'); - await synthtrace.index(generateLogsData({ to })); - await PageObjects.svlCommonPage.loginAsViewer(); - await navigateToLogsExplorer(); - }); - - after(async () => { - await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover'); - await synthtrace.clean(); - }); - - it('should correctly restore the previous selection and data when navigating back from another page', async () => { - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - await PageObjects.observabilityLogsExplorer - .getUncategorizedTab() - .then((tab: WebElementWrapper) => tab.click()); - - await retry.try(async () => { - const menuEntries = await PageObjects.observabilityLogsExplorer - .getUncategorizedContextMenu() - .then((menu: WebElementWrapper) => - PageObjects.observabilityLogsExplorer.getPanelEntries(menu) - ); - - expect(await menuEntries[0].getVisibleText()).to.be('synth'); - await menuEntries[0].click(); - }); - - // Assert selection is loaded correctly - const rows = await PageObjects.discover.getDocTableRows(); - expect(rows.length).to.equal(1); - - // Navigate to Discover - const discoverLink = await PageObjects.observabilityLogsExplorer.getDiscoverFallbackLink(); - await discoverLink.click(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - - // Navigate back to Logs Explorer using browser navigation - await browser.goBack(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - - // Assert selection data is restored correctly - const restoredRows = await PageObjects.discover.getDocTableRows(); - expect(restoredRows.length).to.equal(1); - - // Change selection to all logs to assert its data are loaded - await PageObjects.observabilityLogsExplorer.openDataSourceSelector(); - const allLogsButton = await PageObjects.observabilityLogsExplorer.getAllLogsButton(); - await allLogsButton.click(); - await PageObjects.discover.waitForDocTableLoadingComplete(); - - // Assert new selection data is loaded correctly - const allLogsRows = await PageObjects.discover.getDocTableRows(); - expect(allLogsRows.length).to.equal(2); - }); - }); -} - -function generateLogsData({ to, count = 1 }: { to: string; count?: number }) { - const logsSynth = timerange(moment(to).subtract(1, 'second'), moment(to)) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .message('A sample log') - .logLevel('info') - .timestamp(timestamp) - .defaults({ 'service.name': 'synth-service' }); - }) - ); - - const logsSystem = timerange(moment(to).subtract(2, 'second'), moment(to).subtract(1, 'second')) - .interval('1m') - .rate(1) - .generator((timestamp) => - Array(count) - .fill(0) - .map(() => { - return log - .create() - .dataset('system') - .message('A sample log') - .timestamp(timestamp) - .defaults({ 'service.name': 'system-service' }); - }) - ); - - return [logsSynth, logsSystem]; -} diff --git a/x-pack/test_serverless/functional/test_suites/observability/onboarding/auto_detect.ts b/x-pack/test_serverless/functional/test_suites/observability/onboarding/auto_detect.ts index b8f47086e85c3..50682feeee674 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/onboarding/auto_detect.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/onboarding/auto_detect.ts @@ -11,13 +11,7 @@ import moment from 'moment'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects([ - 'datasetQuality', - 'observabilityLogsExplorer', - 'common', - 'svlCommonNavigation', - 'svlCommonPage', - ]); + const PageObjects = getPageObjects(['common', 'svlCommonPage']); const browser = getService('browser'); const testSubjects = getService('testSubjects'); diff --git a/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts b/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts index 2db9e4c5d3b16..1796434cf56dd 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/role_management/custom_role_access.ts @@ -60,7 +60,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should have limited navigation menu', async () => { await pageObjects.svlCommonPage.assertUserAvatarExists(); // discover navigation link is present - await testSubjects.existOrFail('~nav-item-id-last-used-logs-viewer'); + await testSubjects.existOrFail('~nav-item-id-discover'); // all other links in navigation menu are hidden await testSubjects.missingOrFail('~nav-item-id-dashboards'); diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index 452b6b0260fbf..44cb70abd69ca 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -64,8 +64,6 @@ "@kbn/apm-synthtrace-client", "@kbn/reporting-export-types-csv-common", "@kbn/mock-idp-utils", - "@kbn/io-ts-utils", - "@kbn/logs-explorer-plugin", "@kbn/index-management-plugin", "@kbn/transform-plugin", "@kbn/slo-schema", From 32dbd14519584b118861a209fdc841b011b5b5a0 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Fri, 3 Jan 2025 11:44:11 -0800 Subject: [PATCH 17/43] [Cloud Security] Fix for issue where date is not retained when opening new tab (#205128) ## Summary This PR addresses the issue where when user opens new tab by clicking on popout icon on Alerts Data grid on Alerts preview Contextual flyout, the date from original tab is not retained, as such there are scenario where user don't see any alerts on new tab because its defaulted to today when the alerts only exist from view days ago https://github.com/user-attachments/assets/58a30d7d-18b3-47a8-ab4b-2ce143583368 --- .../alerts_findings_details_table.tsx | 17 ++++++- ...vigate_to_alerts_page_with_filters.test.ts | 50 +++++++++++++++++++ ...se_navigate_to_alerts_page_with_filters.ts | 19 +++++-- 3 files changed, 81 insertions(+), 5 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx index 78c5604615903..e9aab1d922a72 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/cloud_security_posture/components/csp_details/alerts_findings_details_table.tsx @@ -6,6 +6,7 @@ */ import React, { memo, useCallback, useEffect, useState } from 'react'; +import { encode } from '@kbn/rison'; import { capitalize } from 'lodash'; import type { Criteria, EuiBasicTableColumn, EuiTableSortingType } from '@elastic/eui'; import { EuiSpacer, EuiPanel, EuiText, EuiBasicTable, EuiIcon, EuiLink } from '@elastic/eui'; @@ -26,6 +27,7 @@ import { OPEN_IN_ALERTS_TITLE_STATUS, OPEN_IN_ALERTS_TITLE_USERNAME, } from '../../../overview/components/detection_response/translations'; +import { URL_PARAM_KEY } from '../../../common/hooks/use_url_state'; import { useNavigateToAlertsPageWithFilters } from '../../../common/hooks/use_navigate_to_alerts_page_with_filters'; import { DocumentDetailsPreviewPanelKey } from '../../../flyout/document_details/shared/constants/panel_keys'; import { useGlobalTime } from '../../../common/containers/use_global_time'; @@ -114,6 +116,16 @@ export const AlertsDetailsTable = memo( }; const { to, from } = useGlobalTime(); + const timerange = encode({ + global: { + [URL_PARAM_KEY.timerange]: { + kind: 'absolute', + from, + to, + }, + }, + }); + const { signalIndexName } = useSignalIndex(); const { data, setQuery } = useQueryAlerts({ query: buildEntityAlertsQuery({ @@ -327,9 +339,10 @@ export const AlertsDetailsTable = memo( fieldName: 'kibana.alert.workflow_status', }, ], - true + true, + timerange ), - [field, openAlertsPageWithFilters, value] + [field, openAlertsPageWithFilters, timerange, value] ); return ( diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts index c38ddfd402411..07c15b3da9d8a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.test.ts @@ -80,4 +80,54 @@ describe('useNavigateToAlertsPageWithFilters', () => { expect.objectContaining({ deepLinkId: SecurityPageName.alerts }) ); }); + + it('navigates to alerts page in new tab', () => { + const filter = { + title: 'test filter', + selectedOptions: ['test value'], + fieldName: 'test field', + exclude: false, + existsSelected: false, + }; + const openInNewTab = true; + + const { + result: { current: navigateToAlertsPageWithFilters }, + } = renderHook(() => useNavigateToAlertsPageWithFilters()); + + navigateToAlertsPageWithFilters(filter, openInNewTab); + + expect(mockNavigateTo).toHaveBeenCalledWith({ + deepLinkId: SecurityPageName.alerts, + path: "?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field',hideActionBar:!f,selectedOptions:!('test value'),title:'test filter'))", + openInNewTab: true, + }); + }); + + it('navigates to alerts page with timerange', () => { + const filter = { + title: 'test filter', + selectedOptions: ['test value'], + fieldName: 'test field', + exclude: false, + existsSelected: false, + }; + + const timerange = + '(global:(timerange:(from:"2024-12-12T17:03:23.481Z",kind:absolute,to:"2025-01-04T07:59:59.999Z")))'; + + const openInNewTab = true; + + const { + result: { current: navigateToAlertsPageWithFilters }, + } = renderHook(() => useNavigateToAlertsPageWithFilters()); + + navigateToAlertsPageWithFilters(filter, openInNewTab, timerange); + + expect(mockNavigateTo).toHaveBeenCalledWith({ + deepLinkId: SecurityPageName.alerts, + path: `?pageFilters=!((exclude:!f,existsSelected:!f,fieldName:'test field',hideActionBar:!f,selectedOptions:!('test value'),title:'test filter'))&timerange=(global:(timerange:(from:"2024-12-12T17:03:23.481Z",kind:absolute,to:"2025-01-04T07:59:59.999Z")))`, + openInNewTab: true, + }); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts index 037bac32d8c92..5acf972870901 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/hooks/use_navigate_to_alerts_page_with_filters.ts @@ -16,14 +16,27 @@ import { URL_PARAM_KEY } from './use_url_state'; export const useNavigateToAlertsPageWithFilters = () => { const { navigateTo } = useNavigation(); - return (filterItems: FilterControlConfig | FilterControlConfig[], openInNewTab = false) => { + return ( + /** + * Pass one or more filter control configurations to be applied to the alerts page filters + */ + filterItems: FilterControlConfig | FilterControlConfig[], + /** + * If true, the alerts page will be opened in a new tab + */ + openInNewTab = false, + /** + * Allows to customize the timerange url parameter. Should only be used in combination with the openInNewTab=true parameter + */ + timerange?: string + ) => { const urlFilterParams = encode( formatPageFilterSearchParam(Array.isArray(filterItems) ? filterItems : [filterItems]) ); - + const timerangePath = timerange ? `&timerange=${timerange}` : ''; navigateTo({ deepLinkId: SecurityPageName.alerts, - path: `?${URL_PARAM_KEY.pageFilter}=${urlFilterParams}`, + path: `?${URL_PARAM_KEY.pageFilter}=${urlFilterParams}${timerangePath}`, openInNewTab, }); }; From 8534f0a35d671240b1cff02d1d31a7f3b8bfe9a5 Mon Sep 17 00:00:00 2001 From: Mason Herron <46727170+Supplementing@users.noreply.github.com> Date: Fri, 3 Jan 2025 12:45:49 -0700 Subject: [PATCH 18/43] [Fleet] Improve text strings in new agent binary download section (#204988) ## Summary Closes #139151 Changed placeholder and help text to be more descriptive (using approved text from issue). ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks n/a --------- Co-authored-by: Elastic Machine --- .../plugins/private/translations/translations/fr-FR.json | 3 +-- .../plugins/private/translations/translations/ja-JP.json | 3 +-- .../plugins/private/translations/translations/zh-CN.json | 3 +-- .../components/download_source_flyout/index.test.tsx | 2 +- .../settings/components/download_source_flyout/index.tsx | 9 ++------- .../components/settings_page/agent_binary_section.tsx | 2 +- 6 files changed, 7 insertions(+), 15 deletions(-) diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 57b3b22ddfa7c..aad31d0c92b67 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -21262,7 +21262,6 @@ "xpack.fleet.settings.editDownloadSourcesFlyout.editTitle": "Modifier la source de binaire pour les agents", "xpack.fleet.settings.editDownloadSourcesFlyout.hostInputLabel": "Hôte", "xpack.fleet.settings.editDownloadSourcesFlyout.hostsInputDescription": "Adresse que vos agents utiliseront pour télécharger les fichiers binaires. Spécifiez le chemin d'accès au répertoire contenant les fichiers binaires. {guideLink}", - "xpack.fleet.settings.editDownloadSourcesFlyout.hostsInputPlaceholder": "Indiquer l’hôte", "xpack.fleet.settings.editDownloadSourcesFlyout.nameInputLabel": "Nom", "xpack.fleet.settings.editDownloadSourcesFlyout.nameInputPlaceholder": "Indiquer le nom", "xpack.fleet.settings.editDownloadSourcesFlyout.proxyIdLabel": "Proxy", @@ -49573,4 +49572,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "Ce champ est requis.", "xpack.watcher.watcherDescription": "Détectez les modifications survenant dans vos données en créant, gérant et monitorant des alertes." } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 4bebdb1cd1366..707b2b610fd64 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -21121,7 +21121,6 @@ "xpack.fleet.settings.editDownloadSourcesFlyout.editTitle": "エージェントバイナリソースを編集", "xpack.fleet.settings.editDownloadSourcesFlyout.hostInputLabel": "ホスト", "xpack.fleet.settings.editDownloadSourcesFlyout.hostsInputDescription": "エージェントがバイナリをダウンロードするために使用するダウンロード元アドレス。バイナリが含まれるディレクトリへのパスを指定します。{guideLink}", - "xpack.fleet.settings.editDownloadSourcesFlyout.hostsInputPlaceholder": "ホストを指定", "xpack.fleet.settings.editDownloadSourcesFlyout.nameInputLabel": "名前", "xpack.fleet.settings.editDownloadSourcesFlyout.nameInputPlaceholder": "名前を指定", "xpack.fleet.settings.editDownloadSourcesFlyout.proxyIdLabel": "プロキシ", @@ -49422,4 +49421,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 23655e9886d48..4a3ddd8ad1856 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -20792,7 +20792,6 @@ "xpack.fleet.settings.editDownloadSourcesFlyout.editTitle": "编辑代理二进制源", "xpack.fleet.settings.editDownloadSourcesFlyout.hostInputLabel": "主机", "xpack.fleet.settings.editDownloadSourcesFlyout.hostsInputDescription": "您的代理将用于从中下载其二进制文件的地址。指定包含二进制文件的目录的路径。{guideLink}", - "xpack.fleet.settings.editDownloadSourcesFlyout.hostsInputPlaceholder": "指定主机", "xpack.fleet.settings.editDownloadSourcesFlyout.nameInputLabel": "名称", "xpack.fleet.settings.editDownloadSourcesFlyout.nameInputPlaceholder": "指定名称", "xpack.fleet.settings.editDownloadSourcesFlyout.proxyIdLabel": "代理", @@ -48695,4 +48694,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.test.tsx index 56a0aeb9b33f8..f7cdf82845768 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.test.tsx @@ -27,7 +27,7 @@ describe('EditOutputFlyout', () => { expect(comp.queryByLabelText('Name')).not.toBeNull(); expect(comp.queryByLabelText('Host')).not.toBeNull(); expect(comp.queryByPlaceholderText('Specify name')).not.toBeNull(); - expect(comp.queryByPlaceholderText('Specify host')).not.toBeNull(); + expect(comp.queryByPlaceholderText('https://artifacts.elastic.co/downloads')).not.toBeNull(); }); it('should render the flyout if the provided download source is valid', async () => { diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx index 8249fd6fc82f3..905dfed8f6e7c 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/settings/components/download_source_flyout/index.tsx @@ -104,7 +104,7 @@ export const EditDownloadSourceFlyout: React.FunctionComponent @@ -129,12 +129,7 @@ export const EditDownloadSourceFlyout: React.FunctionComponent From 283bcf5a155ae14c4217a02d6850da43eb8eea15 Mon Sep 17 00:00:00 2001 From: Kurt Date: Fri, 3 Jan 2025 15:39:25 -0500 Subject: [PATCH 19/43] Upgrading APM Node (#205440) ## Summary Upgrade `elastic-apm-node` from 4.9.0 to 4.10.0 [CHANGELOG](https://github.com/elastic/apm-agent-nodejs/blob/main/CHANGELOG.asciidoc) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b8db84b23f605..b181416d8547d 100644 --- a/package.json +++ b/package.json @@ -1134,7 +1134,7 @@ "del": "^6.1.0", "diff": "^5.1.0", "dotenv": "^16.4.5", - "elastic-apm-node": "^4.9.0", + "elastic-apm-node": "^4.10.0", "email-addresses": "^5.0.0", "eventsource-parser": "^3.0.0", "execa": "^5.1.1", diff --git a/yarn.lock b/yarn.lock index 8b0934ef49def..3387a8df6156f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17732,10 +17732,10 @@ elastic-apm-node@3.46.0: traverse "^0.6.6" unicode-byte-truncate "^1.0.0" -elastic-apm-node@^4.9.0: - version "4.9.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.9.0.tgz#8ca807fdf64404b615b60c4090bd9c9aad5b78ed" - integrity sha512-VUbrahqHmqpfwGHtUysWJ7aeCIUm61hAw8tSu0gE9AYdriVSPod4Ki11X4K8sBri0bIc/FTHxOrg7kudhGoqHQ== +elastic-apm-node@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.10.0.tgz#b0560f1f49ac4f7633a810d4d7f727e45f8ebe20" + integrity sha512-QmnKLArA84lJ4mNoUTi9qMUfLE1nUeFLB+VO/7iG6UOTiAdR5Wxk0nu3SpcgFe+tr+4GkyXM2xXdmL2XYfQ1Qg== dependencies: "@elastic/ecs-pino-format" "^1.5.0" "@opentelemetry/api" "^1.4.1" From f7b9d15df44320f307b890534b0453d9fdcbb719 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Fri, 3 Jan 2025 16:01:05 -0500 Subject: [PATCH 20/43] [Obs AI Assistant] Add alerts test to serverless (#205524) (#205530) Closes https://github.com/elastic/kibana/issues/205524 ## Summary Adds the alerts.spec to serverless tests. It's skipped in MKI because of the LLM proxy. There is an open issue for this - https://github.com/elastic/kibana/issues/192751 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../tests/complete/functions/alerts.spec.ts | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/alerts.spec.ts diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/alerts.spec.ts b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/alerts.spec.ts new file mode 100644 index 0000000000000..47e46f12b82ee --- /dev/null +++ b/x-pack/test_serverless/api_integration/test_suites/observability/ai_assistant/tests/complete/functions/alerts.spec.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MessageAddEvent, MessageRole } from '@kbn/observability-ai-assistant-plugin/common'; +import expect from '@kbn/expect'; +import { + LlmProxy, + createLlmProxy, +} from '@kbn/test-suites-xpack/observability_ai_assistant_api_integration/common/create_llm_proxy'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { getMessageAddedEvents, invokeChatCompleteWithFunctionRequest } from './helpers'; +import { + createProxyActionConnector, + deleteActionConnector, +} from '../../../common/action_connectors'; +import type { InternalRequestHeader, RoleCredentials } from '../../../../../../../shared/services'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const log = getService('log'); + const svlUserManager = getService('svlUserManager'); + const svlCommonApi = getService('svlCommonApi'); + const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + + describe('when calling the alerts function', function () { + // TODO: https://github.com/elastic/kibana/issues/192751 + this.tags(['skipMKI']); + let roleAuthc: RoleCredentials; + let internalReqHeader: InternalRequestHeader; + let proxy: LlmProxy; + let connectorId: string; + let alertsEvents: MessageAddEvent[]; + + const start = 'now-100h'; + const end = 'now'; + + before(async () => { + roleAuthc = await svlUserManager.createM2mApiKeyWithRoleScope('editor'); + internalReqHeader = svlCommonApi.getInternalRequestHeader(); + proxy = await createLlmProxy(log); + connectorId = await createProxyActionConnector({ + supertest, + log, + port: proxy.getPort(), + roleAuthc, + internalReqHeader, + }); + + void proxy + .intercept('conversation', () => true, 'Hello from LLM Proxy') + .completeAfterIntercept(); + + const alertsResponseBody = await invokeChatCompleteWithFunctionRequest({ + connectorId, + observabilityAIAssistantAPIClient, + functionCall: { + name: 'alerts', + trigger: MessageRole.Assistant, + arguments: JSON.stringify({ start, end }), + }, + }); + + await proxy.waitForAllInterceptorsSettled(); + + alertsEvents = getMessageAddedEvents(alertsResponseBody); + }); + + after(async () => { + proxy.close(); + await deleteActionConnector({ supertest, connectorId, log, roleAuthc, internalReqHeader }); + await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); + }); + + // This test ensures that invoking the alerts function does not result in an error. + it('should execute the function without any errors', async () => { + const alertsFunctionResponse = alertsEvents[0]; + expect(alertsFunctionResponse.message.message.name).to.be('alerts'); + + const parsedAlertsResponse = JSON.parse(alertsFunctionResponse.message.message.content!); + + expect(parsedAlertsResponse).not.to.have.property('error'); + expect(parsedAlertsResponse).to.have.property('total'); + expect(parsedAlertsResponse).to.have.property('alerts'); + expect(parsedAlertsResponse.alerts).to.be.an('array'); + expect(parsedAlertsResponse.total).to.be(0); + expect(parsedAlertsResponse.alerts.length).to.be(0); + }); + }); +} From 2b9104c3278e1684c31b8ffd623a428b178238c0 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Fri, 3 Jan 2025 13:10:37 -0800 Subject: [PATCH 21/43] [kbn-grid-layout] EUI Visual Refresh Integration (#204445) ## Summary Related to https://github.com/elastic/kibana/issues/203132. Closes [#204592](https://github.com/elastic/kibana/issues/204592). This replaces all references to euiThemeVars in favor of the useEuiTheme hook in the `kbn-grid-layout` package. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] [See some risk examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx) - [ ] ... --- packages/kbn-grid-layout/grid/drag_preview.tsx | 8 ++++---- .../grid/grid_panel/drag_handle.tsx | 17 ++++++++--------- .../grid/grid_panel/grid_panel.tsx | 5 +++-- .../grid/grid_panel/resize_handle.tsx | 15 ++++++++------- .../kbn-grid-layout/grid/grid_row/grid_row.tsx | 11 ++++++----- .../grid/use_grid_layout_state.ts | 11 ++++++++--- .../kbn-grid-layout/grid/utils/mobile_view.ts | 5 ++--- packages/kbn-grid-layout/tsconfig.json | 1 - 8 files changed, 39 insertions(+), 34 deletions(-) diff --git a/packages/kbn-grid-layout/grid/drag_preview.tsx b/packages/kbn-grid-layout/grid/drag_preview.tsx index 24aa81ffea1df..83823bf80d64f 100644 --- a/packages/kbn-grid-layout/grid/drag_preview.tsx +++ b/packages/kbn-grid-layout/grid/drag_preview.tsx @@ -10,9 +10,8 @@ import React, { useEffect, useRef } from 'react'; import { combineLatest, skip } from 'rxjs'; -import { transparentize } from '@elastic/eui'; +import { transparentize, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; import { GridLayoutStateManager } from './types'; @@ -24,6 +23,7 @@ export const DragPreview = ({ gridLayoutStateManager: GridLayoutStateManager; }) => { const dragPreviewRef = useRef(null); + const { euiTheme } = useEuiTheme(); useEffect( () => { @@ -62,8 +62,8 @@ export const DragPreview = ({ css={css` display: none; pointer-events: none; - border-radius: ${euiThemeVars.euiBorderRadius}; - background-color: ${transparentize(euiThemeVars.euiColorSuccess, 0.2)}; + border-radius: ${euiTheme.border.radius}; + background-color: ${transparentize(euiTheme.colors.accentSecondary, 0.2)}; transition: opacity 100ms linear; `} /> diff --git a/packages/kbn-grid-layout/grid/grid_panel/drag_handle.tsx b/packages/kbn-grid-layout/grid/grid_panel/drag_handle.tsx index f175cf227a7e5..688ba6d3a4a5d 100644 --- a/packages/kbn-grid-layout/grid/grid_panel/drag_handle.tsx +++ b/packages/kbn-grid-layout/grid/grid_panel/drag_handle.tsx @@ -11,7 +11,6 @@ import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } import { EuiIcon, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; import { i18n } from '@kbn/i18n'; import { GridLayoutStateManager, PanelInteractionEvent } from '../types'; @@ -101,17 +100,17 @@ export const DragHandle = React.forwardRef< position: absolute; align-items: center; justify-content: center; - top: -${euiThemeVars.euiSizeL}; - width: ${euiThemeVars.euiSizeL}; - height: ${euiThemeVars.euiSizeL}; - z-index: ${euiThemeVars.euiZLevel3}; - margin-left: ${euiThemeVars.euiSizeS}; + top: -${euiTheme.size.l}; + width: ${euiTheme.size.l}; + height: ${euiTheme.size.l}; + z-index: ${euiTheme.levels.modal}; + margin-left: ${euiTheme.size.s}; border: 1px solid ${euiTheme.border.color}; border-bottom: none; - background-color: ${euiTheme.colors.emptyShade}; - border-radius: ${euiThemeVars.euiBorderRadius} ${euiThemeVars.euiBorderRadius} 0 0; + background-color: ${euiTheme.colors.backgroundBasePlain}; + border-radius: ${euiTheme.border.radius} ${euiTheme.border.radius} 0 0; cursor: grab; - transition: ${euiThemeVars.euiAnimSpeedSlow} opacity; + transition: ${euiTheme.animation.slow} opacity; .kbnGridPanel:hover &, .kbnGridPanel:focus-within &, &:active, diff --git a/packages/kbn-grid-layout/grid/grid_panel/grid_panel.tsx b/packages/kbn-grid-layout/grid/grid_panel/grid_panel.tsx index c30e6ecc996eb..8bd35a7d06ef7 100644 --- a/packages/kbn-grid-layout/grid/grid_panel/grid_panel.tsx +++ b/packages/kbn-grid-layout/grid/grid_panel/grid_panel.tsx @@ -10,8 +10,8 @@ import React, { forwardRef, useEffect, useMemo, useState } from 'react'; import { combineLatest, skip } from 'rxjs'; +import { useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; -import { euiThemeVars } from '@kbn/ui-theme'; import { GridLayoutStateManager, PanelInteractionEvent } from '../types'; import { getKeysInOrder } from '../utils/resolve_grid_row'; @@ -38,6 +38,7 @@ export const GridPanel = forwardRef( panelRef ) => { const [dragHandleApi, setDragHandleApi] = useState(null); + const { euiTheme } = useEuiTheme(); useEffect(() => { const onDropEventHandler = (dropEvent: MouseEvent) => interactionStart('drop', dropEvent); @@ -107,7 +108,7 @@ export const GridPanel = forwardRef( // if the current panel is active, give it fixed positioning depending on the interaction event const { position: draggingPosition } = activePanel; - ref.style.zIndex = `${euiThemeVars.euiZModal}`; + ref.style.zIndex = `${euiTheme.levels.modal}`; if (currentInteractionEvent?.type === 'resize') { // if the current panel is being resized, ensure it is not shrunk past the size of a single cell ref.style.width = `${Math.max( diff --git a/packages/kbn-grid-layout/grid/grid_panel/resize_handle.tsx b/packages/kbn-grid-layout/grid/grid_panel/resize_handle.tsx index ffee2f2764ed0..59e81802651c6 100644 --- a/packages/kbn-grid-layout/grid/grid_panel/resize_handle.tsx +++ b/packages/kbn-grid-layout/grid/grid_panel/resize_handle.tsx @@ -9,8 +9,8 @@ import { transparentize } from '@elastic/eui'; import { css } from '@emotion/react'; +import { useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { euiThemeVars } from '@kbn/ui-theme'; import React from 'react'; import { PanelInteractionEvent } from '../types'; @@ -22,6 +22,7 @@ export const ResizeHandle = ({ e: MouseEvent | React.MouseEvent ) => void; }) => { + const { euiTheme } = useEuiTheme(); return (