Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ pageLoadAssetSize:
searchSynonyms: 6371
security: 79627
securitySolution: 171101
securitySolutionEss: 38689
securitySolutionEss: 42592
securitySolutionServerless: 52082
serverless: 7412
serverlessObservability: 19300
Expand Down
6 changes: 5 additions & 1 deletion src/platform/plugins/shared/navigation/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { DEFAULT_ROUTES, DEFAULT_ROUTE_UI_SETTING_ID } from './constants';
export {
DEFAULT_ROUTES,
DEFAULT_ROUTE_UI_SETTING_ID,
AGENT_BUILDER_NAV_AT_TOP_FLAG,
} from './constants';
2 changes: 2 additions & 0 deletions src/platform/plugins/shared/navigation/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type {
AddSolutionNavigationArg,
} from './types';

export { AGENT_BUILDER_NAV_AT_TOP_FLAG } from '../common';

// Export plugin after all other imports
import { NavigationPublicPlugin } from './plugin';
export { NavigationPublicPlugin as Plugin };
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,23 @@ import {
import { i18nStrings, securityLink } from '@kbn/security-solution-navigation/links';
import { defaultNavigationTree } from '@kbn/security-solution-navigation/navigation_tree';
import { STACK_MANAGEMENT_NAV_ID, DATA_MANAGEMENT_NAV_ID } from '@kbn/deeplinks-management';
import { AGENT_BUILDER_NAV_AT_TOP_FLAG } from '@kbn/navigation-plugin/public';
import { type Services } from '../common/services';
import { SOLUTION_NAME } from './translations';

export const createNavigationTree = (
services: Services,
chatExperience: AIChatExperience = AIChatExperience.Classic
): NavigationTreeDefinition => {
const showAgentBuilder = chatExperience === AIChatExperience.Agent;
const agentBuilderNavAtTop = services.featureFlags.getBooleanValue(
AGENT_BUILDER_NAV_AT_TOP_FLAG,
false
);
const agentBuilderLink = {
icon: 'productAgent',
link: 'agent_builder' as AppDeepLinkId,
};
const showAlertingV2 = Boolean(services.application.capabilities.alertingVTwo);
return {
body: [
Expand All @@ -31,6 +41,7 @@ export const createNavigationTree = (
renderAs: 'home',
title: SOLUTION_NAME,
},
...(showAgentBuilder && agentBuilderNavAtTop ? [agentBuilderLink] : []),
{
link: 'inbox' as AppDeepLinkId,
icon: 'email',
Expand All @@ -51,14 +62,8 @@ export const createNavigationTree = (
{
link: 'workflows',
},
...(chatExperience === AIChatExperience.Agent
? [
{
icon: 'productAgent',
link: 'agent_builder' as AppDeepLinkId,
},
]
: []),
// TODO: remove this item when agentBuilderNavAtTop is enabled by default and the Agent Builder link is always at the top of the nav
...(showAgentBuilder && !agentBuilderNavAtTop ? [agentBuilderLink] : []),
{
id: SecurityPageName.attackDiscovery,
icon: 'bolt',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
{
"type": "plugin",
"id": "@kbn/security-solution-serverless",
"owner": [
"@elastic/security-solution"
],
"owner": ["@elastic/security-solution"],
"group": "security",
"visibility": "private",
"description": "Serverless customizations for security.",
"plugin": {
"id": "securitySolutionServerless",
"browser": true,
"server": true,
"configPath": [
"xpack",
"securitySolutionServerless"
],
"configPath": ["xpack", "securitySolutionServerless"],
"requiredPlugins": [
"kibanaReact",
"management",
Expand All @@ -25,13 +20,9 @@
"cloud",
"fleet",
"actions",
"discover"
"discover",
"navigation"
],
"optionalPlugins": [
"securitySolutionEss",
"automaticImport",
"usageApi",
"workflowsManagement"
]
"optionalPlugins": ["securitySolutionEss", "automaticImport", "usageApi", "workflowsManagement"]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependsOn:
- '@kbn/workflows'
- '@kbn/usage-api-plugin'
- '@kbn/workflows-management-plugin'
- '@kbn/navigation-plugin'
tags:
- plugin
- prod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { AIChatExperience } from '@kbn/ai-assistant-common';
import { createAiNavigationTree } from './ai_navigation_tree';

describe('createAiNavigationTree', () => {
it('returns the Workflows link between Agents and Value report when enabled', () => {
const navigationTree = createAiNavigationTree(AIChatExperience.Agent, true);
it('returns the Workflows link between Agents and Value report when `workflows` is enabled', () => {
const navigationTree = createAiNavigationTree(AIChatExperience.Agent, true, false, false);

const primaryNavSection = navigationTree.body[4];
const children = 'children' in primaryNavSection ? primaryNavSection.children : [];
Expand All @@ -25,8 +25,8 @@ describe('createAiNavigationTree', () => {
expect(workflowsIndex).toBe((agentsIndex ?? 0) + 1);
});

it('does not include the Workflows link when disabled', () => {
const navigationTree = createAiNavigationTree(AIChatExperience.Agent, false);
it('does not include the Workflows link when `workflows` is disabled', () => {
const navigationTree = createAiNavigationTree(AIChatExperience.Agent, false, false, false);

const primaryNavSection = navigationTree.body[4];
const children = 'children' in primaryNavSection ? primaryNavSection.children : [];
Expand All @@ -37,4 +37,25 @@ describe('createAiNavigationTree', () => {

expect(workflowsIndex).toBe(-1);
});

it('places the Agent Builder link after `home` when `agentBuilderNavAtTop` is enabled', () => {
const navigationTree = createAiNavigationTree(AIChatExperience.Agent, true, false, true);

const secondNavItem = navigationTree.body[1];

expect('link' in secondNavItem && secondNavItem.link).toBe('agent_builder');
});

it('places the Agent Builder later in the nav when `agentBuilderNavAtTop` is disabled', () => {
const navigationTree = createAiNavigationTree(AIChatExperience.Agent, true, false, false);

const primaryNavSection = navigationTree.body[4];
const children = 'children' in primaryNavSection ? primaryNavSection.children : [];

const agentBuilderIndex = children?.findIndex(
(item) => 'link' in item && item.link === 'agent_builder'
);

expect(agentBuilderIndex).toBeGreaterThan(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const SOLUTION_NAME = i18n.translate(
export const createAiNavigationTree = (
chatExperience: AIChatExperience = AIChatExperience.Classic,
workflowsUiEnabled: boolean = false,
showAlertingV2: boolean = false
showAlertingV2: boolean = false,
showAgentBuilderNavAtTop: boolean = false
): NavigationTreeDefinition => ({
body: [
{
Expand All @@ -33,6 +34,14 @@ export const createAiNavigationTree = (
icon: AiNavigationIcon,
renderAs: 'home',
},
...(chatExperience === AIChatExperience.Agent && showAgentBuilderNavAtTop
? [
{
icon: 'productAgent',
link: 'agent_builder' as AppDeepLinkId,
},
]
: []),
{
id: SecurityPageName.alertSummary,
link: securityLink(SecurityPageName.alertSummary),
Expand Down Expand Up @@ -83,7 +92,8 @@ export const createAiNavigationTree = (
link: 'discover' as AppDeepLinkId,
icon: 'productDiscover',
},
...(chatExperience === AIChatExperience.Agent
// TODO: remove this item when agentBuilderNavAtTop is enabled by default and the Agent Builder link is always at the top of the nav
...(chatExperience === AIChatExperience.Agent && !showAgentBuilderNavAtTop
? [
{
icon: 'productAgent',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe('Security Side Nav', () => {
expect(mockedCreateAiNavigationTree).toHaveBeenCalledWith(
AIChatExperience.Classic,
false,
false,
false
);
expect(mockedCreateNavigationTree).not.toHaveBeenCalled();
Expand Down Expand Up @@ -119,6 +120,7 @@ describe('Security Side Nav', () => {
expect(mockedCreateAiNavigationTree).toHaveBeenCalledWith(
AIChatExperience.Classic,
true,
false,
false
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { firstValueFrom } from 'rxjs';
import { AI_CHAT_EXPERIENCE_TYPE } from '@kbn/management-settings-ids';
import type { AIChatExperience } from '@kbn/ai-assistant-common';
import { WORKFLOWS_UI_SETTING_ID } from '@kbn/workflows/common/constants';
import { AGENT_BUILDER_NAV_AT_TOP_FLAG } from '@kbn/navigation-plugin/public';
import { ProductLine } from '../../common/product';
import type { SecurityProductTypes } from '../../common/config';
import { type Services } from '../common/services';
Expand All @@ -24,6 +25,11 @@ export const registerSolutionNavigation = async (
(productType) => productType.product_line === ProductLine.aiSoc
);

const agentBuilderNavAtTop = services.featureFlags.getBooleanValue(
AGENT_BUILDER_NAV_AT_TOP_FLAG,
false
);

// Do not pass a defaultOverride: when userValue is unset, get() must use the registered
// default (e.g. Agent for security spaces from aiAssistantManagementSelection), not Classic.
const chatExperience$ = services.settings.client.get$<AIChatExperience>(AI_CHAT_EXPERIENCE_TYPE);
Expand All @@ -39,7 +45,12 @@ export const registerSolutionNavigation = async (
const showAlertingV2 = Boolean(services.application.capabilities.alertingVTwo);

const navigationTree = shouldUseAINavigation
? createAiNavigationTree(initialChatExperience, workflowsUiEnabled, showAlertingV2)
? createAiNavigationTree(
initialChatExperience,
workflowsUiEnabled,
showAlertingV2,
agentBuilderNavAtTop
)
: await createNavigationTree(services, initialChatExperience);

services.securitySolution.setSolutionNavigationTree(navigationTree);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@kbn/security-solution-navigation';
import { i18nStrings, securityLink } from '@kbn/security-solution-navigation/links';
import { defaultNavigationTree } from '@kbn/security-solution-navigation/navigation_tree';
import { AGENT_BUILDER_NAV_AT_TOP_FLAG } from '@kbn/navigation-plugin/public';

import { type Services } from '../common/services';
import { createManagementFooterItemsTree } from './management_footer_items';
Expand All @@ -29,6 +30,17 @@ export const createNavigationTree = async (
chatExperience: AIChatExperience = AIChatExperience.Classic
): Promise<NavigationTreeDefinition> => {
const showAlertingV2 = Boolean(services.application.capabilities.alertingVTwo);

const showAgentBuilder = chatExperience === AIChatExperience.Agent;
const agentBuilderNavAtTop = services.featureFlags.getBooleanValue(
AGENT_BUILDER_NAV_AT_TOP_FLAG,
false
);
const agentBuilderLink = {
icon: 'productAgent',
link: 'agent_builder' as AppDeepLinkId,
};

return {
body: [
{
Expand All @@ -38,6 +50,7 @@ export const createNavigationTree = async (
icon: 'logoSecurity',
renderAs: 'home',
},
...(showAgentBuilder && agentBuilderNavAtTop ? [agentBuilderLink] : []),
{
link: 'inbox' as AppDeepLinkId,
icon: 'email',
Expand All @@ -58,14 +71,8 @@ export const createNavigationTree = async (
{
link: 'workflows',
},
...(chatExperience === AIChatExperience.Agent
? [
{
icon: 'productAgent',
link: 'agent_builder' as AppDeepLinkId,
},
]
: []),
// TODO: remove this item when agentBuilderNavAtTop is enabled by default and the Agent Builder link is always at the top of the nav
...(showAgentBuilder && !agentBuilderNavAtTop ? [agentBuilderLink] : []),
{
id: SecurityPageName.attackDiscovery,
icon: 'bolt',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@
"@kbn/workflows",
"@kbn/usage-api-plugin",
"@kbn/workflows-management-plugin",
"@kbn/navigation-plugin",
]
}
Loading