Skip to content

Commit

Permalink
[Security Solution] [AI Assistant] Update copy of the citations tour. (
Browse files Browse the repository at this point in the history
…elastic#210398)

## Summary

Addresses
elastic/security-docs#6485 (comment)

This PR updates the copy in the Citations and Anonymized values tour
according to the figma linked in the issue. Furthermore, the PR includes
the logic that disables the "Show anonymized values" and "Show
citations" buttons in the assistant settings menu when the conversation
does not contain anonymized values or citations respectivly.

### How to test new copy is correct
<img width="864" alt="image"
src="https://github.com/user-attachments/assets/22bd2671-6d06-4e68-85f5-3c10d9974a4a"
/>


- Enable feature flag
```yaml
# kibana.dev.yml
xpack.securitySolution.enableExperimental: ['contentReferencesEnabled']
```
- Clear the key
`elasticAssistant.anonymizedValuesAndCitationsTourCompleted.v8.18` from
your local storage if it exists.
- Open Security assistant
- Ask the assistant a question about your alerts or a KB document, the
response should contain anonymized values or a citation.
- The tour with the new copy should appear (copy in screenshot above).
*the Anonymized values and citations will not appear if the knowledge
base tour is currently open.

### How to test assistant settings menu changes:
<img width="349" alt="image"
src="https://github.com/user-attachments/assets/58e445d7-79c0-46ca-a245-bc2ab90eeb5d"
/>

- Enable feature flag
```yaml
# kibana.dev.yml
xpack.securitySolution.enableExperimental: ['contentReferencesEnabled']
```
- Open Security assistant
- In a new conversation, inside the settings menu, the "Show anonymized
values" and "Show citations" menu items should be disabled because the
conversation is empty and does not contain citations or anonymized
values.
- Ask the assistant a question about a KB document or Alert. The "Show
citations" menu item should become available if the response contains
citations. The "Show anonymized values" menu item will become available
if the conversation contains replacements. Hovering over the disabled
menu item will make a tooltip appear explaining why it is disabled.
*Sometimes the conversation will contain replacements but the
replacements are not used in the messages. In that case, the anonymized
values menu item will not be disabled. This is a known
[issue](elastic#208517).

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [X] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [X]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [X] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [X] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [X] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [X] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [X] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 21, 2025
1 parent 0c932fd commit b59712f
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
*/

import { HttpSetup } from '@kbn/core/public';
import { API_VERSIONS, ApiConfig, Replacements } from '@kbn/elastic-assistant-common';
import {
API_VERSIONS,
ApiConfig,
MessageMetadata,
Replacements,
} from '@kbn/elastic-assistant-common';
import { API_ERROR } from '../translations';
import { getOptionalRequestParams } from '../helpers';
import { TraceOptions } from '../types';
Expand Down Expand Up @@ -34,6 +39,7 @@ export interface FetchConnectorExecuteResponse {
transactionId: string;
traceId: string;
};
metadata?: MessageMetadata;
}

export const fetchConnectorExecuteAction = async ({
Expand Down Expand Up @@ -110,6 +116,7 @@ export const fetchConnectorExecuteAction = async ({
transaction_id: string;
trace_id: string;
};
metadata?: MessageMetadata;
}>(`/internal/elastic_assistant/actions/connector/${apiConfig?.connectorId}/_execute`, {
method: 'POST',
body: JSON.stringify(requestBody),
Expand Down Expand Up @@ -144,6 +151,7 @@ export const fetchConnectorExecuteAction = async ({

return {
response: response.data,
metadata: response.metadata,
isError: false,
isStream: false,
traceData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,11 @@ export const AssistantHeader: React.FC<Props> = ({
/>
</EuiFlexItem>
<EuiFlexItem id={AI_ASSISTANT_SETTINGS_MENU_CONTAINER_ID}>
<SettingsContextMenu isDisabled={isDisabled} onChatCleared={onChatCleared} />
<SettingsContextMenu
isDisabled={isDisabled}
onChatCleared={onChatCleared}
selectedConversation={selectedConversation}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,6 @@ export const CHAT_OPTIONS = i18n.translate(
}
);

const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;

export const ANONYMIZE_VALUES_TOOLTIP = i18n.translate(
'xpack.elasticAssistant.assistant.settings.anonymizeValues.tooltip',
{
values: { keyboardShortcut: isMac ? '⌥ + a' : 'Alt + a' },
defaultMessage:
'Toggle to reveal or hide field values in your chat stream. The data sent to the LLM is still anonymized based on settings in the Anonymization panel. Keyboard shortcut: {keyboardShortcut}',
}
);

export const SHOW_CITATIONS_TOOLTIP = i18n.translate(
'xpack.elasticAssistant.assistant.settings.showCitationsLabel.tooltip',
{
values: { keyboardShortcut: isMac ? '⌥ + c' : 'Alt + c' },
defaultMessage: 'Keyboard shortcut: {keyboardShortcut}',
}
);

export const CANCEL_BUTTON_TEXT = i18n.translate(
'xpack.elasticAssistant.assistant.resetConversationModal.cancelButtonText',
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { alertConvo, conversationWithContentReferences } from '../../../mock/conversation';
import { Conversation } from '../../../..';
import { conversationContainsContentReferences, conversationContainsAnonymizedValues } from '.';

describe('conversation utils', () => {
it.each([
[undefined, false],
[conversationWithContentReferences, true],
[alertConvo, false],
])(
'conversationContainsContentReferences',
(conversation: Conversation | undefined, expected: boolean) => {
expect(conversationContainsContentReferences(conversation)).toBe(expected);
}
);

it.each([
[undefined, false],
[conversationWithContentReferences, false],
[alertConvo, true],
])(
'conversationContainsAnonymizedValues',
(conversation: Conversation | undefined, expected: boolean) => {
expect(conversationContainsAnonymizedValues(conversation)).toBe(expected);
}
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { isEmpty } from 'lodash';
import { Conversation } from '../../../..';

export const conversationContainsContentReferences = (conversation?: Conversation): boolean => {
return (
conversation?.messages.some((message) => !isEmpty(message.metadata?.contentReferences)) ?? false
);
};

/** Checks if the conversations has replacements, not if the replacements are actually used */
export const conversationContainsAnonymizedValues = (conversation?: Conversation): boolean => {
return !isEmpty(conversation?.replacements);
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const getMessageFromRawResponse = (
timestamp: dateTimeString,
isError,
traceData: rawResponse.traceData,
metadata: rawResponse.metadata,
};
} else {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ import { ConversationSidePanel } from './conversations/conversation_sidepanel';
import { SelectedPromptContexts } from './prompt_editor/selected_prompt_contexts';
import { AssistantHeader } from './assistant_header';
import { AnonymizedValuesAndCitationsTour } from '../tour/anonymized_values_and_citations_tour';
import {
conversationContainsAnonymizedValues,
conversationContainsContentReferences,
} from './conversations/utils';

export const CONVERSATION_SIDE_PANEL_WIDTH = 220;

Expand Down Expand Up @@ -227,10 +231,12 @@ const AssistantComponent: React.FC<Props> = ({
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
if (event.altKey && event.code === 'KeyC') {
if (!conversationContainsContentReferences(currentConversation)) return;
event.preventDefault();
setContentReferencesVisible(!contentReferencesVisible);
}
if (event.altKey && event.code === 'KeyA') {
if (!conversationContainsAnonymizedValues(currentConversation)) return;
event.preventDefault();
setShowAnonymizedValues(!showAnonymizedValues);
}
Expand All @@ -240,6 +246,7 @@ const AssistantComponent: React.FC<Props> = ({
contentReferencesVisible,
setShowAnonymizedValues,
showAnonymizedValues,
currentConversation,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,42 @@ import {
EuiHorizontalRule,
EuiToolTip,
EuiSwitchEvent,
EuiIcon,
} from '@elastic/eui';
import { css } from '@emotion/react';
import { FormattedMessage } from '@kbn/i18n-react';
import { KnowledgeBaseTour } from '../../../tour/knowledge_base';
import { AnonymizationSettingsManagement } from '../../../data_anonymization/settings/anonymization_settings_management';
import { useAssistantContext } from '../../../..';
import { Conversation, useAssistantContext } from '../../../..';
import * as i18n from '../../assistant_header/translations';
import { AlertsSettingsModal } from '../alerts_settings/alerts_settings_modal';
import { KNOWLEDGE_BASE_TAB } from '../const';
import { AI_ASSISTANT_MENU } from './translations';
import {
conversationContainsAnonymizedValues,
conversationContainsContentReferences,
} from '../../conversations/utils';

interface Params {
isDisabled?: boolean;
onChatCleared?: () => void;
selectedConversation?: Conversation;
}

const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;

const ConditionalWrap = ({
condition,
wrap,
children,
}: {
condition: boolean;
wrap: (children: React.ReactElement) => React.ReactElement;
children: React.ReactElement;
}) => (condition ? wrap(children) : children);

export const SettingsContextMenu: React.FC<Params> = React.memo(
({ isDisabled = false, onChatCleared }: Params) => {
({ isDisabled = false, onChatCleared, selectedConversation }: Params) => {
const { euiTheme } = useEuiTheme();
const {
navigateToApp,
Expand Down Expand Up @@ -117,6 +136,16 @@ export const SettingsContextMenu: React.FC<Params> = React.memo(
[setShowAnonymizedValues]
);

const selectedConversationHasCitations = useMemo(
() => conversationContainsContentReferences(selectedConversation),
[selectedConversation]
);

const selectedConversationHasAnonymizedValues = useMemo(
() => conversationContainsAnonymizedValues(selectedConversation),
[selectedConversation]
);

const items = useMemo(
() => [
<EuiContextMenuItem
Expand Down Expand Up @@ -174,43 +203,114 @@ export const SettingsContextMenu: React.FC<Params> = React.memo(
<h3>{i18n.CHAT_OPTIONS}</h3>
</EuiTitle>
<EuiHorizontalRule margin="none" />
<EuiToolTip
position="left"
key={'anonymize-values-tooltip'}
content={i18n.ANONYMIZE_VALUES_TOOLTIP}
<EuiContextMenuItem
aria-label={'anonymize-values'}
key={'anonymize-values'}
data-test-subj={'anonymize-values'}
>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<ConditionalWrap
condition={!selectedConversationHasAnonymizedValues}
wrap={(children) => (
<EuiToolTip
position="top"
key={'disabled-anonymize-values-tooltip'}
content={
<FormattedMessage
id="xpack.elasticAssistant.assistant.settings.anonymizeValues.disabled.tooltip"
defaultMessage="This conversation does not contain anonymized fields."
/>
}
>
{children}
</EuiToolTip>
)}
>
<EuiSwitch
label={i18n.ANONYMIZE_VALUES}
checked={showAnonymizedValues}
onChange={onChangeShowAnonymizedValues}
compressed
disabled={!selectedConversationHasAnonymizedValues}
/>
</ConditionalWrap>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
key={'anonymize-values-tooltip'}
content={
<FormattedMessage
id="xpack.elasticAssistant.assistant.settings.anonymizeValues.tooltip"
defaultMessage="Toggle to reveal or obfuscate field values in your chat stream. The data sent to the LLM is still anonymized based on settings in the Anonymization panel. Keyboard shortcut: <bold>{keyboardShortcut}</bold>"
values={{
keyboardShortcut: isMac ? '⌥ + a' : 'Alt + a',
bold: (str) => <strong>{str}</strong>,
}}
/>
}
>
<EuiIcon tabIndex={0} type="iInCircle" />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiContextMenuItem>

{contentReferencesEnabled && (
<EuiContextMenuItem
aria-label={'anonymize-values'}
key={'anonymize-values'}
data-test-subj={'anonymize-values'}
aria-label={'show-citations'}
key={'show-citations'}
data-test-subj={'show-citations'}
>
<EuiSwitch
label={i18n.ANONYMIZE_VALUES}
checked={showAnonymizedValues}
onChange={onChangeShowAnonymizedValues}
compressed
/>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<ConditionalWrap
condition={!selectedConversationHasCitations}
wrap={(children) => (
<EuiToolTip
position="top"
key={'disabled-anonymize-values-tooltip'}
content={
<FormattedMessage
id="xpack.elasticAssistant.assistant.settings.showCitationsLabel.disabled.tooltip"
defaultMessage="This conversation does not contain citations."
/>
}
>
{children}
</EuiToolTip>
)}
>
<EuiSwitch
label={i18n.SHOW_CITATIONS}
checked={contentReferencesVisible}
onChange={onChangeContentReferencesVisible}
compressed
disabled={!selectedConversationHasCitations}
/>
</ConditionalWrap>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
key={'show-citations-tooltip'}
content={
<FormattedMessage
id="xpack.elasticAssistant.assistant.settings.showCitationsLabel.tooltip"
defaultMessage="Keyboard shortcut: <bold>{keyboardShortcut}</bold>"
values={{
keyboardShortcut: isMac ? '⌥ + c' : 'Alt + c',
bold: (str) => <strong>{str}</strong>,
}}
/>
}
>
<EuiIcon tabIndex={0} type="iInCircle" />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiContextMenuItem>
</EuiToolTip>
{contentReferencesEnabled && (
<EuiToolTip
position="left"
key={'show-citations-tooltip'}
content={i18n.SHOW_CITATIONS_TOOLTIP}
>
<EuiContextMenuItem
aria-label={'show-citations'}
key={'show-citations'}
data-test-subj={'show-citations'}
>
<EuiSwitch
label={i18n.SHOW_CITATIONS}
checked={contentReferencesVisible}
onChange={onChangeContentReferencesVisible}
compressed
/>
</EuiContextMenuItem>
</EuiToolTip>
)}
<EuiHorizontalRule margin="none" />
<EuiContextMenuItem
Expand Down Expand Up @@ -242,6 +342,8 @@ export const SettingsContextMenu: React.FC<Params> = React.memo(
contentReferencesEnabled,
euiTheme.size.m,
euiTheme.size.xs,
selectedConversationHasCitations,
selectedConversationHasAnonymizedValues,
]
);

Expand Down
Loading

0 comments on commit b59712f

Please # to comment.