From 8b05944d9765cd723adcdd64aabad652250b1f42 Mon Sep 17 00:00:00 2001 From: Andrew Macri Date: Sun, 14 May 2023 19:26:02 -0600 Subject: [PATCH] [Security Solution] Security Assistant: Add note to Timeline #9 - Add note to Timeline - Common New Chat button for alert flyout and Timeline - Updated context descriptions --- .../event_details/alert_summary_view.tsx | 36 +----------- .../event_details/summary_view.test.tsx | 16 +----- .../components/event_details/summary_view.tsx | 5 +- .../components/event_details/translations.ts | 32 +++++++++-- .../context_pills/index.tsx | 9 ++- .../prompt_context/helpers.ts | 34 ++---------- .../security_assistant/security_assistant.tsx | 45 +++++++++++++-- .../public/security_assistant/translations.ts | 8 +++ .../event_details/expandable_event.tsx | 48 +++++++++++----- .../event_details/flyout/header.tsx | 5 ++ .../side_panel/event_details/index.tsx | 55 +++++++++++++++++-- .../timeline/tabs_content/index.tsx | 2 +- .../timeline/tabs_content/translations.ts | 7 +++ 13 files changed, 192 insertions(+), 110 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 6e18297bb6b5d..5704eab502ed8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -5,17 +5,12 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import type { BrowserFields } from '../../../../common/search_strategy/index_fields'; import { getSummaryRows } from './get_alert_summary_rows'; -import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE } from '../../../security_assistant/content/prompts/user/translations'; -import { useSecurityAssistantContext } from '../../../security_assistant/security_assistant_context'; import { SummaryView } from './summary_view'; -import * as i18n from './translations'; -import { getPromptContextFromEventDetailsItem } from '../../../security_assistant/prompt_context/helpers'; -import { getUniquePromptContextId } from '../../../security_assistant/security_assistant_context/helpers'; const AlertSummaryViewComponent: React.FC<{ browserFields: BrowserFields; @@ -32,35 +27,8 @@ const AlertSummaryViewComponent: React.FC<{ [browserFields, data, eventId, isDraggable, scopeId, isReadOnly] ); - const { registerPromptContext, unRegisterPromptContext } = useSecurityAssistantContext(); - const promptContextId = useMemo(() => getUniquePromptContextId(), []); - - const getPromptContext = useCallback( - async () => getPromptContextFromEventDetailsItem(data), - [data] - ); - - useEffect(() => { - registerPromptContext({ - category: 'alert', - description: i18n.ALERT_SUMMARY_VIEW_CONTEXT_DESCRIPTION, - id: promptContextId, - getPromptContext, - suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE, - tooltip: i18n.ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP, - }); - - return () => unRegisterPromptContext(promptContextId); - }, [getPromptContext, promptContextId, registerPromptContext, unRegisterPromptContext]); - return ( - + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx index 19f822876535c..e465bfb37407e 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx @@ -53,7 +53,6 @@ const enrichedHostIpData: AlertSummaryRow['description'] = { }; const mockCount = 90019001; -const promptContextId = 'abcd'; jest.mock('../../containers/alerts/use_alert_prevalence', () => ({ useAlertPrevalence: () => ({ @@ -68,12 +67,7 @@ describe('Summary View', () => { test('should show an empty table', () => { render( - + ); expect(screen.getByText('No items found')).toBeInTheDocument(); @@ -91,12 +85,7 @@ describe('Summary View', () => { render( - + ); // Shows the field name @@ -127,7 +116,6 @@ describe('Summary View', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index acd6c04fe1b11..d4ddc993a9fc6 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -18,7 +18,6 @@ import { import React from 'react'; import type { AlertSummaryRow } from './helpers'; -import { NewChat } from '../../../security_assistant/new_chat'; import * as i18n from './translations'; import { VIEW_ALL_FIELDS } from './translations'; import { SummaryTable } from './table/summary_table'; @@ -70,15 +69,13 @@ const rowProps = { const SummaryViewComponent: React.FC<{ goToTable: () => void; title: string; - promptContextId: string; rows: AlertSummaryRow[]; isReadOnly?: boolean; -}> = ({ goToTable, promptContextId, rows, title, isReadOnly }) => { +}> = ({ goToTable, rows, title, isReadOnly }) => { const columns = isReadOnly ? baseColumns : allColumns; return (
- diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index ff7d8f85fa524..4052a03a54928 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -147,16 +147,36 @@ export const VIEW_ALL_FIELDS = i18n.translate('xpack.securitySolution.eventDetai defaultMessage: 'View all fields in table', }); -export const ALERT_SUMMARY_VIEW_CONTEXT_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.alertSummaryView.contextDescription', +export const SUMMARY_VIEW = i18n.translate('xpack.securitySolution.eventDetails.summaryView', { + defaultMessage: 'summary', +}); + +export const TIMELINE_VIEW = i18n.translate('xpack.securitySolution.eventDetails.timelineView', { + defaultMessage: 'Timeline', +}); + +export const ALERT_SUMMARY_CONTEXT_DESCRIPTION = (view: string) => + i18n.translate('xpack.securitySolution.alertSummaryView.alertSummaryViewContextDescription', { + defaultMessage: 'Alert (from {view})', + values: { view }, + }); + +export const ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP = i18n.translate( + 'xpack.securitySolution.alertSummaryView.alertSummaryViewContextTooltip', { - defaultMessage: 'Alert (summary view)', + defaultMessage: 'Use this alert for context', } ); -export const ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP = i18n.translate( - 'xpack.securitySolution.alertSummaryView.contextTooltip', +export const EVENT_SUMMARY_CONTEXT_DESCRIPTION = (view: string) => + i18n.translate('xpack.securitySolution.alertSummaryView.eventSummaryViewContextDescription', { + defaultMessage: 'Event (from {view})', + values: { view }, + }); + +export const EVENT_SUMMARY_VIEW_CONTEXT_TOOLTIP = i18n.translate( + 'xpack.securitySolution.alertSummaryView.eventSummaryViewContextTooltip', { - defaultMessage: 'Use this alert for context', + defaultMessage: 'Use this event for context', } ); diff --git a/x-pack/plugins/security_solution/public/security_assistant/context_pills/index.tsx b/x-pack/plugins/security_solution/public/security_assistant/context_pills/index.tsx index e320d3a225cc5..90dae354f1428 100644 --- a/x-pack/plugins/security_solution/public/security_assistant/context_pills/index.tsx +++ b/x-pack/plugins/security_solution/public/security_assistant/context_pills/index.tsx @@ -6,12 +6,17 @@ */ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import styled from 'styled-components'; import { sortBy } from 'lodash/fp'; import React, { useCallback, useMemo } from 'react'; import type { PromptContext } from '../prompt_context/types'; +const PillButton = styled(EuiButton)` + margin-right: ${({ theme }) => theme.eui.euiSizeXS}; +`; + interface Props { promptContexts: Record; selectedPromptContextIds: string[]; @@ -42,14 +47,14 @@ const ContextPillsComponent: React.FC = ({ {sortedPromptContexts.map(({ description, id, getPromptContext, tooltip }) => ( - selectPromptContext(id)} > {description} - + ))} diff --git a/x-pack/plugins/security_solution/public/security_assistant/prompt_context/helpers.ts b/x-pack/plugins/security_solution/public/security_assistant/prompt_context/helpers.ts index cbb857966d255..efc765533b88b 100644 --- a/x-pack/plugins/security_solution/public/security_assistant/prompt_context/helpers.ts +++ b/x-pack/plugins/security_solution/public/security_assistant/prompt_context/helpers.ts @@ -12,38 +12,16 @@ export interface QueryField { values: string; } -export const getQueryFields = (data: TimelineEventsDetailsItem[]): QueryField[] => [ - ...data - ?.filter((x) => x.field === 'kibana.alert.rule.description') - ?.map((x) => ({ - field: 'kibana.alert.rule.description', - values: x.values?.join(',\n') ?? '', - })), - ...data - ?.filter((x) => x.field === 'event.category') - ?.map((x) => ({ field: 'event.category', values: x.values?.join(',\n') ?? '' })), - ...data - ?.filter((x) => x.field === 'event.action') - ?.map((x) => ({ field: 'event.action', values: x.values?.join(',\n') ?? '' })), - ...data - ?.filter((x) => x.field === 'host.name') - ?.map((x) => ({ field: 'host.name', values: x.values?.join(',\n') ?? '' })), - ...data - ?.filter((x) => x.field === 'kibana.alert.reason') - ?.map((x) => ({ field: 'kibana.alert.reason', values: x.values?.join(',\n') ?? '' })), - ...data - ?.filter((x) => x.field === 'destination.ip') - ?.map((x) => ({ field: 'destination.ip', values: x.values?.join(',\n') ?? '' })), - ...data - ?.filter((x) => x.field === 'user.name') - ?.map((x) => ({ field: 'user.name', values: x.values?.join(',\n') ?? '' })), -]; +export const getAllFields = (data: TimelineEventsDetailsItem[]): QueryField[] => + data + .filter(({ field }) => !field.startsWith('signal.')) + .map(({ field, values }) => ({ field, values: values?.join(',') ?? '' })); export const getFieldsAsCsv = (queryFields: QueryField[]): string => queryFields.map(({ field, values }) => `${field},${values}`).join('\n'); export const getPromptContextFromEventDetailsItem = (data: TimelineEventsDetailsItem[]): string => { - const queryFields = getQueryFields(data); + const allFields = getAllFields(data); - return getFieldsAsCsv(queryFields); + return getFieldsAsCsv(allFields); }; diff --git a/x-pack/plugins/security_solution/public/security_assistant/security_assistant.tsx b/x-pack/plugins/security_solution/public/security_assistant/security_assistant.tsx index 0b2e919478818..27bba55c0b2a9 100644 --- a/x-pack/plugins/security_solution/public/security_assistant/security_assistant.tsx +++ b/x-pack/plugins/security_solution/public/security_assistant/security_assistant.tsx @@ -26,9 +26,10 @@ import { CommentType } from '@kbn/cases-plugin/common'; import styled from 'styled-components'; import { createPortal } from 'react-dom'; import { css } from '@emotion/react'; +import { useDispatch } from 'react-redux'; import * as i18n from './translations'; -import { useKibana } from '../common/lib/kibana'; +import { useKibana, useToasts } from '../common/lib/kibana'; import { getCombinedMessage, getMessageFromRawResponse, isFileHash } from './helpers'; import { SettingsPopover } from './settings_popover'; @@ -46,6 +47,11 @@ import type { Prompt } from './types'; import { getPromptById } from './prompt_editor/helpers'; import { augmentMessageCodeBlocks } from './use_conversation/helpers'; import { QuickPrompts } from './quick_prompts/quick_prompts'; +import { updateAndAssociateNode } from '../timelines/components/notes/helpers'; +import { timelineActions } from '../timelines/store/timeline'; +import { appActions } from '../common/store/actions'; +import { TimelineId } from '../../common/types'; +import type { Note } from '../common/lib/note'; const CommentsContainer = styled.div` max-height: 600px; @@ -148,6 +154,34 @@ export const SecurityAssistant: React.FC = ); //// + const dispatch = useDispatch(); + const toasts = useToasts(); + + const associateNote = useCallback( + (noteId: string) => dispatch(timelineActions.addNote({ id: TimelineId.active, noteId })), + [dispatch] + ); + + const updateNote = useCallback( + (note: Note) => dispatch(appActions.updateNote({ note })), + [dispatch] + ); + + const handleAddNoteToTimelineClick = useCallback( + (messageContents: string) => { + updateAndAssociateNode({ + associateNote, + newNote: messageContents, + updateNewNote: () => {}, + updateNote, + user: '', // TODO: attribute assistant messages + }); + + toasts.addSuccess(i18n.ADDED_NOTE_TO_TIMELINE); + }, + [associateNote, toasts, updateNote] + ); + // Handles sending latest user prompt to API const handleSendMessage = useCallback( async (promptText) => { @@ -200,11 +234,14 @@ export const SecurityAssistant: React.FC = conversationId: selectedConversationId, message, }); + + // Reset prompt context selection and preview before sending: + setSelectedPromptContextIds([]); + setPromptTextPreview(''); + const rawResponse = await sendMessages(updatedMessages); const responseMessage: Message = getMessageFromRawResponse(rawResponse); appendMessage({ conversationId: selectedConversationId, message: responseMessage }); - setSelectedPromptContextIds([]); - setPromptTextPreview(''); } }, [ @@ -319,7 +356,7 @@ export const SecurityAssistant: React.FC = <> handleAddToExistingCaseClick(message.content)} + onClick={() => handleAddNoteToTimelineClick(message.content)} iconType="editorComment" color="primary" aria-label="Add message content as a timeline note" diff --git a/x-pack/plugins/security_solution/public/security_assistant/translations.ts b/x-pack/plugins/security_solution/public/security_assistant/translations.ts index d6668a751a224..fc4d18dcb7594 100644 --- a/x-pack/plugins/security_solution/public/security_assistant/translations.ts +++ b/x-pack/plugins/security_solution/public/security_assistant/translations.ts @@ -7,12 +7,20 @@ import { i18n } from '@kbn/i18n'; +export const ADDED_NOTE_TO_TIMELINE = i18n.translate( + 'xpack.securitySolution.securityAssistant.addedNoteToTimeline', + { + defaultMessage: 'Added note to timeline', + } +); + export const SECURITY_ASSISTANT_TITLE = i18n.translate( 'xpack.securitySolution.securityAssistant.title', { defaultMessage: 'Elastic Security Assistant', } ); + export const PROMPT_PLACEHOLDER = i18n.translate( 'xpack.securitySolution.securityAssistant.promptPlaceholder', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index addeb6889af1c..4504e1ab8e463 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -35,6 +35,7 @@ import * as i18n from './translations'; import { PreferenceFormattedDate } from '../../../../common/components/formatted_date'; import { SecurityPageName } from '../../../../../common/constants'; import { useGetAlertDetailsFlyoutLink } from './use_get_alert_details_flyout_link'; +import { NewChat } from '../../../../security_assistant/new_chat'; export type HandleOnEventClosed = () => void; interface Props { @@ -58,6 +59,7 @@ interface ExpandableEventTitleProps { eventIndex: string; isAlert: boolean; loading: boolean; + promptContextId?: string; ruleName?: string; timestamp: string; handleOnEventClosed?: HandleOnEventClosed; @@ -80,7 +82,16 @@ const StyledEuiFlexItem = styled(EuiFlexItem)` `; export const ExpandableEventTitle = React.memo( - ({ eventId, eventIndex, isAlert, loading, handleOnEventClosed, ruleName, timestamp }) => { + ({ + eventId, + eventIndex, + isAlert, + loading, + handleOnEventClosed, + promptContextId, + ruleName, + timestamp, + }) => { const isAlertDetailsPageEnabled = useIsExperimentalFeatureEnabled('alertDetailsPageEnabled'); const { onClick } = useGetSecuritySolutionLinkProps()({ deepLinkId: SecurityPageName.alerts, @@ -134,19 +145,30 @@ export const ExpandableEventTitle = React.memo( /> )} - {isAlert && alertDetailsLink && ( - - {(copy) => ( - - {i18n.SHARE_ALERT} - + + + {promptContextId != null && ( + + + )} - - )} + {isAlert && alertDetailsLink && ( + + + {(copy) => ( + + {i18n.SHARE_ALERT} + + )} + + + )} + + diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx index eac84c1853693..8b3d50d849c4b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/header.tsx @@ -18,6 +18,7 @@ interface FlyoutHeaderComponentProps { isHostIsolationPanelOpen: boolean; isolateAction: 'isolateHost' | 'unisolateHost'; loading: boolean; + promptContextId?: string; ruleName: string; showAlertDetails: () => void; timestamp: string; @@ -30,6 +31,7 @@ const FlyoutHeaderContentComponent = ({ isHostIsolationPanelOpen, isolateAction, loading, + promptContextId, ruleName, showAlertDetails, timestamp, @@ -44,6 +46,7 @@ const FlyoutHeaderContentComponent = ({ eventIndex={eventIndex} isAlert={isAlert} loading={loading} + promptContextId={promptContextId} ruleName={ruleName} timestamp={timestamp} /> @@ -60,6 +63,7 @@ const FlyoutHeaderComponent = ({ isHostIsolationPanelOpen, isolateAction, loading, + promptContextId, ruleName, showAlertDetails, timestamp, @@ -73,6 +77,7 @@ const FlyoutHeaderComponent = ({ isHostIsolationPanelOpen={isHostIsolationPanelOpen} isolateAction={isolateAction} loading={loading} + promptContextId={promptContextId} ruleName={ruleName} showAlertDetails={showAlertDetails} timestamp={timestamp} diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 059f4322d8970..09d4ac2f4648a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -6,7 +6,7 @@ */ import { EuiSpacer, EuiFlyoutBody } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -21,6 +21,18 @@ import { useBasicDataFromDetailsData, getAlertIndexAlias } from './helpers'; import { useSpaceId } from '../../../../common/hooks/use_space_id'; import { EndpointIsolateSuccess } from '../../../../common/components/endpoint/host_isolation'; import { HostIsolationPanel } from '../../../../detections/components/host_isolation'; +import { useSecurityAssistantContext } from '../../../../security_assistant/security_assistant_context'; +import { getUniquePromptContextId } from '../../../../security_assistant/security_assistant_context/helpers'; +import { getPromptContextFromEventDetailsItem } from '../../../../security_assistant/prompt_context/helpers'; +import { + ALERT_SUMMARY_CONTEXT_DESCRIPTION, + ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP, + EVENT_SUMMARY_CONTEXT_DESCRIPTION, + EVENT_SUMMARY_VIEW_CONTEXT_TOOLTIP, + SUMMARY_VIEW, + TIMELINE_VIEW, +} from '../../../../common/components/event_details/translations'; +import { EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE } from '../../../../security_assistant/content/prompts/user/translations'; interface EventDetailsPanelProps { browserFields: BrowserFields; @@ -76,6 +88,38 @@ const EventDetailsPanelComponent: React.FC = ({ const { alertId, isAlert, hostName, ruleName, timestamp } = useBasicDataFromDetailsData(detailsData); + const { registerPromptContext, unRegisterPromptContext } = useSecurityAssistantContext(); + const promptContextId = useMemo(() => getUniquePromptContextId(), []); + + const getPromptContext = useCallback( + async () => getPromptContextFromEventDetailsItem(detailsData ?? []), + [detailsData] + ); + + useEffect(() => { + const view = isFlyoutView ? SUMMARY_VIEW : TIMELINE_VIEW; + + registerPromptContext({ + category: isAlert ? 'alert' : 'event', + description: isAlert + ? ALERT_SUMMARY_CONTEXT_DESCRIPTION(view) + : EVENT_SUMMARY_CONTEXT_DESCRIPTION(view), + id: promptContextId, + getPromptContext, + suggestedUserPrompt: EXPLAIN_THEN_SUMMARIZE_SUGGEST_INVESTIGATION_GUIDE, + tooltip: isAlert ? ALERT_SUMMARY_VIEW_CONTEXT_TOOLTIP : EVENT_SUMMARY_VIEW_CONTEXT_TOOLTIP, + }); + + return () => unRegisterPromptContext(promptContextId); + }, [ + getPromptContext, + isAlert, + isFlyoutView, + promptContextId, + registerPromptContext, + unRegisterPromptContext, + ]); + const header = useMemo( () => isFlyoutView || isHostIsolationPanelOpen ? ( @@ -89,6 +133,7 @@ const EventDetailsPanelComponent: React.FC = ({ ruleName={ruleName} showAlertDetails={showAlertDetails} timestamp={timestamp} + promptContextId={promptContextId} /> ) : ( = ({ ruleName={ruleName} timestamp={timestamp} handleOnEventClosed={handleOnEventClosed} + promptContextId={promptContextId} /> ), [ + isFlyoutView, + isHostIsolationPanelOpen, expandedEvent.eventId, eventIndex, - handleOnEventClosed, isAlert, - isFlyoutView, - isHostIsolationPanelOpen, isolateAction, loading, ruleName, showAlertDetails, timestamp, + handleOnEventClosed, + promptContextId, ] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 8381ee2d0d46d..239945cfb6ae7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -441,7 +441,7 @@ const TabsContentComponent: React.FC = ({ isSelected={activeTab === TimelineTabs.securityAssistant} key={TimelineTabs.securityAssistant} > - {'Security Assistant++'} + {i18n.SECURITY_ASSISTANT} )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts index e3a53675389b7..cc8e07b36c7c0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts @@ -39,6 +39,13 @@ export const PINNED_TAB = i18n.translate( } ); +export const SECURITY_ASSISTANT = i18n.translate( + 'xpack.securitySolution.timeline.tabs.securityAssistantTimelineTitle', + { + defaultMessage: 'Security Assistant', + } +); + export const SESSION_TAB = i18n.translate( 'xpack.securitySolution.timeline.tabs.sessionTabTimelineTitle', {