From 9d38922401d0bbd0d95d750f68fec77ca22758fb Mon Sep 17 00:00:00 2001 From: Marco Antonio Ghiani Date: Thu, 14 Nov 2024 11:00:38 +0100 Subject: [PATCH] [One Discover] Add app menu actions for Observability projects (#198987) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📓 Summary Closes #182230 This work introduces a new observability root profile and uses the new extension point to register custom actions on the app menu. The registered actions and link will appear only with the new project navigation enabled on an Observability project: - A link to the data sets quality page - On the alerts sub menu... - replace the default search rule creation with the observability custom threshold rule - add an entry to directly create an SLO for the current search To access the SLO capabilities without breaking the dependencies hierarchy of the new sustainable architecture, the feature is registered by the common plugin `discover-shared` in SLO and consumed then by Discover using the IoC principle. ## 🖼️ Screenshots ### Observability project solution - show new menu Screenshot 2024-11-06 at 12 37 02 ### Search project solution - hide new menu Screenshot 2024-11-06 at 12 36 19 ### Default navigation mode - hide new menu Screenshot 2024-11-06 at 12 35 43 ## 🎥 Demo https://github.com/user-attachments/assets/104e6074-0401-4fd2-a8e6-8b05f2c070d7 --------- Co-authored-by: Marco Antonio Ghiani Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/discover/kibana.jsonc | 5 +- src/plugins/discover/public/build_services.ts | 3 + .../accessors/get_app_menu.ts | 161 ++++++++++++++++++ .../accessors/index.ts | 10 ++ .../observability_root_profile/index.ts | 10 ++ .../profile.test.ts | 51 ++++++ .../observability_root_profile/profile.tsx | 28 +++ .../register_profile_providers.ts | 2 + src/plugins/discover/public/types.ts | 2 + src/plugins/discover/tsconfig.json | 3 +- .../services/discover_features/types.ts | 10 +- .../observability_solution/slo/kibana.jsonc | 1 + .../slo/public/plugin.ts | 16 +- .../slo/public/types.ts | 2 + .../utils/get_lazy_with_context_providers.tsx | 5 +- .../observability_solution/slo/tsconfig.json | 1 + .../discover/search_source_alert.ts | 2 +- .../context_awareness/_get_app_menu.ts | 81 +++++++++ .../discover/context_awareness/index.ts | 40 +++++ .../test_suites/observability/index.ts | 1 + 20 files changed, 424 insertions(+), 10 deletions(-) create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/get_app_menu.ts create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/index.ts create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/index.ts create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts create mode 100644 src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.tsx create mode 100644 x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/_get_app_menu.ts create mode 100644 x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/index.ts diff --git a/src/plugins/discover/kibana.jsonc b/src/plugins/discover/kibana.jsonc index 87837a38ed834..f605d0ae1df95 100644 --- a/src/plugins/discover/kibana.jsonc +++ b/src/plugins/discover/kibana.jsonc @@ -15,6 +15,7 @@ "charts", "data", "dataViews", + "discoverShared", "embeddable", "inspector", "fieldFormats", @@ -30,7 +31,7 @@ "unifiedDocViewer", "unifiedSearch", "unifiedHistogram", - "contentManagement" + "contentManagement", ], "optionalPlugins": [ "dataVisualizer", @@ -46,7 +47,7 @@ "observabilityAIAssistant", "aiops", "fieldsMetadata", - "logsDataAccess" + "logsDataAccess", ], "requiredBundles": [ "kibanaUtils", diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index bae2907af7699..df194bc03fa0f 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -59,6 +59,7 @@ import type { AiopsPluginStart } from '@kbn/aiops-plugin/public'; import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; import type { DiscoverStartPlugins } from './types'; import type { DiscoverContextAppLocator } from './application/context/services/locator'; import type { DiscoverSingleDocLocator } from './application/doc/locator'; @@ -89,6 +90,7 @@ export interface DiscoverServices { chrome: ChromeStart; core: CoreStart; data: DataPublicPluginStart; + discoverShared: DiscoverSharedPublicStart; docLinks: DocLinksStart; embeddable: EmbeddableStart; history: History; @@ -178,6 +180,7 @@ export const buildServices = memoize( core, data: plugins.data, dataVisualizer: plugins.dataVisualizer, + discoverShared: plugins.discoverShared, docLinks: core.docLinks, embeddable: plugins.embeddable, i18n: core.i18n, diff --git a/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/get_app_menu.ts b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/get_app_menu.ts new file mode 100644 index 0000000000000..759765e93767f --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/get_app_menu.ts @@ -0,0 +1,161 @@ +/* + * 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 { AppMenuActionId, AppMenuActionType, AppMenuRegistry } from '@kbn/discover-utils'; +import { DATA_QUALITY_LOCATOR_ID, DataQualityLocatorParams } from '@kbn/deeplinks-observability'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { isOfQueryType } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; +import { AppMenuExtensionParams } from '../../../..'; +import type { RootProfileProvider } from '../../../../profiles'; +import { ProfileProviderServices } from '../../../profile_provider_services'; + +export const createGetAppMenu = + (services: ProfileProviderServices): RootProfileProvider['profile']['getAppMenu'] => + (prev) => + (params) => { + const prevValue = prev(params); + + return { + appMenuRegistry: (registry) => { + // Register custom link actions + registerDatasetQualityLink(registry, services); + // Register alerts sub menu actions + registerCreateSLOAction(registry, services, params); + registerCustomThresholdRuleAction(registry, services, params); + + return prevValue.appMenuRegistry(registry); + }, + }; + }; + +const registerDatasetQualityLink = ( + registry: AppMenuRegistry, + { share, timefilter }: ProfileProviderServices +) => { + const dataQualityLocator = + share?.url.locators.get(DATA_QUALITY_LOCATOR_ID); + + if (dataQualityLocator) { + registry.registerCustomAction({ + id: 'dataset-quality-link', + type: AppMenuActionType.custom, + controlProps: { + label: i18n.translate('discover.observabilitySolution.appMenu.datasets', { + defaultMessage: 'Data sets', + }), + testId: 'discoverAppMenuDatasetQualityLink', + onClick: ({ onFinishAction }) => { + const refresh = timefilter.getRefreshInterval(); + const { from, to } = timefilter.getTime(); + + dataQualityLocator.navigate({ + filters: { + timeRange: { + from: from ?? 'now-24h', + to: to ?? 'now', + refresh, + }, + }, + }); + + onFinishAction(); + }, + }, + }); + } +}; + +const registerCustomThresholdRuleAction = ( + registry: AppMenuRegistry, + { data, triggersActionsUi }: ProfileProviderServices, + { dataView }: AppMenuExtensionParams +) => { + registry.registerCustomActionUnderSubmenu(AppMenuActionId.alerts, { + id: AppMenuActionId.createRule, + type: AppMenuActionType.custom, + order: 101, + controlProps: { + label: i18n.translate('discover.observabilitySolution.appMenu.customThresholdRule', { + defaultMessage: 'Create custom threshold rule', + }), + iconType: 'visGauge', + testId: 'discoverAppMenuCustomThresholdRule', + onClick: ({ onFinishAction }) => { + const index = dataView?.toMinimalSpec(); + const { filters, query } = data.query.getState(); + + return triggersActionsUi.getAddRuleFlyout({ + consumer: 'logs', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + canChangeTrigger: false, + initialValues: { + params: { + searchConfiguration: { + index, + query, + filter: filters, + }, + }, + }, + onClose: onFinishAction, + }); + }, + }, + }); +}; + +const registerCreateSLOAction = ( + registry: AppMenuRegistry, + { data, discoverShared }: ProfileProviderServices, + { dataView, isEsqlMode }: AppMenuExtensionParams +) => { + const sloFeature = discoverShared.features.registry.getById('observability-create-slo'); + + if (sloFeature) { + registry.registerCustomActionUnderSubmenu(AppMenuActionId.alerts, { + id: 'create-slo', + type: AppMenuActionType.custom, + order: 102, + controlProps: { + label: i18n.translate('discover.observabilitySolution.appMenu.slo', { + defaultMessage: 'Create SLO', + }), + iconType: 'bell', + testId: 'discoverAppMenuCreateSlo', + onClick: ({ onFinishAction }) => { + const index = dataView?.getIndexPattern(); + const timestampField = dataView?.timeFieldName; + const { filters, query: kqlQuery } = data.query.getState(); + + const filter = isEsqlMode + ? {} + : { + kqlQuery: isOfQueryType(kqlQuery) ? kqlQuery.query : '', + filters: filters?.map(({ meta, query }) => ({ meta, query })), + }; + + return sloFeature.createSLOFlyout({ + initialValues: { + indicator: { + type: 'sli.kql.custom', + params: { + index, + timestampField, + filter, + }, + }, + }, + onClose: onFinishAction, + }); + }, + }, + }); + } +}; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/index.ts new file mode 100644 index 0000000000000..4a719e634621e --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/accessors/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", 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". + */ + +export { createGetAppMenu } from './get_app_menu'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/index.ts b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/index.ts new file mode 100644 index 0000000000000..ad888b42b5610 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", 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". + */ + +export { createObservabilityRootProfileProvider } from './profile'; diff --git a/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts new file mode 100644 index 0000000000000..b83afb266f2bd --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", 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 { SolutionType } from '../../../profiles'; +import { createContextAwarenessMocks } from '../../../__mocks__'; +import { createObservabilityRootProfileProvider } from './profile'; + +const mockServices = createContextAwarenessMocks().profileProviderServices; + +describe('observabilityRootProfileProvider', () => { + const observabilityRootProfileProvider = createObservabilityRootProfileProvider(mockServices); + const RESOLUTION_MATCH = { + isMatch: true, + context: { solutionType: SolutionType.Observability }, + }; + const RESOLUTION_MISMATCH = { + isMatch: false, + }; + + it('should match when the solution project is observability', () => { + expect( + observabilityRootProfileProvider.resolve({ + solutionNavId: SolutionType.Observability, + }) + ).toEqual(RESOLUTION_MATCH); + }); + + it('should NOT match when the solution project anything but observability', () => { + expect( + observabilityRootProfileProvider.resolve({ + solutionNavId: SolutionType.Default, + }) + ).toEqual(RESOLUTION_MISMATCH); + expect( + observabilityRootProfileProvider.resolve({ + solutionNavId: SolutionType.Search, + }) + ).toEqual(RESOLUTION_MISMATCH); + expect( + observabilityRootProfileProvider.resolve({ + solutionNavId: SolutionType.Security, + }) + ).toEqual(RESOLUTION_MISMATCH); + }); +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.tsx b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.tsx new file mode 100644 index 0000000000000..d4b10c8d0a095 --- /dev/null +++ b/src/plugins/discover/public/context_awareness/profile_providers/observability/observability_root_profile/profile.tsx @@ -0,0 +1,28 @@ +/* + * 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 { RootProfileProvider, SolutionType } from '../../../profiles'; +import { ProfileProviderServices } from '../../profile_provider_services'; +import { createGetAppMenu } from './accessors'; + +export const createObservabilityRootProfileProvider = ( + services: ProfileProviderServices +): RootProfileProvider => ({ + profileId: 'observability-root-profile', + profile: { + getAppMenu: createGetAppMenu(services), + }, + resolve: (params) => { + if (params.solutionNavId === SolutionType.Observability) { + return { isMatch: true, context: { solutionType: SolutionType.Observability } }; + } + + return { isMatch: false }; + }, +}); diff --git a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts index 997edac1bae57..cb4146d1b99bb 100644 --- a/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts +++ b/src/plugins/discover/public/context_awareness/profile_providers/register_profile_providers.ts @@ -27,6 +27,7 @@ import { ProfileProviderServices, } from './profile_provider_services'; import type { DiscoverServices } from '../../build_services'; +import { createObservabilityRootProfileProvider } from './observability/observability_root_profile'; /** * Register profile providers for root, data source, and document contexts to the profile profile services @@ -122,6 +123,7 @@ const createRootProfileProviders = (providerServices: ProfileProviderServices) = createExampleRootProfileProvider(), createExampleSolutionViewRootProfileProvider(), createSecurityRootProfileProvider(providerServices), + createObservabilityRootProfileProvider(providerServices), ]; /** diff --git a/src/plugins/discover/public/types.ts b/src/plugins/discover/public/types.ts index 3b24341e1a654..2ef380db98703 100644 --- a/src/plugins/discover/public/types.ts +++ b/src/plugins/discover/public/types.ts @@ -42,6 +42,7 @@ import type { AiopsPluginStart } from '@kbn/aiops-plugin/public'; import type { DataVisualizerPluginStart } from '@kbn/data-visualizer-plugin/public'; import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public'; import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; import { DiscoverAppLocator } from '../common'; import { DiscoverCustomizationContext } from './customizations'; import { type DiscoverContainerProps } from './components/discover_container'; @@ -151,6 +152,7 @@ export interface DiscoverStartPlugins { dataViewFieldEditor: IndexPatternFieldEditorStart; dataViews: DataViewsServicePublic; dataVisualizer?: DataVisualizerPluginStart; + discoverShared: DiscoverSharedPublicStart; embeddable: EmbeddableStart; expressions: ExpressionsStart; fieldFormats: FieldFormatsStart; diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index 72d5594ba40f0..1bb3aa10acce0 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -98,7 +98,8 @@ "@kbn/logs-data-access-plugin", "@kbn/core-lifecycle-browser", "@kbn/discover-contextual-components", - "@kbn/esql-ast" + "@kbn/esql-ast", + "@kbn/discover-shared-plugin" ], "exclude": [ "target/**/*" diff --git a/src/plugins/discover_shared/public/services/discover_features/types.ts b/src/plugins/discover_shared/public/services/discover_features/types.ts index ebfa3970e96e4..cdf78b3335507 100644 --- a/src/plugins/discover_shared/public/services/discover_features/types.ts +++ b/src/plugins/discover_shared/public/services/discover_features/types.ts @@ -30,8 +30,16 @@ export interface ObservabilityLogsAIAssistantFeature { render: (deps: ObservabilityLogsAIAssistantFeatureRenderDeps) => JSX.Element; } +export interface ObservabilityCreateSLOFeature { + id: 'observability-create-slo'; + createSLOFlyout: (props: { + onClose: () => void; + initialValues: Record; + }) => React.ReactNode; +} + // This should be a union of all the available client features. -export type DiscoverFeature = ObservabilityLogsAIAssistantFeature; +export type DiscoverFeature = ObservabilityLogsAIAssistantFeature | ObservabilityCreateSLOFeature; /** * Service types diff --git a/x-pack/plugins/observability_solution/slo/kibana.jsonc b/x-pack/plugins/observability_solution/slo/kibana.jsonc index e5732ee25e7e1..79302b58f8269 100644 --- a/x-pack/plugins/observability_solution/slo/kibana.jsonc +++ b/x-pack/plugins/observability_solution/slo/kibana.jsonc @@ -22,6 +22,7 @@ "dashboard", "data", "dataViews", + "discoverShared", "lens", "dataViewEditor", "dataViewFieldEditor", diff --git a/x-pack/plugins/observability_solution/slo/public/plugin.ts b/x-pack/plugins/observability_solution/slo/public/plugin.ts index e61910e108a7d..9a1b5f3267b86 100644 --- a/x-pack/plugins/observability_solution/slo/public/plugin.ts +++ b/x-pack/plugins/observability_solution/slo/public/plugin.ts @@ -198,6 +198,7 @@ export class SLOPlugin public start(core: CoreStart, plugins: SLOPublicPluginsStart) { const kibanaVersion = this.initContext.env.packageInfo.version; + const sloClient = createRepositoryClient(core); const lazyWithContextProviders = getLazyWithContextProviders({ @@ -212,11 +213,18 @@ export class SLOPlugin sloClient, }); + const getCreateSLOFlyout = lazyWithContextProviders( + lazy(() => import('./pages/slo_edit/shared_flyout/slo_add_form_flyout')), + { spinnerSize: 'm' } + ); + + plugins.discoverShared.features.registry.register({ + id: 'observability-create-slo', + createSLOFlyout: getCreateSLOFlyout, + }); + return { - getCreateSLOFlyout: lazyWithContextProviders( - lazy(() => import('./pages/slo_edit/shared_flyout/slo_add_form_flyout')), - { spinnerSize: 'm' } - ), + getCreateSLOFlyout, }; } diff --git a/x-pack/plugins/observability_solution/slo/public/types.ts b/x-pack/plugins/observability_solution/slo/public/types.ts index 2c66b340df6d3..1397b08a29528 100644 --- a/x-pack/plugins/observability_solution/slo/public/types.ts +++ b/x-pack/plugins/observability_solution/slo/public/types.ts @@ -14,6 +14,7 @@ import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; +import type { DiscoverSharedPublicStart } from '@kbn/discover-shared-plugin/public'; import type { EmbeddableSetup, EmbeddableStart } from '@kbn/embeddable-plugin/public'; import { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { LensPublicStart } from '@kbn/lens-plugin/public'; @@ -77,6 +78,7 @@ export interface SLOPublicPluginsStart { dataViewFieldEditor: DataViewFieldEditorStart; dataViews: DataViewsPublicPluginStart; discover?: DiscoverStart; + discoverShared: DiscoverSharedPublicStart; embeddable: EmbeddableStart; fieldFormats: FieldFormatsStart; lens: LensPublicStart; diff --git a/x-pack/plugins/observability_solution/slo/public/utils/get_lazy_with_context_providers.tsx b/x-pack/plugins/observability_solution/slo/public/utils/get_lazy_with_context_providers.tsx index a43aa9e7bff59..49bb461b97af0 100644 --- a/x-pack/plugins/observability_solution/slo/public/utils/get_lazy_with_context_providers.tsx +++ b/x-pack/plugins/observability_solution/slo/public/utils/get_lazy_with_context_providers.tsx @@ -47,7 +47,10 @@ export const getLazyWithContextProviders = experimentalFeatures, sloClient, }: Props) => - (LazyComponent: React.LazyExoticComponent, options?: Options): React.FunctionComponent => { + >( + LazyComponent: React.LazyExoticComponent, + options?: Options + ): React.FunctionComponent> => { const { spinnerSize = 'xl' } = options ?? {}; const queryClient = new QueryClient(); return (props) => ( diff --git a/x-pack/plugins/observability_solution/slo/tsconfig.json b/x-pack/plugins/observability_solution/slo/tsconfig.json index 4b05b5aa0b063..23efcc39698b1 100644 --- a/x-pack/plugins/observability_solution/slo/tsconfig.json +++ b/x-pack/plugins/observability_solution/slo/tsconfig.json @@ -96,6 +96,7 @@ "@kbn/core-theme-browser", "@kbn/ebt-tools", "@kbn/observability-alerting-rule-utils", + "@kbn/discover-shared-plugin", "@kbn/server-route-repository-client", "@kbn/server-route-repository-utils" ] diff --git a/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts b/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts index 69db169209d59..d4653ca02f6f1 100644 --- a/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts +++ b/x-pack/test_serverless/functional/test_suites/common/discover_ml_uptime/discover/search_source_alert.ts @@ -367,7 +367,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('Search source Alert', function () { // see details: https://github.com/elastic/kibana/issues/193842 - this.tags(['failsOnMKI']); + this.tags(['failsOnMKI', 'skipSvlOblt']); before(async () => { await security.testUser.setRoles(['discover_alert']); await PageObjects.svlCommonPage.loginAsAdmin(); diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/_get_app_menu.ts b/x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/_get_app_menu.ts new file mode 100644 index 0000000000000..2be17df28d12f --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/_get_app_menu.ts @@ -0,0 +1,81 @@ +/* + * 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 kbnRison from '@kbn/rison'; +import type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { common, svlCommonPage } = getPageObjects([ + 'common', + 'timePicker', + 'discover', + 'header', + 'timePicker', + 'svlCommonPage', + ]); + const browser = getService('browser'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + describe('extension getAppMenu', () => { + before(async () => { + await svlCommonPage.loginAsAdmin(); + await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + after(async () => { + await esArchiver.unload('test/functional/fixtures/es_archiver/logstash_functional'); + }); + + beforeEach(async () => { + const state = kbnRison.encode({ + dataSource: { type: 'esql' }, + query: { esql: 'from logstash* | sort @timestamp desc' }, + }); + await common.navigateToActualUrl('discover', `?_a=${state}`, { + ensureCurrentUrl: false, + }); + }); + + it('should display a "Add data" link to navigate to the onboading page', async () => { + const link = await testSubjects.find('discoverAppMenuDatasetQualityLink'); + await link.click(); + + await retry.try(async () => { + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/app/management/data/data_quality`); + }); + }); + + it('should display a "Create custom threshold rule" action under the Alerts menu to create an o11y alert', async () => { + const alertsButton = await testSubjects.find('discoverAlertsButton'); + await alertsButton.click(); + + const createRuleButton = await testSubjects.find('discoverAppMenuCustomThresholdRule'); + await createRuleButton.click(); + + const ruleTitleElement = await testSubjects.find('selectedRuleTypeTitle'); + + await retry.try(async () => { + expect(await ruleTitleElement.getVisibleText()).to.equal('Custom threshold'); + }); + }); + + it('should display a "Create SLO" action under the Alerts menu to create an o11y alert', async () => { + const alertsButton = await testSubjects.find('discoverAlertsButton'); + await alertsButton.click(); + + const createSLOButton = await testSubjects.find('discoverAppMenuCreateSlo'); + await createSLOButton.click(); + + const sloTitleElement = await testSubjects.find('addSLOFlyoutTitle'); + expect(await sloTitleElement.getVisibleText()).to.equal('Create SLO'); + }); + }); +} diff --git a/x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/index.ts b/x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/index.ts new file mode 100644 index 0000000000000..c8277b273f428 --- /dev/null +++ b/x-pack/test_serverless/functional/test_suites/observability/discover/context_awareness/index.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 type { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker', 'svlCommonPage']); + const from = '2024-06-10T14:00:00.000Z'; + const to = '2024-06-10T16:30:00.000Z'; + + describe('discover/observabilitySolution/context_awareness', function () { + this.tags(['esGate']); + + before(async () => { + await esArchiver.load('test/functional/fixtures/es_archiver/discover/context_awareness'); + await kibanaServer.importExport.load( + 'test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await kibanaServer.uiSettings.update({ + 'timepicker:timeDefaults': `{ "from": "${from}", "to": "${to}"}`, + }); + }); + + after(async () => { + await esArchiver.unload('test/functional/fixtures/es_archiver/discover/context_awareness'); + await kibanaServer.importExport.unload( + 'test/functional/fixtures/kbn_archiver/discover/context_awareness' + ); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + }); + + loadTestFile(require.resolve('./_get_app_menu')); + }); +} 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 566f2b8e6854e..0885a319636b1 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/index.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/index.ts @@ -15,6 +15,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { 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')); loadTestFile(require.resolve('./rules/rules_list')); loadTestFile(require.resolve('./cases'));