From 6d22aebb9faa101fdce2c6f763f935df1b6c9dab Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 22 Jan 2026 09:55:11 -0700 Subject: [PATCH 1/4] add to nav --- .../ai_navigation/ai_navigation_tree.test.ts | 34 +++++++++++++++++++ .../ai_navigation/ai_navigation_tree.ts | 10 +++++- .../public/navigation/navigation.test.ts | 20 ++++++++--- .../public/navigation/navigation.ts | 10 ++++-- 4 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts new file mode 100644 index 0000000000000..a325a0bc23834 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts @@ -0,0 +1,34 @@ +/* + * 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 { createAiNavigationTree } from './ai_navigation_tree'; + +describe('createAiNavigationTree', () => { + it('returns the Workflows link between Agents and Value report when enabled', () => { + const navigationTree = createAiNavigationTree('agent', true); + + const primaryNavSection = navigationTree.body[4]; + const children = 'children' in primaryNavSection ? primaryNavSection.children : []; + + const workflowsIndex = children.findIndex((item) => 'link' in item && item.link === 'workflows'); + const agentsIndex = children.findIndex((item) => 'link' in item && item.link === 'agent_builder'); + + expect(workflowsIndex).toBe(agentsIndex + 1); + }); + + it('does not include the Workflows link when disabled', () => { + const navigationTree = createAiNavigationTree('agent', false); + + const primaryNavSection = navigationTree.body[4]; + const children = 'children' in primaryNavSection ? primaryNavSection.children : []; + + const workflowsIndex = children.findIndex((item) => 'link' in item && item.link === 'workflows'); + + expect(workflowsIndex).toBe(-1); + }); +}); + diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts index 467ad3e1ca32c..c84f3bfa2ff37 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts @@ -24,7 +24,8 @@ const SOLUTION_NAME = i18n.translate( ); export const createAiNavigationTree = ( - chatExperience: AIChatExperience = AIChatExperience.Classic + chatExperience: AIChatExperience = AIChatExperience.Classic, + workflowsUiEnabled: boolean = false ): NavigationTreeDefinition => ({ body: [ { @@ -88,6 +89,13 @@ export const createAiNavigationTree = ( }, ] : []), + ...(workflowsUiEnabled + ? [ + { + link: 'workflows', + }, + ] + : []), { id: SecurityPageName.aiValue, link: securityLink(SecurityPageName.aiValue), diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.test.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.test.ts index 6625221c2406b..398d7ef34c868 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.test.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.test.ts @@ -7,6 +7,7 @@ import { of } from 'rxjs'; import { AIChatExperience } from '@kbn/ai-assistant-common'; +import { WORKFLOWS_UI_SETTING_ID } from '@kbn/workflows/common/constants'; import type { ProductLine, ProductTier } from '../../common/product'; import { mockServices } from '../common/services/__mocks__/services.mock'; import { registerSolutionNavigation } from './navigation'; @@ -31,8 +32,13 @@ describe('Security Side Nav', () => { beforeEach(() => { jest.clearAllMocks(); initNavigationSpy.mockReset(); - // Mock settings.client.get$ to return Classic chat experience by default - services.settings.client.get$ = jest.fn().mockReturnValue(of(AIChatExperience.Classic)); + services.settings.client.get$ = jest.fn().mockImplementation((key: string) => { + if (key === WORKFLOWS_UI_SETTING_ID) { + return of(false); + } + + return of(AIChatExperience.Classic); + }); }); it('registers the navigation tree definition for serverless security', async () => { @@ -64,7 +70,7 @@ describe('Security Side Nav', () => { ]); expect(initNavigationSpy).toHaveBeenCalled(); - expect(mockedCreateAiNavigationTree).toHaveBeenCalledWith(AIChatExperience.Classic); + expect(mockedCreateAiNavigationTree).toHaveBeenCalledWith(AIChatExperience.Classic, false); expect(mockedCreateNavigationTree).not.toHaveBeenCalled(); const [, navigationTree$] = initNavigationSpy.mock.calls[0]; @@ -77,7 +83,13 @@ describe('Security Side Nav', () => { }); it('passes Agent chat experience when settings return Agent', async () => { - services.settings.client.get$ = jest.fn().mockReturnValue(of(AIChatExperience.Agent)); + services.settings.client.get$ = jest.fn().mockImplementation((key: string) => { + if (key === WORKFLOWS_UI_SETTING_ID) { + return of(false); + } + + return of(AIChatExperience.Agent); + }); await registerSolutionNavigation(services, []); diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts index 151c967bec8d1..0f2fbc63147bb 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts @@ -9,7 +9,8 @@ import * as Rx from 'rxjs'; import { firstValueFrom } from 'rxjs'; import { AI_CHAT_EXPERIENCE_TYPE } from '@kbn/management-settings-ids'; import { AIChatExperience } from '@kbn/ai-assistant-common'; -import { ProductTier } from '../../common/product'; +import { WORKFLOWS_UI_SETTING_ID } from '@kbn/workflows/common/constants'; +import { ProductLine } from '../../common/product'; import type { SecurityProductTypes } from '../../common/config'; import { type Services } from '../common/services'; import { createAiNavigationTree } from './ai_navigation/ai_navigation_tree'; @@ -20,7 +21,7 @@ export const registerSolutionNavigation = async ( productTypes: SecurityProductTypes ) => { const shouldUseAINavigation = productTypes.some( - (productType) => productType.product_tier === ProductTier.searchAiLake + (productType) => productType.product_line === ProductLine.aiSoc ); const chatExperience$ = services.settings.client.get$( @@ -31,8 +32,11 @@ export const registerSolutionNavigation = async ( // Get initial chat experience for setting initial navigation tree const initialChatExperience = await firstValueFrom(chatExperience$); + const workflowsUiEnabled$ = services.settings.client.get$(WORKFLOWS_UI_SETTING_ID, false); + const workflowsUiEnabled = await firstValueFrom(workflowsUiEnabled$); + const navigationTree = shouldUseAINavigation - ? createAiNavigationTree(initialChatExperience) + ? createAiNavigationTree(initialChatExperience, workflowsUiEnabled) : await createNavigationTree(services, initialChatExperience); services.securitySolution.setSolutionNavigationTree(navigationTree); From dbe71535471c72c9663273eef3f4c89c756607dd Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 22 Jan 2026 09:56:36 -0700 Subject: [PATCH 2/4] lint --- .../public/navigation/navigation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts index 0f2fbc63147bb..e75083b3f2d19 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/navigation.ts @@ -32,7 +32,10 @@ export const registerSolutionNavigation = async ( // Get initial chat experience for setting initial navigation tree const initialChatExperience = await firstValueFrom(chatExperience$); - const workflowsUiEnabled$ = services.settings.client.get$(WORKFLOWS_UI_SETTING_ID, false); + const workflowsUiEnabled$ = services.settings.client.get$( + WORKFLOWS_UI_SETTING_ID, + false + ); const workflowsUiEnabled = await firstValueFrom(workflowsUiEnabled$); const navigationTree = shouldUseAINavigation From fc4d2c09c8988c8311a611cc36925a1b88826d20 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:30:49 +0000 Subject: [PATCH 3/4] Changes from node scripts/eslint_all_files --no-cache --fix --- .../ai_navigation/ai_navigation_tree.test.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts index a325a0bc23834..25210c5d950d3 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts @@ -14,8 +14,12 @@ describe('createAiNavigationTree', () => { const primaryNavSection = navigationTree.body[4]; const children = 'children' in primaryNavSection ? primaryNavSection.children : []; - const workflowsIndex = children.findIndex((item) => 'link' in item && item.link === 'workflows'); - const agentsIndex = children.findIndex((item) => 'link' in item && item.link === 'agent_builder'); + const workflowsIndex = children.findIndex( + (item) => 'link' in item && item.link === 'workflows' + ); + const agentsIndex = children.findIndex( + (item) => 'link' in item && item.link === 'agent_builder' + ); expect(workflowsIndex).toBe(agentsIndex + 1); }); @@ -26,9 +30,10 @@ describe('createAiNavigationTree', () => { const primaryNavSection = navigationTree.body[4]; const children = 'children' in primaryNavSection ? primaryNavSection.children : []; - const workflowsIndex = children.findIndex((item) => 'link' in item && item.link === 'workflows'); + const workflowsIndex = children.findIndex( + (item) => 'link' in item && item.link === 'workflows' + ); expect(workflowsIndex).toBe(-1); }); }); - From b48c8392c3fe97f5392eebb9ad2bb9a67857f366 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 22 Jan 2026 14:19:05 -0700 Subject: [PATCH 4/4] fix types --- .../ai_navigation/ai_navigation_tree.test.ts | 20 ++++++++++++------- .../ai_navigation/ai_navigation_tree.ts | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts index a325a0bc23834..6f645186ffd7c 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.test.ts @@ -5,30 +5,36 @@ * 2.0. */ +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('agent', true); + const navigationTree = createAiNavigationTree(AIChatExperience.Agent, true); const primaryNavSection = navigationTree.body[4]; const children = 'children' in primaryNavSection ? primaryNavSection.children : []; - const workflowsIndex = children.findIndex((item) => 'link' in item && item.link === 'workflows'); - const agentsIndex = children.findIndex((item) => 'link' in item && item.link === 'agent_builder'); + const workflowsIndex = children?.findIndex( + (item) => 'link' in item && item.link === 'workflows' + ); + const agentsIndex = children?.findIndex( + (item) => 'link' in item && item.link === 'agent_builder' + ); - expect(workflowsIndex).toBe(agentsIndex + 1); + expect(workflowsIndex).toBe((agentsIndex ?? 0) + 1); }); it('does not include the Workflows link when disabled', () => { - const navigationTree = createAiNavigationTree('agent', false); + const navigationTree = createAiNavigationTree(AIChatExperience.Agent, false); const primaryNavSection = navigationTree.body[4]; const children = 'children' in primaryNavSection ? primaryNavSection.children : []; - const workflowsIndex = children.findIndex((item) => 'link' in item && item.link === 'workflows'); + const workflowsIndex = children?.findIndex( + (item) => 'link' in item && item.link === 'workflows' + ); expect(workflowsIndex).toBe(-1); }); }); - diff --git a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts index c84f3bfa2ff37..03e62c32426aa 100644 --- a/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts +++ b/x-pack/solutions/security/plugins/security_solution_serverless/public/navigation/ai_navigation/ai_navigation_tree.ts @@ -78,7 +78,7 @@ export const createAiNavigationTree = ( breadcrumbStatus: 'hidden', children: [ { - link: 'discover', + link: 'discover' as AppDeepLinkId, }, ...(chatExperience === AIChatExperience.Agent ? [ @@ -92,7 +92,7 @@ export const createAiNavigationTree = ( ...(workflowsUiEnabled ? [ { - link: 'workflows', + link: 'workflows' as AppDeepLinkId, }, ] : []),