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>
This commit is contained in:
Bryce Buchanan 2025-06-24 15:08:51 -07:00 committed by GitHub
parent c04d1782b7
commit d157214e1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 229 additions and 50 deletions

View file

@ -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'

View file

@ -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',
]

View file

@ -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

View file

@ -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<typeof pricingServiceMock.createStartContract>;
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 (
<KibanaRenderContextProvider {...core}>
<KibanaContextProvider services={{ pricing: pricingStart }}>
<MemoryRouter initialEntries={['/overview']}>
<App />
</MemoryRouter>
</KibanaContextProvider>
</KibanaRenderContextProvider>
);
}
it('should adjust routes for complete', () => {
// Mock feature availability
pricingStart.isFeatureAvailable.mockImplementation((featureId) => {
if (featureId === 'observability:complete_overview') {
return true;
}
return true;
});
render(<App />, { 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(<App />, { wrapper: AppWrapper });
expect(document.body.textContent).not.toContain('Unable to load page');
});
});

View file

@ -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 (
<>
<Routes enableExecutionContextTracking={true}>
{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();
};

View file

@ -62,7 +62,7 @@ function SimpleRedirect({ to, redirectToApp }: { to: string; redirectToApp?: str
return null;
}
export const routes = {
export const completeRoutes = {
[ROOT_PATH]: {
handler: () => {
return <SimpleRedirect to={OVERVIEW_PATH} />;
@ -70,17 +70,6 @@ export const routes = {
params: {},
exact: true,
},
[LANDING_PATH]: {
handler: () => {
return (
<HasDataContextProvider>
<LandingPage />
</HasDataContextProvider>
);
},
params: {},
exact: true,
},
[OVERVIEW_PATH]: {
handler: () => {
return (
@ -94,6 +83,20 @@ export const routes = {
params: {},
exact: true,
},
};
export const routes = {
[LANDING_PATH]: {
handler: () => {
return (
<HasDataContextProvider>
<LandingPage />
</HasDataContextProvider>
);
},
params: {},
exact: true,
},
[CASES_PATH]: {
handler: () => {
return <CasesPage />;

View file

@ -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/**/*"]
}

View file

@ -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: ['<rootDir>/x-pack/solutions/observability/plugins/serverless_observability'],
coverageDirectory:
'<rootDir>/target/kibana-coverage/jest/x-pack/solutions/observability/plugins/serverless_observability',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/solutions/observability/plugins/serverless_observability/{common,public,server}/**/*.{js,ts,tsx}',
],
};

View file

@ -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<AppDeepLinkId, string, string>).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<AppDeepLinkId, string, string>).children
).not.toEqual(
expect.arrayContaining([
{
title: 'Overview',
link: 'observability-overview',
},
])
);
});
});

View file

@ -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: [
...(overviewAvailable
? [
{
title: i18n.translate('xpack.serverlessObservability.nav.overview', {
defaultMessage: 'Overview',
}),
link: 'observability-overview',
link: 'observability-overview' as const,
},
]
: []),
{
title: i18n.translate('xpack.serverlessObservability.nav.discover', {
defaultMessage: 'Discover',

View file

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

View file

@ -29,12 +29,22 @@ export class ServerlessObservabilityPlugin
{
constructor(_initializerContext: PluginInitializerContext) {}
public setup(_coreSetup: CoreSetup, pluginsSetup: SetupDependencies) {
public setup(
_coreSetup: CoreSetup<StartDependencies, ServerlessObservabilityPluginStart>,
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 {};
}

View file

@ -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 });
});
},
};
}

View file

@ -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 () => {