Skip to content

Commit 00a841c

Browse files
committed
fix ai settings showing in EASE
1 parent e4cf14f commit 00a841c

File tree

10 files changed

+281
-13
lines changed

10 files changed

+281
-13
lines changed

x-pack/solutions/security/plugins/security_solution/public/app/links/app_links.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
* 2.0.
66
*/
77
import type { CoreStart } from '@kbn/core/public';
8+
import { firstValueFrom, take } from 'rxjs';
9+
import { AIChatExperience } from '@kbn/ai-assistant-common';
10+
import { AI_CHAT_EXPERIENCE_TYPE } from '@kbn/management-settings-ids';
811

912
import { ATTACKS_ALERTS_ALIGNMENT_ENABLED } from '../../../common/constants';
1013
import { aiValueLinks } from '../../reports/links';
11-
import { configurationsLinks } from '../../configurations/links';
14+
import { configurationsLinks, getConfigurationsLinks } from '../../configurations/links';
1215
import { links as attackDiscoveryLinks } from '../../attack_discovery/links';
1316
import { links as assetInventoryLinks } from '../../asset_inventory/links';
1417
import { siemReadinessLinks } from '../../siem_readiness/links';
@@ -54,6 +57,13 @@ export const getFilteredLinks = async (
5457
): Promise<AppLinkItems> => {
5558
const managementFilteredLinks = await getManagementFilteredLinks(core, plugins);
5659

60+
const chatExperience$ = core.uiSettings.client.get$<AIChatExperience>(
61+
AI_CHAT_EXPERIENCE_TYPE,
62+
AIChatExperience.Classic
63+
);
64+
const chatExperience = await firstValueFrom(chatExperience$.pipe(take(1)));
65+
const filteredConfigurationsLinks = getConfigurationsLinks(chatExperience);
66+
5767
return Object.freeze([
5868
dashboardsLinks,
5969
core.featureFlags.getBooleanValue(ATTACKS_ALERTS_ALIGNMENT_ENABLED, false)
@@ -63,7 +73,7 @@ export const getFilteredLinks = async (
6373
attackDiscoveryLinks,
6474
findingsLinks,
6575
casesLinks,
66-
configurationsLinks,
76+
filteredConfigurationsLinks,
6777
timelinesLinks,
6878
indicatorsLinks,
6979
exploreLinks,

x-pack/solutions/security/plugins/security_solution/public/configurations/links.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import {
99
RULES_UI_READ_PRIVILEGE,
1010
SECURITY_UI_SHOW_PRIVILEGE,
1111
} from '@kbn/security-solution-features/constants';
12+
import { AIChatExperience } from '@kbn/ai-assistant-common';
1213
import { ConfigurationTabs } from './constants';
1314
import * as i18n from './translations';
1415
import type { LinkItem } from '..';
1516
import { CONFIGURATIONS_PATH, SECURITY_FEATURE_ID, SecurityPageName } from '../../common/constants';
1617
import { CONFIGURATIONS } from '../app/translations';
1718

18-
export const configurationsLinks: LinkItem = {
19+
const baseConfigurationsLinks: LinkItem = {
1920
capabilities: [[SECURITY_UI_SHOW_PRIVILEGE, `${SECURITY_FEATURE_ID}.configurations`]],
2021
globalNavPosition: 3,
2122
globalSearchKeywords: [i18n.CONFIGURATIONS],
@@ -49,3 +50,19 @@ export const configurationsLinks: LinkItem = {
4950
},
5051
],
5152
};
53+
54+
export const getConfigurationsLinks = (
55+
chatExperience: AIChatExperience = AIChatExperience.Classic
56+
): LinkItem => {
57+
if (chatExperience === AIChatExperience.Agent) {
58+
return {
59+
...baseConfigurationsLinks,
60+
links: baseConfigurationsLinks.links?.filter(
61+
(link) => link.id !== SecurityPageName.configurationsAiSettings
62+
),
63+
};
64+
}
65+
return baseConfigurationsLinks;
66+
};
67+
68+
export const configurationsLinks: LinkItem = baseConfigurationsLinks;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React, { useEffect } from 'react';
9+
import { Redirect } from 'react-router-dom';
10+
import { AISettings } from '../tabs/ai_settings';
11+
import { useAgentBuilderAvailability } from '../../agent_builder/hooks/use_agent_builder_availability';
12+
import { useNavigation } from '../../common/lib/kibana';
13+
import { SecurityPageName } from '@kbn/security-solution-navigation';
14+
import { CONFIGURATIONS_PATH } from '../../../common/constants';
15+
import { ConfigurationTabs } from '../constants';
16+
17+
export const AISettingsRouteGuard: React.FC = React.memo(() => {
18+
const { isAgentChatExperienceEnabled } = useAgentBuilderAvailability();
19+
const { navigateTo } = useNavigation();
20+
21+
useEffect(() => {
22+
if (isAgentChatExperienceEnabled) {
23+
navigateTo({
24+
deepLinkId: SecurityPageName.configurationsIntegrations,
25+
});
26+
}
27+
}, [isAgentChatExperienceEnabled, navigateTo]);
28+
29+
if (isAgentChatExperienceEnabled) {
30+
return <Redirect to={`${CONFIGURATIONS_PATH}/${ConfigurationTabs.integrations}`} />;
31+
}
32+
33+
return <AISettings />;
34+
});
35+
36+
AISettingsRouteGuard.displayName = 'AISettingsRouteGuard';
37+
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render } from '@testing-library/react';
10+
import { MemoryRouter } from '@kbn/shared-ux-router';
11+
import { ConfigurationsRouter } from './configuration_router';
12+
import { TestProviders } from '../../common/mock';
13+
import { CONFIGURATIONS_PATH } from '../../../common/constants';
14+
import { ConfigurationTabs } from '../constants';
15+
import { useAgentBuilderAvailability } from '../../agent_builder/hooks/use_agent_builder_availability';
16+
import { useKibana, useNavigation } from '../../common/lib/kibana';
17+
import { SecurityPageName } from '@kbn/security-solution-navigation';
18+
19+
jest.mock('../../agent_builder/hooks/use_agent_builder_availability');
20+
jest.mock('../../common/lib/kibana', () => ({
21+
useKibana: jest.fn(),
22+
useNavigation: jest.fn(),
23+
}));
24+
jest.mock('../../common/hooks/use_space_id', () => ({
25+
useSpaceId: jest.fn().mockReturnValue('default'),
26+
}));
27+
28+
const mockNavigateTo = jest.fn();
29+
30+
describe('ConfigurationsRouter', () => {
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
(useAgentBuilderAvailability as jest.Mock).mockReturnValue({
34+
isAgentChatExperienceEnabled: false,
35+
});
36+
(useNavigation as jest.Mock).mockReturnValue({
37+
navigateTo: mockNavigateTo,
38+
});
39+
(useKibana as jest.Mock).mockReturnValue({
40+
services: {
41+
application: {
42+
navigateToApp: jest.fn(),
43+
capabilities: {
44+
securitySolutionAssistant: { 'ai-assistant': true },
45+
},
46+
},
47+
data: { dataViews: {} },
48+
},
49+
});
50+
});
51+
52+
it('renders AISettings component when isAgentChatExperienceEnabled is false', () => {
53+
const { getByTestId } = render(
54+
<MemoryRouter initialEntries={[`${CONFIGURATIONS_PATH}/${ConfigurationTabs.aiSettings}`]}>
55+
<TestProviders>
56+
<ConfigurationsRouter />
57+
</TestProviders>
58+
</MemoryRouter>
59+
);
60+
61+
// The AISettingsRouteGuard will render AISettings when Agent experience is disabled
62+
// We can verify this by checking that the route guard component exists
63+
expect(getByTestId('SearchAILakeConfigurationsSettingsManagement')).toBeInTheDocument();
64+
});
65+
66+
it('redirects when isAgentChatExperienceEnabled is true', () => {
67+
(useAgentBuilderAvailability as jest.Mock).mockReturnValue({
68+
isAgentChatExperienceEnabled: true,
69+
});
70+
71+
render(
72+
<MemoryRouter initialEntries={[`${CONFIGURATIONS_PATH}/${ConfigurationTabs.aiSettings}`]}>
73+
<TestProviders>
74+
<ConfigurationsRouter />
75+
</TestProviders>
76+
</MemoryRouter>
77+
);
78+
79+
// The route guard should redirect to integrations
80+
expect(mockNavigateTo).toHaveBeenCalledWith({
81+
deepLinkId: SecurityPageName.configurationsIntegrations,
82+
});
83+
});
84+
});
85+

x-pack/solutions/security/plugins/security_solution/public/configurations/page/configuration_router.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { PromotionRules } from '../tabs/promotion_rules';
1414
import { CONFIGURATIONS_PATH } from '../../../common/constants';
1515
import { ConfigurationTabs } from '../constants';
1616
import { LazyConfigurationsIntegrationsHome } from '../tabs/integrations';
17+
import { AISettingsRouteGuard } from './ai_settings_route_guard';
1718

1819
export const ConfigurationsRouter = React.memo(() => {
1920
return (
@@ -24,7 +25,7 @@ export const ConfigurationsRouter = React.memo(() => {
2425
/>
2526
<Route
2627
path={`${CONFIGURATIONS_PATH}/:tab(${ConfigurationTabs.aiSettings})`}
27-
component={AISettings}
28+
component={AISettingsRouteGuard}
2829
/>
2930
<Route
3031
path={`${CONFIGURATIONS_PATH}/:tab(${ConfigurationTabs.basicRules})`}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import React from 'react';
9+
import { render } from '@testing-library/react';
10+
import { MemoryRouter } from '@kbn/shared-ux-router';
11+
import { ConfigurationsTabs } from './configuration_tabs';
12+
import { useNavigation } from '../../common/lib/kibana';
13+
import { TestProviders } from '../../common/mock';
14+
import { ConfigurationTabs } from '../constants';
15+
import { useAgentBuilderAvailability } from '../../agent_builder/hooks/use_agent_builder_availability';
16+
17+
jest.mock('../../common/lib/kibana');
18+
jest.mock('../../agent_builder/hooks/use_agent_builder_availability');
19+
20+
const mockNavigateTo = jest.fn();
21+
22+
describe('ConfigurationsTabs', () => {
23+
beforeEach(() => {
24+
jest.clearAllMocks();
25+
(useNavigation as jest.Mock).mockReturnValue({
26+
navigateTo: mockNavigateTo,
27+
});
28+
(useAgentBuilderAvailability as jest.Mock).mockReturnValue({
29+
isAgentChatExperienceEnabled: false,
30+
});
31+
});
32+
33+
it('renders all tabs including AI Settings when isAgentChatExperienceEnabled is false', () => {
34+
const { getByText } = render(
35+
<MemoryRouter initialEntries={[`/configurations/${ConfigurationTabs.integrations}`]}>
36+
<TestProviders>
37+
<ConfigurationsTabs />
38+
</TestProviders>
39+
</MemoryRouter>
40+
);
41+
42+
expect(getByText('Integrations')).toBeInTheDocument();
43+
expect(getByText('Rules')).toBeInTheDocument();
44+
expect(getByText('AI settings')).toBeInTheDocument();
45+
});
46+
47+
it('does not render AI Settings tab when isAgentChatExperienceEnabled is true', () => {
48+
(useAgentBuilderAvailability as jest.Mock).mockReturnValue({
49+
isAgentChatExperienceEnabled: true,
50+
});
51+
52+
const { getByText, queryByText } = render(
53+
<MemoryRouter initialEntries={[`/configurations/${ConfigurationTabs.integrations}`]}>
54+
<TestProviders>
55+
<ConfigurationsTabs />
56+
</TestProviders>
57+
</MemoryRouter>
58+
);
59+
60+
expect(getByText('Integrations')).toBeInTheDocument();
61+
expect(getByText('Rules')).toBeInTheDocument();
62+
expect(queryByText('AI settings')).not.toBeInTheDocument();
63+
});
64+
});
65+

x-pack/solutions/security/plugins/security_solution/public/configurations/page/configuration_tabs.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
* 2.0.
66
*/
77

8-
import React, { useCallback } from 'react';
8+
import React, { useCallback, useMemo } from 'react';
99
import { EuiTab, EuiTabs } from '@elastic/eui';
1010
import { useParams } from 'react-router-dom';
1111
import { SecurityPageName } from '@kbn/security-solution-navigation';
1212
import { useNavigation } from '../../common/lib/kibana';
1313
import { ConfigurationTabs } from '../constants';
1414
import * as i18n from '../translations';
15+
import { useAgentBuilderAvailability } from '../../agent_builder/hooks/use_agent_builder_availability';
1516

1617
const CONFIGURATION_TABS = [
1718
{
@@ -33,6 +34,15 @@ const CONFIGURATION_TABS = [
3334
export const ConfigurationsTabs = React.memo(() => {
3435
const { navigateTo } = useNavigation();
3536
const params: { tab: ConfigurationTabs } = useParams();
37+
const { isAgentChatExperienceEnabled } = useAgentBuilderAvailability();
38+
39+
const filteredTabs = useMemo(
40+
() =>
41+
CONFIGURATION_TABS.filter(
42+
(tab) => !isAgentChatExperienceEnabled || tab.tabId !== ConfigurationTabs.aiSettings
43+
),
44+
[isAgentChatExperienceEnabled]
45+
);
3646

3747
const onSelectedTabChanged = useCallback(
3848
(deepLinkId: SecurityPageName) => {
@@ -43,7 +53,7 @@ export const ConfigurationsTabs = React.memo(() => {
4353

4454
return (
4555
<EuiTabs size="m" bottomBorder>
46-
{CONFIGURATION_TABS.map((tab) => (
56+
{filteredTabs.map((tab) => (
4757
<EuiTab
4858
key={tab.deepLinkId}
4959
onClick={() => onSelectedTabChanged(tab.deepLinkId)}

x-pack/solutions/security/plugins/security_solution/public/configurations/tabs/ai_settings.test.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ import { useKibana, useNavigation } from '../../common/lib/kibana';
1313
import { TestProviders } from '../../common/mock';
1414
import { CONVERSATIONS_TAB } from '@kbn/elastic-assistant';
1515
import { SecurityPageName } from '@kbn/deeplinks-security';
16+
import { useAgentBuilderAvailability } from '../../agent_builder/hooks/use_agent_builder_availability';
1617

1718
const mockNavigateTo = jest.fn();
1819
jest.mock('../../common/lib/kibana');
1920
jest.mock('../../common/hooks/use_space_id', () => ({
2021
useSpaceId: jest.fn().mockReturnValue('default'),
2122
}));
23+
jest.mock('../../agent_builder/hooks/use_agent_builder_availability', () => ({
24+
useAgentBuilderAvailability: jest.fn(),
25+
}));
2226

2327
describe('AISettings', () => {
2428
beforeEach(() => {
@@ -37,6 +41,9 @@ describe('AISettings', () => {
3741
(useNavigation as jest.Mock).mockReturnValue({
3842
navigateTo: mockNavigateTo,
3943
});
44+
(useAgentBuilderAvailability as jest.Mock).mockReturnValue({
45+
isAgentChatExperienceEnabled: false,
46+
});
4047
});
4148

4249
it('renders the SearchAILakeConfigurationsSettingsManagement component wiht default Conversations tab when securityAIAssistantEnabled is true', () => {
@@ -90,4 +97,22 @@ describe('AISettings', () => {
9097

9198
expect(mockNavigateToApp).toHaveBeenCalledWith('home');
9299
});
100+
101+
it('navigates to integrations when isAgentChatExperienceEnabled is true', () => {
102+
(useAgentBuilderAvailability as jest.Mock).mockReturnValue({
103+
isAgentChatExperienceEnabled: true,
104+
});
105+
106+
render(
107+
<MemoryRouter>
108+
<TestProviders>
109+
<AISettings />
110+
</TestProviders>
111+
</MemoryRouter>
112+
);
113+
114+
expect(mockNavigateTo).toHaveBeenCalledWith({
115+
deepLinkId: SecurityPageName.configurationsIntegrations,
116+
});
117+
});
93118
});

0 commit comments

Comments
 (0)