Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Fixes Preconfigured Connectors not working with Assistant #164900

Merged
merged 8 commits into from
Aug 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const AssistantTitle: React.FC<{
const content = useMemo(
() => (
<FormattedMessage
defaultMessage="The Elastic AI Assistant is currently in beta. For more information on the assistant feature and its usage, please reference the {documentationLink}."
defaultMessage="Responses from AI systems may not always be entirely accurate. For more information on the assistant feature and its usage, please reference the {documentationLink}."
id="xpack.elasticAssistant.assistant.technicalPreview.tooltipContent"
values={{
documentationLink,
Expand Down Expand Up @@ -106,7 +106,6 @@ export const AssistantTitle: React.FC<{
anchorPosition="rightUp"
>
<EuiText data-test-subj="tooltipContent" grow={false} css={{ maxWidth: '400px' }}>
<h4>{i18n.TOOLTIP_TITLE}</h4>
<EuiText size={'s'}>
<p>{content}</p>
</EuiText>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { HttpSetup } from '@kbn/core-http-browser';
import { FormattedMessage } from '@kbn/i18n-react';
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/public/common';
import { noop } from 'lodash/fp';
import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types';
import { Conversation, Prompt } from '../../../..';
import * as i18n from './translations';
import * as i18nModel from '../../../connectorland/models/model_selector/translations';
Expand All @@ -23,6 +24,11 @@ import { ModelSelector } from '../../../connectorland/models/model_selector/mode
import { UseAssistantContext } from '../../../assistant_context';
import { ConversationSelectorSettings } from '../conversation_selector_settings';
import { getDefaultSystemPrompt } from '../../use_conversation/helpers';
import { useLoadConnectors } from '../../../connectorland/use_load_connectors';

interface Config {
defaultModel?: string;
}

export interface ConversationSettingsProps {
actionTypeRegistry: ActionTypeRegistryContract;
Expand Down Expand Up @@ -63,6 +69,8 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation });
}, [allSystemPrompts, selectedConversation]);

const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http });

// Conversation callbacks
// When top level conversation selection changes
const onConversationSelectionChange = useCallback(
Expand Down Expand Up @@ -131,18 +139,21 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
[selectedConversation, setUpdatedConversationSettings]
);

const selectedConnectorId = useMemo(
() => selectedConversation?.apiConfig.connectorId,
[selectedConversation?.apiConfig.connectorId]
);
const selectedConnector = useMemo(() => {
const selectedConnectorId = selectedConversation?.apiConfig.connectorId;
if (areConnectorsFetched) {
return connectors?.find((c) => c.id === selectedConnectorId);
}
return undefined;
}, [areConnectorsFetched, connectors, selectedConversation?.apiConfig.connectorId]);

const selectedProvider = useMemo(
() => selectedConversation?.apiConfig.provider,
[selectedConversation?.apiConfig.provider]
);

const handleOnConnectorSelectionChange = useCallback(
(connectorId: string, provider: OpenAiProviderType) => {
(connectorId: string, provider?: OpenAiProviderType, model?: string) => {
spong marked this conversation as resolved.
Show resolved Hide resolved
if (selectedConversation != null) {
setUpdatedConversationSettings((prev) => ({
...prev,
Expand All @@ -152,6 +163,7 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
...selectedConversation.apiConfig,
connectorId,
provider,
model: model ?? selectedConversation.apiConfig.model,
spong marked this conversation as resolved.
Show resolved Hide resolved
},
},
}));
Expand All @@ -160,10 +172,12 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
[selectedConversation, setUpdatedConversationSettings]
);

const selectedModel = useMemo(
() => selectedConversation?.apiConfig.model,
[selectedConversation?.apiConfig.model]
);
const selectedModel = useMemo(() => {
const connectorModel: string | undefined = (
selectedConnector as ActionConnectorProps<Config, unknown>
)?.config?.defaultModel;
return selectedConversation?.apiConfig.model ?? connectorModel;
}, [selectedConnector, selectedConversation?.apiConfig.model]);

const handleOnModelSelectionChange = useCallback(
(model?: string) => {
Expand Down Expand Up @@ -244,23 +258,24 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
isDisabled={selectedConversation == null}
onConnectorModalVisibilityChange={() => {}}
onConnectorSelectionChange={handleOnConnectorSelectionChange}
selectedConnectorId={selectedConnectorId}
selectedConnectorId={selectedConnector?.id}
/>
</EuiFormRow>

{selectedProvider === OpenAiProviderType.OpenAi && (
<EuiFormRow
data-test-subj="model-field"
display="rowCompressed"
label={i18nModel.MODEL_TITLE}
helpText={i18nModel.HELP_LABEL}
>
<ModelSelector
onModelSelectionChange={handleOnModelSelectionChange}
selectedModel={selectedModel}
/>
</EuiFormRow>
)}
{selectedConnector?.isPreconfigured === false &&
selectedProvider === OpenAiProviderType.OpenAi && (
<EuiFormRow
data-test-subj="model-field"
display="rowCompressed"
label={i18nModel.MODEL_TITLE}
helpText={i18nModel.HELP_LABEL}
>
<ModelSelector
onModelSelectionChange={handleOnModelSelectionChange}
selectedModel={selectedModel}
/>
</EuiFormRow>
)}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ export const API_ERROR = i18n.translate('xpack.elasticAssistant.assistant.apiErr
'An error occurred sending your message. If the problem persists, please test the connector configuration.',
});

export const TOOLTIP_TITLE = i18n.translate(
'xpack.elasticAssistant.assistant.technicalPreview.tooltipTitle',
{
defaultMessage: 'Beta',
}
);

export const TOOLTIP_ARIA_LABEL = i18n.translate(
'xpack.elasticAssistant.documentationLinks.ariaLabel',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@ interface Props {
actionTypeRegistry: ActionTypeRegistryContract;
http: HttpSetup;
isDisabled?: boolean;
onConnectorSelectionChange: (connectorId: string, provider: OpenAiProviderType) => void;
onConnectorSelectionChange: (
connectorId: string,
provider?: OpenAiProviderType,
model?: string
) => void;
selectedConnectorId?: string;
onConnectorModalVisibilityChange?: (isVisible: boolean) => void;
}

interface Config {
apiProvider: string;
apiProvider?: string;
defaultModel?: string;
}

export const ConnectorSelector: React.FC<Props> = React.memo(
Expand Down Expand Up @@ -98,15 +103,18 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
const apiProvider: string | undefined = (
connector as ActionConnectorProps<Config, unknown>
)?.config?.apiProvider;
const connectorDetails = connector.isPreconfigured
? i18n.PRECONFIGURED_CONNECTOR
: apiProvider;
return {
value: connector.id,
inputDisplay: connector.name,
dropdownDisplay: (
<React.Fragment key={connector.id}>
<strong>{connector.name}</strong>
{apiProvider && (
<EuiText size="s" color="subdued">
<p>{apiProvider}</p>
{connectorDetails && (
<EuiText size="xs" color="subdued">
<p>{connectorDetails}</p>
</EuiText>
)}
</React.Fragment>
Expand Down Expand Up @@ -140,7 +148,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(

const apiProvider = (
connectors?.find((c) => c.id === connectorId) as ActionConnectorProps<Config, unknown>
)?.config.apiProvider as OpenAiProviderType;
)?.config?.apiProvider as OpenAiProviderType;
spong marked this conversation as resolved.
Show resolved Hide resolved
onConnectorSelectionChange(connectorId, apiProvider);
},
[connectors, onConnectorSelectionChange, onConnectorModalVisibilityChange]
Expand All @@ -163,10 +171,11 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
actionType={actionType}
onClose={cleanupAndCloseModal}
postSaveEventHandler={(savedAction: ActionConnector) => {
const config = (savedAction as ActionConnectorProps<Config, unknown>)?.config;
onConnectorSelectionChange(
savedAction.id,
(savedAction as ActionConnectorProps<Config, unknown>)?.config
.apiProvider as OpenAiProviderType
config.apiProvider as OpenAiProviderType,
config.defaultModel
);
refetchConnectors?.();
cleanupAndCloseModal();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@ import { useConversation } from '../../assistant/use_conversation';
export const ADD_NEW_CONNECTOR = 'ADD_NEW_CONNECTOR';
interface Props {
isDisabled?: boolean;
onConnectorSelectionChange: (connectorId: string, provider: OpenAiProviderType) => void;
onConnectorSelectionChange: (
connectorId: string,
provider?: OpenAiProviderType,
model?: string
) => void;
selectedConnectorId?: string;
selectedConversation?: Conversation;
onConnectorModalVisibilityChange?: (isVisible: boolean) => void;
}

interface Config {
apiProvider: string;
apiProvider?: string;
defaultModel?: string;
}

const inputContainerClassName = css`
Expand Down Expand Up @@ -139,6 +144,9 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
const apiProvider: string | undefined = (
connector as ActionConnectorProps<Config, unknown>
)?.config?.apiProvider;
const connectorDetails = connector.isPreconfigured
? i18n.PRECONFIGURED_CONNECTOR
: apiProvider;
return {
value: connector.id,
inputDisplay: (
Expand All @@ -149,9 +157,9 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
dropdownDisplay: (
<React.Fragment key={connector.id}>
<strong>{connector.name}</strong>
{apiProvider && (
{connectorDetails && (
<EuiText size="xs" color="subdued">
<p>{apiProvider}</p>
<p>{connectorDetails}</p>
</EuiText>
)}
</React.Fragment>
Expand Down Expand Up @@ -182,7 +190,7 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
const handleOnBlur = useCallback(() => setIsOpen(false), []);

const onChange = useCallback(
(connectorId: string, apiProvider?: OpenAiProviderType) => {
(connectorId: string, apiProvider?: OpenAiProviderType, model?: string) => {
setIsOpen(false);

if (connectorId === ADD_NEW_CONNECTOR) {
Expand All @@ -203,6 +211,7 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
...selectedConversation.apiConfig,
connectorId,
provider,
model: model ?? selectedConversation.apiConfig.model,
},
});
}
Expand Down Expand Up @@ -279,8 +288,10 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
postSaveEventHandler={(savedAction: ActionConnector) => {
const provider = (savedAction as ActionConnectorProps<Config, unknown>)?.config
.apiProvider as OpenAiProviderType;
onChange(savedAction.id, provider);
onConnectorSelectionChange(savedAction.id, provider);
const model = (savedAction as ActionConnectorProps<Config, unknown>)?.config
.defaultModel;
onChange(savedAction.id, provider, model);
onConnectorSelectionChange(savedAction.id, provider, model);
refetchConnectors?.();
cleanupAndCloseModal();
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const SkipEuiText = styled(EuiText)`

interface Config {
apiProvider: string;
defaultModel?: string;
}

export interface ConnectorSetupProps {
Expand Down Expand Up @@ -239,6 +240,8 @@ export const useConnectorSetup = ({
connectorId: savedAction.id,
provider: (savedAction as ActionConnectorProps<Config, unknown>)?.config
.apiProvider as OpenAiProviderType,
model: (savedAction as ActionConnectorProps<Config, unknown>)?.config
.defaultModel,
},
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ export const WELCOME_SECURITY = i18n.translate(
}
);

export const PRECONFIGURED_CONNECTOR = i18n.translate(
'xpack.elasticAssistant.assistant.connectors.preconfiguredTitle',
{
defaultMessage: 'Preconfigured',
}
);

export const CONNECTOR_SELECTOR_TITLE = i18n.translate(
'xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel',
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
* 2.0.
*/

import React, { useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { NewChat } from '@kbn/elastic-assistant';
import { useUserData } from '../../../../detections/components/user_info';
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
import { usePrebuiltRulesStatus } from '../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_status';
import { useRuleManagementFilters } from '../../../rule_management/logic/use_rule_management_filters';
import * as i18n from './translations';
import { getPromptContextFromDetectionRules } from '../../../../assistant/helpers';
import { useRulesTableContext } from './rules_table/rules_table_context';
import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability';
import * as i18nAssistant from '../../../../detections/pages/detection_engine/rules/translations';

export enum AllRulesTabs {
management = 'management',
Expand Down Expand Up @@ -71,7 +77,39 @@ export const RulesTableToolbar = React.memo(() => {
[installedTotal, updateTotal, shouldDisplayRuleUpdatesTab]
);

return <TabNavigation navTabs={ruleTabs} />;
// Assistant integration for using selected rules as prompt context
const { hasAssistantPrivilege } = useAssistantAvailability();
const {
state: { rules, selectedRuleIds },
} = useRulesTableContext();
const selectedRules = useMemo(
() => rules.filter((rule) => selectedRuleIds.includes(rule.id)),
[rules, selectedRuleIds]
);
const getPromptContext = useCallback(
async () => getPromptContextFromDetectionRules(selectedRules),
[selectedRules]
);

return (
<EuiFlexGroup justifyContent={'spaceBetween'}>
<EuiFlexItem grow={false}>
<TabNavigation navTabs={ruleTabs} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
{hasAssistantPrivilege && selectedRules.length > 0 && (
<NewChat
category="detection-rules"
conversationId={i18nAssistant.DETECTION_RULES_CONVERSATION_ID}
description={i18nAssistant.RULE_MANAGEMENT_CONTEXT_DESCRIPTION}
getPromptContext={getPromptContext}
suggestedUserPrompt={i18nAssistant.EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS}
tooltip={i18nAssistant.RULE_MANAGEMENT_CONTEXT_TOOLTIP}
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
);
});

RulesTableToolbar.displayName = 'RulesTableToolbar';
Loading