Skip to content

Commit f8aa0e4

Browse files
stephmilovicpgayvallet
authored andcommitted
[AI4SOC] AI settings page (elastic#217373)
1 parent 9025201 commit f8aa0e4

File tree

29 files changed

+840
-61
lines changed

29 files changed

+840
-61
lines changed

x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/conversations/conversation_settings/conversation_settings_editor.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
1111
import { HttpSetup } from '@kbn/core-http-browser';
1212
import { FormattedMessage } from '@kbn/i18n-react';
1313
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common';
14-
import { noop } from 'lodash/fp';
1514
import { PromptResponse } from '@kbn/elastic-assistant-common';
1615
import { Conversation } from '../../../..';
1716
import * as i18n from './translations';
@@ -214,7 +213,6 @@ export const ConversationSettingsEditor: React.FC<ConversationSettingsEditorProp
214213
isSettingsModalVisible={true}
215214
onSystemPromptSelectionChange={handleOnSystemPromptSelectionChange}
216215
selectedPrompt={selectedSystemPrompt}
217-
setIsSettingsModalVisible={noop} // noop, already in settings
218216
/>
219217
</EuiFormRow>
220218

x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/prompt_editor/system_prompt/select_system_prompt/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface Props {
3838
isOpen?: boolean;
3939
isSettingsModalVisible: boolean;
4040
selectedPrompt: PromptResponse | undefined;
41-
setIsSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
41+
setIsSettingsModalVisible?: React.Dispatch<React.SetStateAction<boolean>>;
4242
onSystemPromptSelectionChange: (promptId: string | undefined) => void;
4343
}
4444

@@ -94,7 +94,7 @@ const SelectSystemPromptComponent: React.FC<Props> = ({
9494
const onChange = useCallback(
9595
async (selectedSystemPromptId: string) => {
9696
if (selectedSystemPromptId === ADD_NEW_SYSTEM_PROMPT) {
97-
setIsSettingsModalVisible(true);
97+
setIsSettingsModalVisible?.(true);
9898
setSelectedSettingsTab(SYSTEM_PROMPTS_TAB);
9999
return;
100100
}

x-pack/platform/packages/shared/kbn-elastic-assistant/impl/assistant/settings/assistant_settings_management.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import React, { useMemo } from 'react';
8+
import React, { useEffect, useMemo } from 'react';
99
import { EuiAvatar, EuiPageTemplate, EuiTitle, useEuiShadow, useEuiTheme } from '@elastic/eui';
1010
import { css } from '@emotion/react';
1111
import { DataViewsContract } from '@kbn/data-views-plugin/public';
@@ -47,7 +47,17 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
4747
const {
4848
assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled },
4949
http,
50+
selectedSettingsTab: contextSettingsTab,
51+
setSelectedSettingsTab,
5052
} = useAssistantContext();
53+
54+
useEffect(() => {
55+
if (contextSettingsTab) {
56+
// contextSettingsTab can be selected from Conversations > System Prompts > Add System Prompt
57+
onTabChange?.(contextSettingsTab);
58+
}
59+
}, [onTabChange, contextSettingsTab, setSelectedSettingsTab]);
60+
5161
const { data: connectors } = useLoadConnectors({
5262
http,
5363
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
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 { welcomeConvo } from '../../mock/conversation';
9+
import { useAssistantContext } from '../../assistant_context';
10+
import { fireEvent, render } from '@testing-library/react';
11+
12+
import React from 'react';
13+
import { I18nProvider } from '@kbn/i18n-react';
14+
import { MOCK_QUICK_PROMPTS } from '../../mock/quick_prompt';
15+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
16+
import { SearchAILakeConfigurationsSettingsManagement } from './search_ai_lake_configurations_settings_management';
17+
18+
import {
19+
CONNECTORS_TAB,
20+
ANONYMIZATION_TAB,
21+
CONVERSATIONS_TAB,
22+
KNOWLEDGE_BASE_TAB,
23+
QUICK_PROMPTS_TAB,
24+
SYSTEM_PROMPTS_TAB,
25+
} from './const';
26+
import { DataViewsContract } from '@kbn/data-views-plugin/public';
27+
28+
const mockContext = {
29+
basePromptContexts: MOCK_QUICK_PROMPTS,
30+
http: {
31+
get: jest.fn(),
32+
},
33+
assistantFeatures: { assistantModelEvaluation: true },
34+
assistantAvailability: {
35+
isAssistantEnabled: true,
36+
},
37+
};
38+
39+
const mockDataViews = {
40+
getIndices: jest.fn(),
41+
} as unknown as DataViewsContract;
42+
43+
const onTabChange = jest.fn();
44+
const testProps = {
45+
selectedConversation: welcomeConvo,
46+
dataViews: mockDataViews,
47+
onTabChange,
48+
currentTab: CONNECTORS_TAB,
49+
};
50+
jest.mock('../../assistant_context');
51+
52+
jest.mock('../../connectorland/connector_settings_management', () => ({
53+
ConnectorsSettingsManagement: () => <span data-test-subj="connectors-tab" />,
54+
}));
55+
56+
jest.mock('../conversations/conversation_settings_management', () => ({
57+
ConversationSettingsManagement: () => <span data-test-subj="conversations-tab" />,
58+
}));
59+
60+
jest.mock('../quick_prompts/quick_prompt_settings_management', () => ({
61+
QuickPromptSettingsManagement: () => <span data-test-subj="quick_prompts-tab" />,
62+
}));
63+
64+
jest.mock('../prompt_editor/system_prompt/system_prompt_settings_management', () => ({
65+
SystemPromptSettingsManagement: () => <span data-test-subj="system_prompts-tab" />,
66+
}));
67+
68+
jest.mock('../../knowledge_base/knowledge_base_settings_management', () => ({
69+
KnowledgeBaseSettingsManagement: () => <span data-test-subj="knowledge_base-tab" />,
70+
}));
71+
72+
jest.mock('../../data_anonymization/settings/anonymization_settings_management', () => ({
73+
AnonymizationSettingsManagement: () => <span data-test-subj="anonymization-tab" />,
74+
}));
75+
76+
jest.mock('.', () => {
77+
return {
78+
EvaluationSettings: () => <span data-test-subj="evaluation-tab" />,
79+
};
80+
});
81+
82+
jest.mock('../../connectorland/use_load_connectors', () => ({
83+
useLoadConnectors: jest.fn().mockReturnValue({ data: [] }),
84+
}));
85+
86+
const queryClient = new QueryClient();
87+
88+
const wrapper = (props: { children: React.ReactNode }) => (
89+
<I18nProvider>
90+
<QueryClientProvider client={queryClient}>{props.children}</QueryClientProvider>
91+
</I18nProvider>
92+
);
93+
94+
describe('SearchAILakeConfigurationsSettingsManagement', () => {
95+
beforeEach(() => {
96+
jest.clearAllMocks();
97+
(useAssistantContext as jest.Mock).mockImplementation(() => mockContext);
98+
});
99+
100+
it('Bottom bar is hidden when no pending changes', async () => {
101+
const { queryByTestId } = render(
102+
<SearchAILakeConfigurationsSettingsManagement {...testProps} />,
103+
{
104+
wrapper,
105+
}
106+
);
107+
108+
expect(queryByTestId(`bottom-bar`)).not.toBeInTheDocument();
109+
});
110+
111+
describe.each([
112+
CONNECTORS_TAB,
113+
ANONYMIZATION_TAB,
114+
CONVERSATIONS_TAB,
115+
KNOWLEDGE_BASE_TAB,
116+
QUICK_PROMPTS_TAB,
117+
SYSTEM_PROMPTS_TAB,
118+
])('%s', (tab) => {
119+
it('Opens the tab on button click', () => {
120+
const { getByTestId } = render(
121+
<SearchAILakeConfigurationsSettingsManagement {...testProps} currentTab={tab} />,
122+
{
123+
wrapper,
124+
}
125+
);
126+
fireEvent.click(getByTestId(`settingsPageTab-${tab}`));
127+
expect(onTabChange).toHaveBeenCalledWith(tab);
128+
});
129+
it('renders with the correct tab open', () => {
130+
const { getByTestId } = render(
131+
<SearchAILakeConfigurationsSettingsManagement {...testProps} currentTab={tab} />,
132+
{
133+
wrapper,
134+
}
135+
);
136+
expect(getByTestId(`tab-${tab}`)).toBeInTheDocument();
137+
});
138+
});
139+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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, { useCallback, useEffect, useMemo } from 'react';
9+
import {
10+
EuiFlexGroup,
11+
EuiFlexItem,
12+
EuiListGroup,
13+
EuiListGroupItem,
14+
useEuiTheme,
15+
} from '@elastic/eui';
16+
import { css } from '@emotion/react';
17+
import { DataViewsContract } from '@kbn/data-views-plugin/public';
18+
import { AIForSOCConnectorSettingsManagement } from '../../connectorland/ai_for_soc_connector_settings_management';
19+
import * as i18n from './translations';
20+
import { useAssistantContext } from '../../assistant_context';
21+
import { useLoadConnectors } from '../../connectorland/use_load_connectors';
22+
import { getDefaultConnector } from '../helpers';
23+
import { ConversationSettingsManagement } from '../conversations/conversation_settings_management';
24+
import { QuickPromptSettingsManagement } from '../quick_prompts/quick_prompt_settings_management';
25+
import { SystemPromptSettingsManagement } from '../prompt_editor/system_prompt/system_prompt_settings_management';
26+
import { AnonymizationSettingsManagement } from '../../data_anonymization/settings/anonymization_settings_management';
27+
28+
import {
29+
ANONYMIZATION_TAB,
30+
CONNECTORS_TAB,
31+
CONVERSATIONS_TAB,
32+
KNOWLEDGE_BASE_TAB,
33+
QUICK_PROMPTS_TAB,
34+
SYSTEM_PROMPTS_TAB,
35+
} from './const';
36+
import { KnowledgeBaseSettingsManagement } from '../../knowledge_base/knowledge_base_settings_management';
37+
import { ManagementSettingsTabs } from './types';
38+
39+
interface Props {
40+
dataViews: DataViewsContract;
41+
onTabChange?: (tabId: string) => void;
42+
currentTab: ManagementSettingsTabs;
43+
}
44+
45+
/**
46+
* Modal for overall Assistant Settings, including conversation settings, quick prompts, system prompts,
47+
* anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag.
48+
*/
49+
export const SearchAILakeConfigurationsSettingsManagement: React.FC<Props> = React.memo(
50+
({ dataViews, onTabChange, currentTab }) => {
51+
const { http, selectedSettingsTab, setSelectedSettingsTab } = useAssistantContext();
52+
53+
useEffect(() => {
54+
if (selectedSettingsTab) {
55+
// selectedSettingsTab can be selected from Conversations > System Prompts > Add System Prompt
56+
onTabChange?.(selectedSettingsTab);
57+
}
58+
}, [onTabChange, selectedSettingsTab, setSelectedSettingsTab]);
59+
60+
const { data: connectors } = useLoadConnectors({
61+
http,
62+
});
63+
const defaultConnector = useMemo(() => getDefaultConnector(connectors), [connectors]);
64+
65+
const { euiTheme } = useEuiTheme();
66+
67+
const tabsConfig = useMemo(
68+
() => [
69+
{
70+
id: CONVERSATIONS_TAB,
71+
label: i18n.CONVERSATIONS_MENU_ITEM,
72+
},
73+
{
74+
id: CONNECTORS_TAB,
75+
label: i18n.CONNECTORS_MENU_ITEM,
76+
},
77+
{
78+
id: SYSTEM_PROMPTS_TAB,
79+
label: i18n.SYSTEM_PROMPTS_MENU_ITEM,
80+
},
81+
{
82+
id: QUICK_PROMPTS_TAB,
83+
label: i18n.QUICK_PROMPTS_MENU_ITEM,
84+
},
85+
{
86+
id: ANONYMIZATION_TAB,
87+
label: i18n.ANONYMIZATION_MENU_ITEM,
88+
},
89+
{
90+
id: KNOWLEDGE_BASE_TAB,
91+
label: i18n.KNOWLEDGE_BASE_MENU_ITEM,
92+
},
93+
],
94+
[]
95+
);
96+
97+
const tabs = useMemo(() => {
98+
return tabsConfig.map((t) => ({
99+
...t,
100+
onClick: () => {
101+
onTabChange?.(t.id);
102+
},
103+
isSelected: t.id === currentTab,
104+
}));
105+
}, [onTabChange, currentTab, tabsConfig]);
106+
107+
const renderTabBody = useCallback(() => {
108+
switch (currentTab) {
109+
case CONNECTORS_TAB:
110+
return <AIForSOCConnectorSettingsManagement />;
111+
case SYSTEM_PROMPTS_TAB:
112+
return (
113+
<SystemPromptSettingsManagement
114+
connectors={connectors}
115+
defaultConnector={defaultConnector}
116+
/>
117+
);
118+
case QUICK_PROMPTS_TAB:
119+
return <QuickPromptSettingsManagement />;
120+
case ANONYMIZATION_TAB:
121+
return <AnonymizationSettingsManagement />;
122+
case KNOWLEDGE_BASE_TAB:
123+
return <KnowledgeBaseSettingsManagement dataViews={dataViews} />;
124+
case CONVERSATIONS_TAB:
125+
default:
126+
return (
127+
<ConversationSettingsManagement
128+
connectors={connectors}
129+
defaultConnector={defaultConnector}
130+
/>
131+
);
132+
}
133+
}, [connectors, currentTab, dataViews, defaultConnector]);
134+
return (
135+
<EuiFlexGroup
136+
data-test-subj="SearchAILakeConfigurationsSettingsManagement"
137+
css={css`
138+
margin-top: ${euiTheme.size.l};
139+
`}
140+
>
141+
<EuiFlexItem grow={false} css={{ width: '200px' }}>
142+
<EuiListGroup flush>
143+
{tabs.map(({ id, label, onClick, isSelected }) => (
144+
<EuiListGroupItem
145+
key={id}
146+
label={label}
147+
onClick={onClick}
148+
data-test-subj={`settingsPageTab-${id}`}
149+
isActive={isSelected}
150+
size="s"
151+
/>
152+
))}
153+
</EuiListGroup>
154+
</EuiFlexItem>
155+
<EuiFlexItem data-test-subj={`tab-${currentTab}`}>{renderTabBody()}</EuiFlexItem>
156+
</EuiFlexGroup>
157+
);
158+
}
159+
);
160+
161+
SearchAILakeConfigurationsSettingsManagement.displayName =
162+
'SearchAILakeConfigurationsSettingsManagement';

0 commit comments

Comments
 (0)