From d157214e1a3bdbdbf6de7fa1d3b622ae65335808 Mon Sep 17 00:00:00 2001 From: Bryce Buchanan <75274611+bryce-b@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:08:51 -0700 Subject: [PATCH] Logs Essentials for Observability (#223030) ## Summary disables features under Application for serverless-essentials. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- config/serverless.oblt.complete.yml | 17 +++++- config/serverless.oblt.logs_essentials.yml | 38 ++++++++++++ config/serverless.oblt.yml | 17 ------ .../public/application/application.test.tsx | 60 ++++++++++++++++++- .../public/application/index.tsx | 16 +++-- .../observability/public/routes/routes.tsx | 27 +++++---- .../plugins/observability/tsconfig.json | 3 +- .../serverless_observability/jest.config.js | 18 ++++++ .../public/navigation_tree.test.ts | 36 +++++++++++ .../public/navigation_tree.ts | 18 ++++-- .../serverless_observability/public/plugin.ts | 5 +- .../serverless_observability/server/plugin.ts | 14 ++++- .../services/svl_oblt_navigation.ts | 8 ++- .../logs_essentials_only/navigation.ts | 2 +- 14 files changed, 229 insertions(+), 50 deletions(-) create mode 100644 x-pack/solutions/observability/plugins/serverless_observability/jest.config.js create mode 100644 x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.test.ts diff --git a/config/serverless.oblt.complete.yml b/config/serverless.oblt.complete.yml index 58bddccef8b2..8ab6ea4fb9cb 100644 --- a/config/serverless.oblt.complete.yml +++ b/config/serverless.oblt.complete.yml @@ -3,7 +3,22 @@ ## Enabled plugins xpack.infra.enabled: true xpack.slo.enabled: true +xpack.uptime.enabled: true xpack.features.overrides: - ### By default, this feature named as `Metrics`, but should be renamed to `Infrastructure`. infrastructure.name: 'Infrastructure' + ### By default, this feature named as `APM and User Experience`, but should be renamed to `Applications`. + apm.name: 'Applications' + ### Dashboards feature should be moved from Analytics category to the Observability one. + dashboard_v2.category: 'observability' + ### Discover feature should be moved from Analytics category to the Observability one and its privileges are + ### fine-tuned to grant access to Observability app. + discover_v2.category: 'observability' + ### Machine Learning feature should be moved from Analytics category to the Observability one and renamed to `AI Ops`. + ml: + category: 'observability' + order: 1200 + ### Stack alerts is hidden in Role management since it's not needed. + stackAlerts.hidden: true + ### By default, this feature named as `Synthetics and Uptime`, but should be renamed to `Synthetics` since `Uptime` is not available. + uptime.name: 'Synthetics' \ No newline at end of file diff --git a/config/serverless.oblt.logs_essentials.yml b/config/serverless.oblt.logs_essentials.yml index 5f91fd167d97..35abf909523b 100644 --- a/config/serverless.oblt.logs_essentials.yml +++ b/config/serverless.oblt.logs_essentials.yml @@ -5,3 +5,41 @@ xpack.infra.enabled: false xpack.slo.enabled: false xpack.observabilityAIAssistant.enabled: false xpack.aiops.ui.enabled: false +xpack.apm.enabled: false + +xpack.legacy_uptime.enabled: false +xpack.ux.enabled: false +xpack.uptime.enabled: false + +xpack.fleet.internal.registry.excludePackages: [ + # Oblt integrations + 'synthetics', + # Security integrations + 'endpoint', + 'beaconing', + 'cloud_security_posture', + 'cloud_defend', + 'security_detection_engine', + + # Deprecated security integrations + 'bluecoat', + 'cisco', + 'cyberark', + 'cylance', + 'f5', + 'fortinet_forticlient', + 'juniper_junos', + 'juniper_netscreen', + 'microsoft', + 'netscout', + 'radware', + 'symantec', + 'tomcat', + + # ML integrations + 'dga', + + # Profiling integrations + 'profiler_agent', + 'synthetics_dashboards', +] diff --git a/config/serverless.oblt.yml b/config/serverless.oblt.yml index 5909cfdb2f7a..8ef9e0a82984 100644 --- a/config/serverless.oblt.yml +++ b/config/serverless.oblt.yml @@ -13,23 +13,6 @@ plugins.allowlistPluginGroups: ['platform', 'observability'] xpack.ux.enabled: false xpack.legacy_uptime.enabled: false -xpack.features.overrides: - ### By default, this feature named as `APM and User Experience`, but should be renamed to `Applications`. - apm.name: 'Applications' - ### Dashboards feature should be moved from Analytics category to the Observability one. - dashboard_v2.category: 'observability' - ### Discover feature should be moved from Analytics category to the Observability one and its privileges are - ### fine-tuned to grant access to Observability app. - discover_v2.category: 'observability' - ### Machine Learning feature should be moved from Analytics category to the Observability one and renamed to `AI Ops`. - ml: - category: 'observability' - order: 1200 - ### Stack alerts is hidden in Role management since it's not needed. - stackAlerts.hidden: true - ### By default, this feature named as `Synthetics and Uptime`, but should be renamed to `Synthetics` since `Uptime` is not available. - uptime.name: 'Synthetics' - ## Cloud settings xpack.cloud.serverless.project_type: observability diff --git a/x-pack/solutions/observability/plugins/observability/public/application/application.test.tsx b/x-pack/solutions/observability/plugins/observability/public/application/application.test.tsx index c1148cb7d38d..be45573e9903 100644 --- a/x-pack/solutions/observability/plugins/observability/public/application/application.test.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/application/application.test.tsx @@ -5,17 +5,23 @@ * 2.0. */ -import { createMemoryHistory } from 'history'; +import { render } from '@testing-library/react'; +import { pricingServiceMock } from '@kbn/core-pricing-browser-mocks'; import { noop } from 'lodash'; -import React from 'react'; +import React, { ReactNode } from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { Observable } from 'rxjs'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; + import { AppMountParameters, CoreStart } from '@kbn/core/public'; import { themeServiceMock } from '@kbn/core/public/mocks'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { ConfigSchema, ObservabilityPublicPluginsStart } from '../plugin'; import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock'; -import { renderApp } from '.'; +import { renderApp, App } from '.'; import { mockService } from '@kbn/observability-ai-assistant-plugin/public/mock'; +import { createMemoryHistory } from 'history'; +import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; describe('renderApp', () => { const originalConsole = global.console; @@ -29,6 +35,8 @@ describe('renderApp', () => { global.console = originalConsole; }); + let pricingStart: ReturnType; + const mockSearchSessionClear = jest.fn(); const plugins = { @@ -82,6 +90,14 @@ describe('renderApp', () => { }, }; + beforeEach(() => { + pricingStart = pricingServiceMock.createStartContract(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + it('renders', async () => { expect(() => { const unmount = renderApp({ @@ -123,4 +139,42 @@ describe('renderApp', () => { expect(mockSearchSessionClear).toBeCalled(); }); + + function AppWrapper({ children }: { children?: ReactNode }) { + return ( + + + + + + + + ); + } + + it('should adjust routes for complete', () => { + // Mock feature availability + pricingStart.isFeatureAvailable.mockImplementation((featureId) => { + if (featureId === 'observability:complete_overview') { + return true; + } + return true; + }); + + render(, { wrapper: AppWrapper }); + expect(document.body.textContent).toContain('Unable to load page'); + }); + + it('should adjust routes for essentials', () => { + // Mock feature availability + pricingStart.isFeatureAvailable.mockImplementation((featureId) => { + if (featureId === 'observability:complete_overview') { + return false; + } + return true; + }); + + render(, { wrapper: AppWrapper }); + expect(document.body.textContent).not.toContain('Unable to load page'); + }); }); diff --git a/x-pack/solutions/observability/plugins/observability/public/application/index.tsx b/x-pack/solutions/observability/plugins/observability/public/application/index.tsx index 20e2ada9eaf7..946392d1ce2f 100644 --- a/x-pack/solutions/observability/plugins/observability/public/application/index.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/application/index.tsx @@ -23,17 +23,23 @@ import { Storage } from '@kbn/kibana-utils-plugin/public'; import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { PluginContext } from '../context/plugin_context/plugin_context'; import { ConfigSchema, ObservabilityPublicPluginsStart } from '../plugin'; -import { routes } from '../routes/routes'; +import { routes, completeRoutes } from '../routes/routes'; +import { useKibana } from '../utils/kibana_react'; import { ObservabilityRuleTypeRegistry } from '../rules/create_observability_rule_type_registry'; import { HideableReactQueryDevTools } from './hideable_react_query_dev_tools'; -function App() { +export function App() { + const { pricing } = useKibana().services; + const isCompleteOverviewEnabled = pricing.isFeatureAvailable('observability:complete_overview'); + const allRoutes = { + ...(isCompleteOverviewEnabled ? { ...completeRoutes, ...routes } : { ...routes }), + }; return ( <> - {Object.keys(routes).map((key) => { - const path = key as keyof typeof routes; - const { handler, exact } = routes[path]; + {Object.keys(allRoutes).map((key) => { + const path = key as keyof typeof allRoutes; + const { handler, exact } = allRoutes[path]; const Wrapper = () => { return handler(); }; diff --git a/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx b/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx index 615efd58700d..600ed57fa368 100644 --- a/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx +++ b/x-pack/solutions/observability/plugins/observability/public/routes/routes.tsx @@ -62,7 +62,7 @@ function SimpleRedirect({ to, redirectToApp }: { to: string; redirectToApp?: str return null; } -export const routes = { +export const completeRoutes = { [ROOT_PATH]: { handler: () => { return ; @@ -70,17 +70,6 @@ export const routes = { params: {}, exact: true, }, - [LANDING_PATH]: { - handler: () => { - return ( - - - - ); - }, - params: {}, - exact: true, - }, [OVERVIEW_PATH]: { handler: () => { return ( @@ -94,6 +83,20 @@ export const routes = { params: {}, exact: true, }, +}; + +export const routes = { + [LANDING_PATH]: { + handler: () => { + return ( + + + + ); + }, + params: {}, + exact: true, + }, [CASES_PATH]: { handler: () => { return ; diff --git a/x-pack/solutions/observability/plugins/observability/tsconfig.json b/x-pack/solutions/observability/plugins/observability/tsconfig.json index f2ab11d73533..baa1f1f88bd1 100644 --- a/x-pack/solutions/observability/plugins/observability/tsconfig.json +++ b/x-pack/solutions/observability/plugins/observability/tsconfig.json @@ -122,7 +122,8 @@ "@kbn/object-utils", "@kbn/task-manager-plugin", "@kbn/core-saved-objects-server", - "@kbn/esql" + "@kbn/core-pricing-browser-mocks", + "@kbn/esql", ], "exclude": ["target/**/*"] } diff --git a/x-pack/solutions/observability/plugins/serverless_observability/jest.config.js b/x-pack/solutions/observability/plugins/serverless_observability/jest.config.js new file mode 100644 index 000000000000..1b711d3fd741 --- /dev/null +++ b/x-pack/solutions/observability/plugins/serverless_observability/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../..', + roots: ['/x-pack/solutions/observability/plugins/serverless_observability'], + coverageDirectory: + '/target/kibana-coverage/jest/x-pack/solutions/observability/plugins/serverless_observability', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/solutions/observability/plugins/serverless_observability/{common,public,server}/**/*.{js,ts,tsx}', + ], +}; diff --git a/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.test.ts b/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.test.ts new file mode 100644 index 000000000000..fb957192c640 --- /dev/null +++ b/x-pack/solutions/observability/plugins/serverless_observability/public/navigation_tree.test.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 { createNavigationTree } from './navigation_tree'; +import type { GroupDefinition, AppDeepLinkId } from '@kbn/core-chrome-browser'; + +describe('Navigation Tree', () => { + it('should generate tree with overview', () => { + const navigation = createNavigationTree({}); + expect((navigation.body[0] as GroupDefinition).children).toEqual( + expect.arrayContaining([ + { + title: 'Overview', + link: 'observability-overview', + }, + ]) + ); + }); + it('should not generate tree with overview', () => { + const navigation = createNavigationTree({ overviewAvailable: false }); + expect( + (navigation.body[0] as GroupDefinition).children + ).not.toEqual( + expect.arrayContaining([ + { + title: 'Overview', + link: 'observability-overview', + }, + ]) + ); + }); +}); 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 7272b631a10d..7325e5cd248b 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 @@ -10,8 +10,10 @@ import type { NavigationTreeDefinition } from '@kbn/core-chrome-browser'; export const createNavigationTree = ({ streamsAvailable, + overviewAvailable = true, }: { streamsAvailable?: boolean; + overviewAvailable?: boolean; }): NavigationTreeDefinition => { return { body: [ @@ -24,12 +26,16 @@ export const createNavigationTree = ({ isCollapsible: false, breadcrumbStatus: 'hidden', children: [ - { - title: i18n.translate('xpack.serverlessObservability.nav.overview', { - defaultMessage: 'Overview', - }), - link: 'observability-overview', - }, + ...(overviewAvailable + ? [ + { + title: i18n.translate('xpack.serverlessObservability.nav.overview', { + defaultMessage: 'Overview', + }), + link: 'observability-overview' as const, + }, + ] + : []), { title: i18n.translate('xpack.serverlessObservability.nav.discover', { defaultMessage: 'Discover', diff --git a/x-pack/solutions/observability/plugins/serverless_observability/public/plugin.ts b/x-pack/solutions/observability/plugins/serverless_observability/public/plugin.ts index a2bcf08bd29b..47e2964ecd4d 100644 --- a/x-pack/solutions/observability/plugins/serverless_observability/public/plugin.ts +++ b/x-pack/solutions/observability/plugins/serverless_observability/public/plugin.ts @@ -43,7 +43,10 @@ export class ServerlessObservabilityPlugin const { serverless, management, security } = setupDeps; const navigationTree$ = (setupDeps.streams?.status$ || of({ status: 'disabled' })).pipe( map(({ status }) => { - return createNavigationTree({ streamsAvailable: status === 'enabled' }); + return createNavigationTree({ + streamsAvailable: status === 'enabled', + overviewAvailable: core.pricing.isFeatureAvailable('observability:complete_overview'), + }); }) ); serverless.setProjectHome('/app/observability/landing'); diff --git a/x-pack/solutions/observability/plugins/serverless_observability/server/plugin.ts b/x-pack/solutions/observability/plugins/serverless_observability/server/plugin.ts index 0757ddd6093e..53cb46c84d4b 100644 --- a/x-pack/solutions/observability/plugins/serverless_observability/server/plugin.ts +++ b/x-pack/solutions/observability/plugins/serverless_observability/server/plugin.ts @@ -29,12 +29,22 @@ export class ServerlessObservabilityPlugin { constructor(_initializerContext: PluginInitializerContext) {} - public setup(_coreSetup: CoreSetup, pluginsSetup: SetupDependencies) { + public setup( + _coreSetup: CoreSetup, + pluginsSetup: SetupDependencies + ) { pluginsSetup.serverless.setupProjectSettings([ ...OBSERVABILITY_PROJECT_SETTINGS, ...(pluginsSetup.observabilityAIAssistant ? OBSERVABILITY_AI_ASSISTANT_PROJECT_SETTINGS : []), ]); - + _coreSetup.pricing.registerProductFeatures([ + { + id: 'observability:complete_overview', + products: [{ name: 'observability', tier: 'complete' }], + description: + 'Observability Overview Complete - Enables overview of the Observability solution.', + }, + ]); return {}; } diff --git a/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts b/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts index a028626f8a11..0acd4863b52a 100644 --- a/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts +++ b/x-pack/test_serverless/functional/services/svl_oblt_navigation.ts @@ -13,7 +13,7 @@ export function SvlObltNavigationServiceProvider({ }: FtrProviderContext) { const retry = getService('retry'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common']); + const PageObjects = getPageObjects(['common', 'header']); return { async navigateToLandingPage() { @@ -22,5 +22,11 @@ export function SvlObltNavigationServiceProvider({ await testSubjects.existOrFail('obltOnboardingHomeTitle', { timeout: 2000 }); }); }, + async navigateToDiscoverPage() { + await retry.tryForTime(60 * 1000, async () => { + await PageObjects.common.navigateToApp('discover'); + await testSubjects.exists('discoverQueryTotalHits', { timeout: 20_000 }); + }); + }, }; } diff --git a/x-pack/test_serverless/functional/test_suites/observability/logs_essentials_only/navigation.ts b/x-pack/test_serverless/functional/test_suites/observability/logs_essentials_only/navigation.ts index 9f3bd4d7d42d..6c112f3e84ab 100644 --- a/x-pack/test_serverless/functional/test_suites/observability/logs_essentials_only/navigation.ts +++ b/x-pack/test_serverless/functional/test_suites/observability/logs_essentials_only/navigation.ts @@ -15,7 +15,7 @@ export default function ({ getPageObject, getService }: FtrProviderContext) { describe('navigation', function () { before(async () => { await svlCommonPage.loginWithPrivilegedRole(); - await svlObltNavigation.navigateToLandingPage(); + await svlObltNavigation.navigateToDiscoverPage(); }); it('does not show the SLO entry', async () => {