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'));