From a75a9b8a6fcb52a468c8beb43e7c593bb106fa9f Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 20 Nov 2020 18:52:17 +0000 Subject: [PATCH 01/60] init alert details tab --- .../event_details/event_details.tsx | 69 +++++++++----- .../event_details/stateful_event_details.tsx | 47 ---------- .../components/event_details/summary_view.tsx | 90 +++++++++++++++++++ .../components/event_details/translations.ts | 11 +++ .../events_viewer/event_details_flyout.tsx | 26 ++---- .../timeline/body/events/stateful_event.tsx | 3 +- .../components/timeline/event_details.tsx | 6 +- .../timeline/expandable_event/index.tsx | 57 ++++++++---- .../expandable_event/translations.tsx | 9 +- 9 files changed, 204 insertions(+), 114 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index a2a7182a768cc..b7c8666491b95 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -5,9 +5,10 @@ */ import { EuiLink, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; +import { get } from 'lodash/fp'; import { BrowserFields } from '../../containers/source'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; @@ -15,11 +16,13 @@ import { OnUpdateColumns } from '../../../timelines/components/timeline/events'; import { EventFieldsBrowser } from './event_fields_browser'; import { JsonView } from './json_view'; import * as i18n from './translations'; +import { SummaryView } from './summary_view'; -export type View = EventsViewType.tableView | EventsViewType.jsonView; +export type View = EventsViewType.tableView | EventsViewType.jsonView | EventsViewType.summaryView; export enum EventsViewType { tableView = 'table-view', jsonView = 'json-view', + summaryView = 'summary-view', } const CollapseLink = styled(EuiLink)` @@ -33,9 +36,7 @@ interface Props { columnHeaders: ColumnHeaderOptions[]; data: TimelineEventsDetailsItem[]; id: string; - view: EventsViewType; onUpdateColumns: OnUpdateColumns; - onViewSelected: (selected: EventsViewType) => void; timelineId: string; toggleColumn: (column: ColumnHeaderOptions) => void; } @@ -47,23 +48,35 @@ const Details = styled.div` Details.displayName = 'Details'; export const EventDetails = React.memo( - ({ - browserFields, - columnHeaders, - data, - id, - view, - onUpdateColumns, - onViewSelected, - timelineId, - toggleColumn, - }) => { - const handleTabClick = useCallback((e) => onViewSelected(e.id as EventsViewType), [ - onViewSelected, - ]); + ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => { + const [view, setView] = useState(EventsViewType.tableView); + const handleTabClick = useCallback((e) => setView(e.id), [setView]); + const eventKindData = useMemo(() => (data || []).find((item) => item.field === 'event.kind'), [ + data, + ]); + const eventKind = get('values.0', eventKindData); + const alerts = useMemo( + () => [ + { + id: EventsViewType.summaryView, + name: i18n.SUMMARY, + content: ( + + ), + }, + ], + [data, id, browserFields, columnHeaders, timelineId] + ); const tabs: EuiTabbedContentTab[] = useMemo( () => [ + ...(eventKind !== 'event' ? alerts : []), { id: EventsViewType.tableView, name: i18n.TABLE, @@ -85,16 +98,24 @@ export const EventDetails = React.memo( content: , }, ], - [browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn] + [ + browserFields, + columnHeaders, + data, + id, + onUpdateColumns, + timelineId, + toggleColumn, + alerts, + eventKind, + ] ); + const selectedTab = useMemo(() => tabs.find((tab) => tab.id === view), [tabs, view]); + return (
- +
); } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx deleted file mode 100644 index 4730dc5c2264f..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/stateful_event_details.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useState } from 'react'; - -import { BrowserFields } from '../../containers/source'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; -import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; -import { OnUpdateColumns } from '../../../timelines/components/timeline/events'; - -import { EventDetails, EventsViewType, View } from './event_details'; - -interface Props { - browserFields: BrowserFields; - columnHeaders: ColumnHeaderOptions[]; - data: TimelineEventsDetailsItem[]; - id: string; - onUpdateColumns: OnUpdateColumns; - timelineId: string; - toggleColumn: (column: ColumnHeaderOptions) => void; -} - -export const StatefulEventDetails = React.memo( - ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => { - // TODO: Move to the store - const [view, setView] = useState(EventsViewType.tableView); - - return ( - - ); - } -); - -StatefulEventDetails.displayName = 'StatefulEventDetails'; 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 new file mode 100644 index 0000000000000..bc85326702280 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useMemo } from 'react'; + +import { + EuiDescriptionList, + EuiSpacer, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; +import { get, getOr } from 'lodash/fp'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { OverflowField } from '../tables/helpers'; +import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; +import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; +import * as i18n from './translations'; + +type Summary = Array<{ title: string; description: JSX.Element }>; + +const fields = [ + '@timestamp', + 'signal.status', + 'signal.rule.name', + 'signal.rule.severity', + 'signal.rule.riskScore', + 'user.name', + 'host.name', + 'source.ip', + 'destination.ip', +]; + +const SummaryViewComponent: React.FC<{ + data: TimelineEventsDetailsItem[]; + eventId: string; + columnHeaders: ColumnHeaderOptions[]; + timelineId: string; +}> = ({ data, eventId, columnHeaders, timelineId }) => { + const summaryList = useMemo(() => { + return data.reduce((acc, item) => { + const column = columnHeaders.find((c) => c.id === item.field); + const fieldValue = getOr(null, 'values.0', item); + return fields.indexOf(item.field) >= 0 + ? [ + ...acc, + { + title: item.field, + description: ( + + ), + }, + ] + : acc; + }, []); + }, [data, columnHeaders, eventId, timelineId]); + + const messageData = useMemo(() => (data || []).find((item) => item.field === 'message'), [data]); + const message = get('values.0', messageData); + + return ( + <> + + + {message && ( + <> + + + {i18n.INVESTIGATION_GUIDE} + + + + + + )} + + ); +}; + +SummaryViewComponent.displayName = 'SummaryViewComponent'; + +export const SummaryView = React.memo(SummaryViewComponent); 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 19e71e0f37da6..76ae2cd4a88a8 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 @@ -6,6 +6,17 @@ import { i18n } from '@kbn/i18n'; +export const SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.summary', { + defaultMessage: 'Summary', +}); + +export const INVESTIGATION_GUIDE = i18n.translate( + 'xpack.securitySolution.alertDetails.summary.investigationGuide', + { + defaultMessage: 'Investigation guide', + } +); + export const TABLE = i18n.translate('xpack.securitySolution.eventDetails.table', { defaultMessage: 'Table', }); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index ad332b2759048..afa6d17360c37 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader } from '@elastic/eui'; +import { EuiFlyout } from '@elastic/eui'; import React, { useCallback } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -13,10 +13,7 @@ import { useDispatch } from 'react-redux'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { timelineActions } from '../../../timelines/store/timeline'; import { BrowserFields, DocValueFields } from '../../containers/source'; -import { - ExpandableEvent, - ExpandableEventTitle, -} from '../../../timelines/components/timeline/expandable_event'; +import { ExpandableEvent } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../hooks/use_selector'; const StyledEuiFlyout = styled(EuiFlyout)` @@ -56,18 +53,13 @@ const EventDetailsFlyoutComponent: React.FC = ({ return ( - - - - - - + ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 6c28c0ce16df1..44cc061f93368 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -112,7 +112,6 @@ const StatefulEventComponent: React.FC = ({ const handleOnEventToggled = useCallback(() => { const eventId = event._id; const indexName = event._index!; - dispatch( timelineActions.toggleExpandedEvent({ timelineId, @@ -127,7 +126,7 @@ const StatefulEventComponent: React.FC = ({ if (timelineId === TimelineId.active) { activeTimeline.toggleExpandedEvent({ eventId, indexName, loading: false }); } - }, [dispatch, event._id, event._index, timelineId]); + }, [dispatch, timelineId, event]); const associateNote = useCallback( (noteId: string) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index 4b595fad9be6f..67700630abd40 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -16,10 +16,7 @@ import deepEqual from 'fast-deep-equal'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; -import { - ExpandableEvent, - ExpandableEventTitle, -} from '../../../timelines/components/timeline/expandable_event'; +import { ExpandableEvent } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; interface EventDetailsProps { @@ -41,7 +38,6 @@ const EventDetailsComponent: React.FC = ({ return ( <> - void; } -export const ExpandableEventTitle = React.memo(() => ( +export const ExpandableEventTitle = React.memo(({ isAlert }: { isAlert: boolean }) => ( -

{i18n.EVENT_DETAILS}

+

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

)); @@ -63,6 +71,12 @@ export const ExpandableEvent = React.memo( skip: !event.eventId, }); + const eventKindData = useMemo( + () => (detailsData || []).find((item) => item.field === 'event.kind'), + [detailsData] + ); + const eventKind = get('values.0', eventKindData); + const onUpdateColumns = useCallback( (columns) => dispatch(timelineActions.updateColumns({ id: timelineId, columns })), [dispatch, timelineId] @@ -70,7 +84,7 @@ export const ExpandableEvent = React.memo( const handleRenderExpandedContent = useCallback( () => ( - ( return {i18n.EVENT_DETAILS_PLACEHOLDER}; } - if (loading) { - return ; - } - return ( - - - + <> + + {loading && } + {!loading && } + + + {loading && } + {!loading && ( + + + + )} + + ); } ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/translations.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/translations.tsx index a4c4679c82058..1086152adb55f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/translations.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/translations.tsx @@ -28,8 +28,15 @@ export const EVENT_DETAILS_PLACEHOLDER = i18n.translate( ); export const EVENT_DETAILS = i18n.translate( - 'xpack.securitySolution.timeline.expandableEvent.titleLabel', + 'xpack.securitySolution.timeline.expandableEvent.eventTitleLabel', { defaultMessage: 'Event details', } ); + +export const ALERT_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.expandableEvent.alertTitleLabel', + { + defaultMessage: 'Alert details', + } +); From 54d1032cc57a8b8828db4134b9ee2f4fb1097f3d Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 23 Nov 2020 12:39:10 +0000 Subject: [PATCH 02/60] styles --- .../timeline/events/details/index.ts | 1 + .../event_details/event_details.tsx | 17 +++++++----- .../components/event_details/summary_view.tsx | 26 ++++++++++++------- 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts index 9fa7f96599deb..80458e41e892b 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts @@ -9,6 +9,7 @@ import { Inspect, Maybe } from '../../../common'; import { TimelineRequestOptionsPaginated } from '../..'; export interface TimelineEventsDetailsItem { + category: string; field: string; values?: Maybe; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index b7c8666491b95..bdbd4237ecf07 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -5,7 +5,7 @@ */ import { EuiLink, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { get } from 'lodash/fp'; @@ -49,13 +49,13 @@ Details.displayName = 'Details'; export const EventDetails = React.memo( ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => { - const [view, setView] = useState(EventsViewType.tableView); - - const handleTabClick = useCallback((e) => setView(e.id), [setView]); const eventKindData = useMemo(() => (data || []).find((item) => item.field === 'event.kind'), [ data, ]); const eventKind = get('values.0', eventKindData); + const [view, setView] = useState(EventsViewType.tableView); + const handleTabClick = useCallback((e) => setView(e.id), [setView]); + const alerts = useMemo( () => [ { @@ -66,13 +66,12 @@ export const EventDetails = React.memo( data={data} eventId={id} browserFields={browserFields} - columnHeaders={columnHeaders} timelineId={timelineId} /> ), }, ], - [data, id, browserFields, columnHeaders, timelineId] + [data, id, browserFields, timelineId] ); const tabs: EuiTabbedContentTab[] = useMemo( () => [ @@ -111,6 +110,12 @@ export const EventDetails = React.memo( ] ); + useEffect(() => { + if (data != null && eventKind !== 'event') { + setView(EventsViewType.summaryView); + } + }, [data, eventKind]); + const selectedTab = useMemo(() => tabs.find((tab) => tab.id === view), [tabs, view]); return ( 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 bc85326702280..0c0e8bde24103 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 @@ -15,8 +15,8 @@ import { get, getOr } from 'lodash/fp'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { OverflowField } from '../tables/helpers'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; -import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import * as i18n from './translations'; +import { BrowserFields } from '../../../../common/search_strategy/index_fields'; type Summary = Array<{ title: string; description: JSX.Element }>; @@ -33,15 +33,21 @@ const fields = [ ]; const SummaryViewComponent: React.FC<{ + browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; eventId: string; - columnHeaders: ColumnHeaderOptions[]; timelineId: string; -}> = ({ data, eventId, columnHeaders, timelineId }) => { +}> = ({ data, eventId, timelineId, browserFields }) => { const summaryList = useMemo(() => { - return data.reduce((acc, item) => { - const column = columnHeaders.find((c) => c.id === item.field); + return (data || []).reduce((acc, item) => { const fieldValue = getOr(null, 'values.0', item); + const eventCategory = item.category; + const fieldType = getOr( + 'string', + `${eventCategory}.fields.${item.field}.type`, + browserFields + ); + const fieldFormat = get(`${eventCategory}.fields.${item.field}.format`, browserFields); return fields.indexOf(item.field) >= 0 ? [ ...acc, @@ -51,9 +57,9 @@ const SummaryViewComponent: React.FC<{ ), @@ -61,7 +67,7 @@ const SummaryViewComponent: React.FC<{ ] : acc; }, []); - }, [data, columnHeaders, eventId, timelineId]); + }, [data, eventId, timelineId, browserFields]); const messageData = useMemo(() => (data || []).find((item) => item.field === 'message'), [data]); const message = get('values.0', messageData); @@ -69,11 +75,11 @@ const SummaryViewComponent: React.FC<{ return ( <> - + {message && ( <> - + {i18n.INVESTIGATION_GUIDE} From e329b91bf7778c5acea4dd2cbc2b2e14a9c7e566 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 24 Nov 2020 17:54:55 +0000 Subject: [PATCH 03/60] readMore button --- .../components/event_details/summary_view.tsx | 72 +++++++++++++++++-- .../components/event_details/translations.ts | 8 +++ 2 files changed, 75 insertions(+), 5 deletions(-) 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 0c0e8bde24103..8c03cd21a6693 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 @@ -3,17 +3,19 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useRef, useState, useEffect } from 'react'; import { EuiDescriptionList, EuiSpacer, EuiDescriptionListTitle, EuiDescriptionListDescription, + EuiButtonEmpty, } from '@elastic/eui'; +import styled from 'styled-components'; + import { get, getOr } from 'lodash/fp'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { OverflowField } from '../tables/helpers'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; import * as i18n from './translations'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; @@ -31,6 +33,34 @@ const fields = [ 'source.ip', 'destination.ip', ]; +const LINE_CLAMP = 3; + +const LineClamp = styled.div<{ + line?: number | 'none'; + maxHeight?: number | 'none'; +}>` + display: -webkit-box; + -webkit-line-clamp: ${({ line = LINE_CLAMP }) => line}; + -webkit-box-orient: vertical; + overflow: scroll; + max-height: ${({ maxHeight = 'none' }) => (maxHeight === 'none' ? 'none' : `${maxHeight}em`)}}; +`; + +LineClamp.displayName = 'LineClamp'; + +const StyledDescription = styled(EuiDescriptionListDescription)` + word-break: break-all; +`; + +StyledDescription.displayName = 'StyledDescription'; + +const ReadMore = styled(EuiButtonEmpty)` + span.euiButtonContent { + padding: 0; + } +`; + +ReadMore.displayName = 'ReadMore'; const SummaryViewComponent: React.FC<{ browserFields: BrowserFields; @@ -71,6 +101,31 @@ const SummaryViewComponent: React.FC<{ const messageData = useMemo(() => (data || []).find((item) => item.field === 'message'), [data]); const message = get('values.0', messageData); + const [lineClamp, setLineClamp] = useState(LINE_CLAMP); + const [lineClampHeight, setLineClampHeight] = useState(4.5); + const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); + const [isOverflow, setIsOverflow] = useState(false); + const descriptionRef = useRef(); + const toggleReadMore = () => { + setLineClamp((prevState) => (prevState !== 'none' ? 'none' : LINE_CLAMP)); + setLineClampHeight((prevState) => (prevState !== 'none' ? 'none' : 4.5)); + setReadMoreButtonText((prevState) => + prevState === i18n.READ_MORE ? i18n.READ_LESS : i18n.READ_MORE + ); + }; + + useEffect(() => { + if ( + message && + descriptionRef && + descriptionRef?.current && + descriptionRef?.current?.scrollHeight != null && + descriptionRef?.current?.clientHeight != null && + descriptionRef?.current?.scrollHeight > descriptionRef?.current?.clientHeight + ) { + setIsOverflow(true); + } + }, [descriptionRef, message]); return ( <> @@ -81,10 +136,17 @@ const SummaryViewComponent: React.FC<{ {i18n.INVESTIGATION_GUIDE} - - - + + + {message} + + + {(isOverflow || readMoreButtonText === i18n.READ_LESS) && ( + + {readMoreButtonText} + + )} )} 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 76ae2cd4a88a8..6fb49a6016c65 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 @@ -17,6 +17,14 @@ export const INVESTIGATION_GUIDE = i18n.translate( } ); +export const READ_MORE = i18n.translate('xpack.securitySolution.alertDetails.summary.readMore', { + defaultMessage: 'Read More', +}); + +export const READ_LESS = i18n.translate('xpack.securitySolution.alertDetails.summary.readLess', { + defaultMessage: 'Read Less', +}); + export const TABLE = i18n.translate('xpack.securitySolution.eventDetails.table', { defaultMessage: 'Table', }); From c9fcf9673575cab1d1a15bed9326fca33a8ddcbf Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Nov 2020 00:43:45 +0000 Subject: [PATCH 04/60] readmore btn --- .../components/event_details/summary_view.tsx | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) 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 8c03cd21a6693..0a1a3390cb9a9 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 @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useRef, useState, useEffect } from 'react'; +import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react'; import { EuiDescriptionList, @@ -25,7 +25,7 @@ type Summary = Array<{ title: string; description: JSX.Element }>; const fields = [ '@timestamp', 'signal.status', - 'signal.rule.name', + 'signal.rule.description', 'signal.rule.severity', 'signal.rule.riskScore', 'user.name', @@ -34,34 +34,27 @@ const fields = [ 'destination.ip', ]; const LINE_CLAMP = 3; +const LINE_CLAMP_HEIGHT = 4.5; -const LineClamp = styled.div<{ - line?: number | 'none'; - maxHeight?: number | 'none'; -}>` +const LineClamp = styled.div` display: -webkit-box; - -webkit-line-clamp: ${({ line = LINE_CLAMP }) => line}; + -webkit-line-clamp: ${LINE_CLAMP}; -webkit-box-orient: vertical; - overflow: scroll; - max-height: ${({ maxHeight = 'none' }) => (maxHeight === 'none' ? 'none' : `${maxHeight}em`)}}; + overflow: hidden; + max-height: ${`${LINE_CLAMP_HEIGHT}em`}; + height: ${`${LINE_CLAMP_HEIGHT}em`}; `; -LineClamp.displayName = 'LineClamp'; - const StyledDescription = styled(EuiDescriptionListDescription)` word-break: break-all; `; -StyledDescription.displayName = 'StyledDescription'; - const ReadMore = styled(EuiButtonEmpty)` span.euiButtonContent { padding: 0; } `; -ReadMore.displayName = 'ReadMore'; - const SummaryViewComponent: React.FC<{ browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; @@ -99,50 +92,54 @@ const SummaryViewComponent: React.FC<{ }, []); }, [data, eventId, timelineId, browserFields]); - const messageData = useMemo(() => (data || []).find((item) => item.field === 'message'), [data]); + const messageData = (data || []).find((item) => item.field === 'message'); const message = get('values.0', messageData); - const [lineClamp, setLineClamp] = useState(LINE_CLAMP); - const [lineClampHeight, setLineClampHeight] = useState(4.5); const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); - const [isOverflow, setIsOverflow] = useState(false); + const [isOverflow, setIsOverflow] = useState(null); + const [isExpanded, setIsReadMoreClicked] = useState(null); const descriptionRef = useRef(); - const toggleReadMore = () => { - setLineClamp((prevState) => (prevState !== 'none' ? 'none' : LINE_CLAMP)); - setLineClampHeight((prevState) => (prevState !== 'none' ? 'none' : 4.5)); + const toggleReadMore = useCallback(() => { + setIsReadMoreClicked((prevState) => !prevState); setReadMoreButtonText((prevState) => prevState === i18n.READ_MORE ? i18n.READ_LESS : i18n.READ_MORE ); - }; + }, []); useEffect(() => { - if ( - message && - descriptionRef && - descriptionRef?.current && - descriptionRef?.current?.scrollHeight != null && - descriptionRef?.current?.clientHeight != null && - descriptionRef?.current?.scrollHeight > descriptionRef?.current?.clientHeight - ) { - setIsOverflow(true); + if (message != null && descriptionRef?.current?.clientHeight != null) { + if ( + (descriptionRef?.current?.scrollHeight ?? 0) > (descriptionRef?.current?.clientHeight ?? 0) + ) { + setIsOverflow(true); + } + + if ( + ((message == null || descriptionRef?.current?.scrollHeight) ?? 0) <= + (descriptionRef?.current?.clientHeight ?? 0) + ) { + setIsOverflow(false); + } } - }, [descriptionRef, message]); + }, [message, descriptionRef?.current?.clientHeight]); return ( <> - {message && ( + {message != null && ( <> {i18n.INVESTIGATION_GUIDE} - - {message} - + {isExpanded ? ( +

{message}

+ ) : ( + {message} + )}
- {(isOverflow || readMoreButtonText === i18n.READ_LESS) && ( + {isOverflow && ( {readMoreButtonText} @@ -153,6 +150,4 @@ const SummaryViewComponent: React.FC<{ ); }; -SummaryViewComponent.displayName = 'SummaryViewComponent'; - export const SummaryView = React.memo(SummaryViewComponent); From c65a78ce4d5bb2f4287f0f3c5ed70fa5c1cdf63c Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Nov 2020 14:53:42 +0000 Subject: [PATCH 05/60] field mappings --- .../components/event_details/summary_view.tsx | 148 +++++++++++++----- .../public/network/components/ip/index.tsx | 2 +- 2 files changed, 112 insertions(+), 38 deletions(-) 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 0a1a3390cb9a9..3521f21fa27ee 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 @@ -6,6 +6,7 @@ import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react'; import { + EuiBadge, EuiDescriptionList, EuiSpacer, EuiDescriptionListTitle, @@ -19,19 +20,38 @@ import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; import * as i18n from './translations'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; +import { + ALERTS_HEADERS_RISK_SCORE, + ALERTS_HEADERS_RULE, + ALERTS_HEADERS_SEVERITY, +} from '../../../detections/components/alerts_table/translations'; +import { + IP_FIELD_TYPE, + MESSAGE_FIELD_NAME, + SIGNAL_RULE_NAME_FIELD_NAME, +} from '../../../timelines/components/timeline/body/renderers/constants'; +import { + DESTINATION_IP_FIELD_NAME, + Ip, + SOURCE_IP_FIELD_NAME, +} from '../../../network/components/ip'; type Summary = Array<{ title: string; description: JSX.Element }>; const fields = [ - '@timestamp', - 'signal.status', - 'signal.rule.description', - 'signal.rule.severity', - 'signal.rule.riskScore', - 'user.name', - 'host.name', - 'source.ip', - 'destination.ip', + { id: 'signal.status' }, + { id: '@timestamp' }, + { + id: SIGNAL_RULE_NAME_FIELD_NAME, + linkField: 'signal.rule.description', + label: ALERTS_HEADERS_RULE, + }, + { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, + { id: 'signal.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE }, + { id: 'host.name' }, + { id: 'user.name' }, + { id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, + { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, ]; const LINE_CLAMP = 3; const LINE_CLAMP_HEIGHT = 4.5; @@ -55,6 +75,58 @@ const ReadMore = styled(EuiButtonEmpty)` } `; +const getDescription = ({ + contextId, + eventId, + fieldName, + value, + fieldType = '', + linkValue, +}: { + contextId: string; + eventId: string; + fieldName: string; + value?: string | null; + fieldType?: string; + linkValue?: string; +}) => { + if (fieldType === IP_FIELD_TYPE) { + return ( + + ); + } + + if (fieldName === 'signal.status') { + return ( + + + + ); + } + + return ( + + ); +}; + const SummaryViewComponent: React.FC<{ browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; @@ -62,42 +134,44 @@ const SummaryViewComponent: React.FC<{ timelineId: string; }> = ({ data, eventId, timelineId, browserFields }) => { const summaryList = useMemo(() => { - return (data || []).reduce((acc, item) => { - const fieldValue = getOr(null, 'values.0', item); - const eventCategory = item.category; - const fieldType = getOr( - 'string', - `${eventCategory}.fields.${item.field}.type`, - browserFields - ); - const fieldFormat = get(`${eventCategory}.fields.${item.field}.format`, browserFields); - return fields.indexOf(item.field) >= 0 - ? [ + const ruleIdField = data.find((d) => d.field === 'signal.rule.rule_id'); + const ruleId = getOr(null, 'values.0', ruleIdField); + return data != null + ? fields.reduce((acc, item) => { + const field = data.find((d) => d.field === item.id || d.field === item.linkField); + if (!field) { + return acc; + } + + const fieldValue = getOr(null, 'values.0', field); + const category = field.category; + const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; + const description = getDescription({ + contextId: timelineId, + eventId, + fieldName: item.id, + value: fieldValue, + fieldType: item.fieldType ?? fieldType, + linkValue: item.id === SIGNAL_RULE_NAME_FIELD_NAME ? ruleId : undefined, + }); + + return [ ...acc, { - title: item.field, - description: ( - - ), + title: item.label ?? item.id, + description, }, - ] - : acc; - }, []); - }, [data, eventId, timelineId, browserFields]); + ]; + }, []) + : []; + }, [browserFields, data, eventId, timelineId]); - const messageData = (data || []).find((item) => item.field === 'message'); + const messageData = (data || []).find((item) => item.field === MESSAGE_FIELD_NAME); const message = get('values.0', messageData); const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); const [isOverflow, setIsOverflow] = useState(null); const [isExpanded, setIsReadMoreClicked] = useState(null); - const descriptionRef = useRef(); + const descriptionRef = useRef(null); const toggleReadMore = useCallback(() => { setIsReadMoreClicked((prevState) => !prevState); setReadMoreButtonText((prevState) => diff --git a/x-pack/plugins/security_solution/public/network/components/ip/index.tsx b/x-pack/plugins/security_solution/public/network/components/ip/index.tsx index 701094cee88a2..4905fdc2e1f57 100644 --- a/x-pack/plugins/security_solution/public/network/components/ip/index.tsx +++ b/x-pack/plugins/security_solution/public/network/components/ip/index.tsx @@ -11,7 +11,7 @@ import { FormattedFieldValue } from '../../../timelines/components/timeline/body export const SOURCE_IP_FIELD_NAME = 'source.ip'; export const DESTINATION_IP_FIELD_NAME = 'destination.ip'; -const IP_FIELD_TYPE = 'ip'; +export const IP_FIELD_TYPE = 'ip'; /** * Renders text containing a draggable IP address (e.g. `source.ip`, From 88faf98399993dd37d7bf4451049968dc2598b09 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Nov 2020 16:38:28 +0000 Subject: [PATCH 06/60] add unit tests --- .../timeline/events/details/index.ts | 2 +- .../event_details/__mocks__/index.ts | 657 ++++++++++++++++++ .../__snapshots__/event_details.test.tsx.snap | 18 + .../event_details/event_details.test.tsx | 37 + .../event_details/summary_view.test.tsx | 42 ++ .../components/event_details/summary_view.tsx | 13 +- .../public/common/mock/mock_detail_item.ts | 4 + 7 files changed, 768 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts index 80458e41e892b..0346f3bb9439b 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts @@ -9,7 +9,7 @@ import { Inspect, Maybe } from '../../../common'; import { TimelineRequestOptionsPaginated } from '../..'; export interface TimelineEventsDetailsItem { - category: string; + category?: string; field: string; values?: Maybe; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts new file mode 100644 index 0000000000000..b4561e6d5bffd --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts @@ -0,0 +1,657 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mockAlertDetailsData = [ + { category: 'process', field: 'process.name', values: ['-'], originalValue: '-' }, + { category: 'process', field: 'process.pid', values: [0], originalValue: 0 }, + { category: 'process', field: 'process.executable', values: ['-'], originalValue: '-' }, + { + category: 'agent', + field: 'agent.hostname', + values: ['windows-native'], + originalValue: 'windows-native', + }, + { + category: 'agent', + field: 'agent.name', + values: ['windows-native'], + originalValue: 'windows-native', + }, + { + category: 'agent', + field: 'agent.id', + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + originalValue: 'abfe4a35-d5b4-42a0-a539-bd054c791769', + }, + { category: 'agent', field: 'agent.type', values: ['winlogbeat'], originalValue: 'winlogbeat' }, + { + category: 'agent', + field: 'agent.ephemeral_id', + values: ['b9850845-c000-4ddd-bd51-9978a07b7e7d'], + originalValue: 'b9850845-c000-4ddd-bd51-9978a07b7e7d', + }, + { category: 'agent', field: 'agent.version', values: ['7.10.0'], originalValue: '7.10.0' }, + { + category: 'winlog', + field: 'winlog.computer_name', + values: ['windows-native'], + originalValue: 'windows-native', + }, + { category: 'winlog', field: 'winlog.process.pid', values: [624], originalValue: 624 }, + { category: 'winlog', field: 'winlog.process.thread.id', values: [1896], originalValue: 1896 }, + { + category: 'winlog', + field: 'winlog.keywords', + values: ['Audit Failure'], + originalValue: ['Audit Failure'], + }, + { + category: 'winlog', + field: 'winlog.logon.failure.reason', + values: ['Unknown user name or bad password.'], + originalValue: 'Unknown user name or bad password.', + }, + { + category: 'winlog', + field: 'winlog.logon.failure.sub_status', + values: ['User logon with misspelled or bad password'], + originalValue: 'User logon with misspelled or bad password', + }, + { + category: 'winlog', + field: 'winlog.logon.failure.status', + values: ['This is either due to a bad username or authentication information'], + originalValue: 'This is either due to a bad username or authentication information', + }, + { category: 'winlog', field: 'winlog.logon.id', values: ['0x0'], originalValue: '0x0' }, + { category: 'winlog', field: 'winlog.logon.type', values: ['Network'], originalValue: 'Network' }, + { category: 'winlog', field: 'winlog.channel', values: ['Security'], originalValue: 'Security' }, + { + category: 'winlog', + field: 'winlog.event_data.Status', + values: ['0xc000006d'], + originalValue: '0xc000006d', + }, + { category: 'winlog', field: 'winlog.event_data.LogonType', values: ['3'], originalValue: '3' }, + { + category: 'winlog', + field: 'winlog.event_data.SubjectLogonId', + values: ['0x0'], + originalValue: '0x0', + }, + { + category: 'winlog', + field: 'winlog.event_data.TransmittedServices', + values: ['-'], + originalValue: '-', + }, + { + category: 'winlog', + field: 'winlog.event_data.LmPackageName', + values: ['-'], + originalValue: '-', + }, + { category: 'winlog', field: 'winlog.event_data.KeyLength', values: ['0'], originalValue: '0' }, + { + category: 'winlog', + field: 'winlog.event_data.SubjectUserName', + values: ['-'], + originalValue: '-', + }, + { + category: 'winlog', + field: 'winlog.event_data.FailureReason', + values: ['%%2313'], + originalValue: '%%2313', + }, + { + category: 'winlog', + field: 'winlog.event_data.SubjectDomainName', + values: ['-'], + originalValue: '-', + }, + { + category: 'winlog', + field: 'winlog.event_data.TargetUserName', + values: ['administrator'], + originalValue: 'administrator', + }, + { + category: 'winlog', + field: 'winlog.event_data.SubStatus', + values: ['0xc000006a'], + originalValue: '0xc000006a', + }, + { + category: 'winlog', + field: 'winlog.event_data.LogonProcessName', + values: ['NtLmSsp '], + originalValue: 'NtLmSsp ', + }, + { + category: 'winlog', + field: 'winlog.event_data.SubjectUserSid', + values: ['S-1-0-0'], + originalValue: 'S-1-0-0', + }, + { + category: 'winlog', + field: 'winlog.event_data.AuthenticationPackageName', + values: ['NTLM'], + originalValue: 'NTLM', + }, + { + category: 'winlog', + field: 'winlog.event_data.TargetUserSid', + values: ['S-1-0-0'], + originalValue: 'S-1-0-0', + }, + { category: 'winlog', field: 'winlog.opcode', values: ['Info'], originalValue: 'Info' }, + { category: 'winlog', field: 'winlog.record_id', values: [890770], originalValue: 890770 }, + { category: 'winlog', field: 'winlog.task', values: ['Logon'], originalValue: 'Logon' }, + { category: 'winlog', field: 'winlog.event_id', values: [4625], originalValue: 4625 }, + { + category: 'winlog', + field: 'winlog.provider_guid', + values: ['{54849625-5478-4994-a5ba-3e3b0328c30d}'], + originalValue: '{54849625-5478-4994-a5ba-3e3b0328c30d}', + }, + { + category: 'winlog', + field: 'winlog.activity_id', + values: ['{e148a943-f9c4-0001-5a39-81b88bbed601}'], + originalValue: '{e148a943-f9c4-0001-5a39-81b88bbed601}', + }, + { + category: 'winlog', + field: 'winlog.api', + values: ['wineventlog'], + originalValue: 'wineventlog', + }, + { + category: 'winlog', + field: 'winlog.provider_name', + values: ['Microsoft-Windows-Security-Auditing'], + originalValue: 'Microsoft-Windows-Security-Auditing', + }, + { category: 'log', field: 'log.level', values: ['information'], originalValue: 'information' }, + { category: 'source', field: 'source.port', values: [0], originalValue: 0 }, + { category: 'source', field: 'source.domain', values: ['-'], originalValue: '-' }, + { + category: 'source', + field: 'source.ip', + values: ['185.156.74.3'], + originalValue: '185.156.74.3', + }, + { + category: 'base', + field: 'message', + values: [ + 'An account failed to log on.\n\nSubject:\n\tSecurity ID:\t\tS-1-0-0\n\tAccount Name:\t\t-\n\tAccount Domain:\t\t-\n\tLogon ID:\t\t0x0\n\nLogon Type:\t\t\t3\n\nAccount For Which Logon Failed:\n\tSecurity ID:\t\tS-1-0-0\n\tAccount Name:\t\tadministrator\n\tAccount Domain:\t\t\n\nFailure Information:\n\tFailure Reason:\t\tUnknown user name or bad password.\n\tStatus:\t\t\t0xC000006D\n\tSub Status:\t\t0xC000006A\n\nProcess Information:\n\tCaller Process ID:\t0x0\n\tCaller Process Name:\t-\n\nNetwork Information:\n\tWorkstation Name:\t-\n\tSource Network Address:\t185.156.74.3\n\tSource Port:\t\t0\n\nDetailed Authentication Information:\n\tLogon Process:\t\tNtLmSsp \n\tAuthentication Package:\tNTLM\n\tTransited Services:\t-\n\tPackage Name (NTLM only):\t-\n\tKey Length:\t\t0\n\nThis event is generated when a logon request fails. It is generated on the computer where access was attempted.\n\nThe Subject fields indicate the account on the local system which requested the logon. This is most commonly a service such as the Server service, or a local process such as Winlogon.exe or Services.exe.\n\nThe Logon Type field indicates the kind of logon that was requested. The most common types are 2 (interactive) and 3 (network).\n\nThe Process Information fields indicate which account and process on the system requested the logon.\n\nThe Network Information fields indicate where a remote logon request originated. Workstation name is not always available and may be left blank in some cases.\n\nThe authentication information fields provide detailed information about this specific logon request.\n\t- Transited services indicate which intermediate services have participated in this logon request.\n\t- Package name indicates which sub-protocol was used among the NTLM protocols.\n\t- Key length indicates the length of the generated session key. This will be 0 if no session key was requested.', + ], + originalValue: + 'An account failed to log on.\n\nSubject:\n\tSecurity ID:\t\tS-1-0-0\n\tAccount Name:\t\t-\n\tAccount Domain:\t\t-\n\tLogon ID:\t\t0x0\n\nLogon Type:\t\t\t3\n\nAccount For Which Logon Failed:\n\tSecurity ID:\t\tS-1-0-0\n\tAccount Name:\t\tadministrator\n\tAccount Domain:\t\t\n\nFailure Information:\n\tFailure Reason:\t\tUnknown user name or bad password.\n\tStatus:\t\t\t0xC000006D\n\tSub Status:\t\t0xC000006A\n\nProcess Information:\n\tCaller Process ID:\t0x0\n\tCaller Process Name:\t-\n\nNetwork Information:\n\tWorkstation Name:\t-\n\tSource Network Address:\t185.156.74.3\n\tSource Port:\t\t0\n\nDetailed Authentication Information:\n\tLogon Process:\t\tNtLmSsp \n\tAuthentication Package:\tNTLM\n\tTransited Services:\t-\n\tPackage Name (NTLM only):\t-\n\tKey Length:\t\t0\n\nThis event is generated when a logon request fails. It is generated on the computer where access was attempted.\n\nThe Subject fields indicate the account on the local system which requested the logon. This is most commonly a service such as the Server service, or a local process such as Winlogon.exe or Services.exe.\n\nThe Logon Type field indicates the kind of logon that was requested. The most common types are 2 (interactive) and 3 (network).\n\nThe Process Information fields indicate which account and process on the system requested the logon.\n\nThe Network Information fields indicate where a remote logon request originated. Workstation name is not always available and may be left blank in some cases.\n\nThe authentication information fields provide detailed information about this specific logon request.\n\t- Transited services indicate which intermediate services have participated in this logon request.\n\t- Package name indicates which sub-protocol was used among the NTLM protocols.\n\t- Key length indicates the length of the generated session key. This will be 0 if no session key was requested.', + }, + { + category: 'cloud', + field: 'cloud.availability_zone', + values: ['us-central1-a'], + originalValue: 'us-central1-a', + }, + { + category: 'cloud', + field: 'cloud.instance.name', + values: ['windows-native'], + originalValue: 'windows-native', + }, + { + category: 'cloud', + field: 'cloud.instance.id', + values: ['5896613765949631815'], + originalValue: '5896613765949631815', + }, + { category: 'cloud', field: 'cloud.provider', values: ['gcp'], originalValue: 'gcp' }, + { + category: 'cloud', + field: 'cloud.machine.type', + values: ['e2-medium'], + originalValue: 'e2-medium', + }, + { + category: 'cloud', + field: 'cloud.project.id', + values: ['elastic-siem'], + originalValue: 'elastic-siem', + }, + { + category: 'base', + field: '@timestamp', + values: ['2020-11-25T15:42:39.417Z'], + originalValue: '2020-11-25T15:42:39.417Z', + }, + { + category: 'related', + field: 'related.user', + values: ['administrator'], + originalValue: 'administrator', + }, + { category: 'ecs', field: 'ecs.version', values: ['1.5.0'], originalValue: '1.5.0' }, + { + category: 'host', + field: 'host.hostname', + values: ['windows-native'], + originalValue: 'windows-native', + }, + { category: 'host', field: 'host.os.build', values: ['17763.1577'], originalValue: '17763.1577' }, + { + category: 'host', + field: 'host.os.kernel', + values: ['10.0.17763.1577 (WinBuild.160101.0800)'], + originalValue: '10.0.17763.1577 (WinBuild.160101.0800)', + }, + { + category: 'host', + field: 'host.os.name', + values: ['Windows Server 2019 Datacenter'], + originalValue: 'Windows Server 2019 Datacenter', + }, + { category: 'host', field: 'host.os.family', values: ['windows'], originalValue: 'windows' }, + { category: 'host', field: 'host.os.version', values: ['10.0'], originalValue: '10.0' }, + { category: 'host', field: 'host.os.platform', values: ['windows'], originalValue: 'windows' }, + { + category: 'host', + field: 'host.ip', + values: ['fe80::406c:d205:5b46:767f', '10.128.15.228'], + originalValue: ['fe80::406c:d205:5b46:767f', '10.128.15.228'], + }, + { + category: 'host', + field: 'host.name', + values: ['windows-native'], + originalValue: 'windows-native', + }, + { + category: 'host', + field: 'host.id', + values: ['08f50e68-847a-4fae-a8eb-c7dc886447bb'], + originalValue: '08f50e68-847a-4fae-a8eb-c7dc886447bb', + }, + { + category: 'host', + field: 'host.mac', + values: ['42:01:0a:80:0f:e4'], + originalValue: ['42:01:0a:80:0f:e4'], + }, + { category: 'host', field: 'host.architecture', values: ['x86_64'], originalValue: 'x86_64' }, + { + category: 'event', + field: 'event.ingested', + values: ['2020-11-25T15:36:40.924914552Z'], + originalValue: '2020-11-25T15:36:40.924914552Z', + }, + { category: 'event', field: 'event.code', values: [4625], originalValue: 4625 }, + { category: 'event', field: 'event.lag.total', values: [2077], originalValue: 2077 }, + { category: 'event', field: 'event.lag.read', values: [1075], originalValue: 1075 }, + { category: 'event', field: 'event.lag.ingest', values: [1002], originalValue: 1002 }, + { + category: 'event', + field: 'event.provider', + values: ['Microsoft-Windows-Security-Auditing'], + originalValue: 'Microsoft-Windows-Security-Auditing', + }, + { + category: 'event', + field: 'event.created', + values: ['2020-11-25T15:36:39.922Z'], + originalValue: '2020-11-25T15:36:39.922Z', + }, + { category: 'event', field: 'event.kind', values: ['signal'], originalValue: 'signal' }, + { category: 'event', field: 'event.module', values: ['security'], originalValue: 'security' }, + { + category: 'event', + field: 'event.action', + values: ['logon-failed'], + originalValue: 'logon-failed', + }, + { category: 'event', field: 'event.type', values: ['start'], originalValue: 'start' }, + { + category: 'event', + field: 'event.category', + values: ['authentication'], + originalValue: 'authentication', + }, + { category: 'event', field: 'event.outcome', values: ['failure'], originalValue: 'failure' }, + { + category: 'user', + field: 'user.name', + values: ['administrator'], + originalValue: 'administrator', + }, + { category: 'user', field: 'user.id', values: ['S-1-0-0'], originalValue: 'S-1-0-0' }, + { + category: 'signal', + field: 'signal.parents', + values: [ + '{"id":"688MAHYB7WTwW_Glsi_d","type":"event","index":"winlogbeat-7.10.0-2020.11.12-000001","depth":0}', + ], + originalValue: [ + { + id: '688MAHYB7WTwW_Glsi_d', + type: 'event', + index: 'winlogbeat-7.10.0-2020.11.12-000001', + depth: 0, + }, + ], + }, + { + category: 'signal', + field: 'signal.ancestors', + values: [ + '{"id":"688MAHYB7WTwW_Glsi_d","type":"event","index":"winlogbeat-7.10.0-2020.11.12-000001","depth":0}', + ], + originalValue: [ + { + id: '688MAHYB7WTwW_Glsi_d', + type: 'event', + index: 'winlogbeat-7.10.0-2020.11.12-000001', + depth: 0, + }, + ], + }, + { category: 'signal', field: 'signal.status', values: ['open'], originalValue: 'open' }, + { + category: 'signal', + field: 'signal.rule.id', + values: ['b69d086c-325a-4f46-b17b-fb6d227006ba'], + originalValue: 'b69d086c-325a-4f46-b17b-fb6d227006ba', + }, + { + category: 'signal', + field: 'signal.rule.rule_id', + values: ['e7cd9a53-ac62-44b5-bdec-9c94d85bb1a5'], + originalValue: 'e7cd9a53-ac62-44b5-bdec-9c94d85bb1a5', + }, + { category: 'signal', field: 'signal.rule.actions', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.author', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.false_positives', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.meta.from', values: ['1m'], originalValue: '1m' }, + { + category: 'signal', + field: 'signal.rule.meta.kibana_siem_app_url', + values: ['http://localhost:5601/app/security'], + originalValue: 'http://localhost:5601/app/security', + }, + { category: 'signal', field: 'signal.rule.max_signals', values: [100], originalValue: 100 }, + { category: 'signal', field: 'signal.rule.risk_score', values: [21], originalValue: 21 }, + { category: 'signal', field: 'signal.rule.risk_score_mapping', values: [], originalValue: [] }, + { + category: 'signal', + field: 'signal.rule.output_index', + values: ['.siem-signals-angelachuang-default'], + originalValue: '.siem-signals-angelachuang-default', + }, + { category: 'signal', field: 'signal.rule.description', values: ['xxx'], originalValue: 'xxx' }, + { + category: 'signal', + field: 'signal.rule.from', + values: ['now-360s'], + originalValue: 'now-360s', + }, + { + category: 'signal', + field: 'signal.rule.index', + values: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + originalValue: [ + 'apm-*-transaction*', + 'auditbeat-*', + 'endgame-*', + 'filebeat-*', + 'logs-*', + 'packetbeat-*', + 'winlogbeat-*', + ], + }, + { category: 'signal', field: 'signal.rule.interval', values: ['5m'], originalValue: '5m' }, + { category: 'signal', field: 'signal.rule.language', values: ['kuery'], originalValue: 'kuery' }, + { category: 'signal', field: 'signal.rule.license', values: [''], originalValue: '' }, + { category: 'signal', field: 'signal.rule.name', values: ['xxx'], originalValue: 'xxx' }, + { + category: 'signal', + field: 'signal.rule.query', + values: ['@timestamp : * '], + originalValue: '@timestamp : * ', + }, + { category: 'signal', field: 'signal.rule.references', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.severity', values: ['low'], originalValue: 'low' }, + { category: 'signal', field: 'signal.rule.severity_mapping', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.tags', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.type', values: ['query'], originalValue: 'query' }, + { category: 'signal', field: 'signal.rule.to', values: ['now'], originalValue: 'now' }, + { + category: 'signal', + field: 'signal.rule.filters', + values: [ + '{"meta":{"alias":null,"negate":false,"disabled":false,"type":"exists","key":"message","value":"exists"},"exists":{"field":"message"},"$state":{"store":"appState"}}', + ], + originalValue: [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'exists', + key: 'message', + value: 'exists', + }, + exists: { field: 'message' }, + $state: { store: 'appState' }, + }, + ], + }, + { + category: 'signal', + field: 'signal.rule.created_by', + values: ['angela'], + originalValue: 'angela', + }, + { + category: 'signal', + field: 'signal.rule.updated_by', + values: ['angela'], + originalValue: 'angela', + }, + { category: 'signal', field: 'signal.rule.threat', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.rule.version', values: [2], originalValue: 2 }, + { + category: 'signal', + field: 'signal.rule.created_at', + values: ['2020-11-24T10:30:33.660Z'], + originalValue: '2020-11-24T10:30:33.660Z', + }, + { + category: 'signal', + field: 'signal.rule.updated_at', + values: ['2020-11-25T15:37:40.939Z'], + originalValue: '2020-11-25T15:37:40.939Z', + }, + { category: 'signal', field: 'signal.rule.exceptions_list', values: [], originalValue: [] }, + { category: 'signal', field: 'signal.depth', values: [1], originalValue: 1 }, + { + category: 'signal', + field: 'signal.parent.id', + values: ['688MAHYB7WTwW_Glsi_d'], + originalValue: '688MAHYB7WTwW_Glsi_d', + }, + { category: 'signal', field: 'signal.parent.type', values: ['event'], originalValue: 'event' }, + { + category: 'signal', + field: 'signal.parent.index', + values: ['winlogbeat-7.10.0-2020.11.12-000001'], + originalValue: 'winlogbeat-7.10.0-2020.11.12-000001', + }, + { category: 'signal', field: 'signal.parent.depth', values: [0], originalValue: 0 }, + { + category: 'signal', + field: 'signal.original_time', + values: ['2020-11-25T15:36:38.847Z'], + originalValue: '2020-11-25T15:36:38.847Z', + }, + { + category: 'signal', + field: 'signal.original_event.ingested', + values: ['2020-11-25T15:36:40.924914552Z'], + originalValue: '2020-11-25T15:36:40.924914552Z', + }, + { category: 'signal', field: 'signal.original_event.code', values: [4625], originalValue: 4625 }, + { + category: 'signal', + field: 'signal.original_event.lag.total', + values: [2077], + originalValue: 2077, + }, + { + category: 'signal', + field: 'signal.original_event.lag.read', + values: [1075], + originalValue: 1075, + }, + { + category: 'signal', + field: 'signal.original_event.lag.ingest', + values: [1002], + originalValue: 1002, + }, + { + category: 'signal', + field: 'signal.original_event.provider', + values: ['Microsoft-Windows-Security-Auditing'], + originalValue: 'Microsoft-Windows-Security-Auditing', + }, + { + category: 'signal', + field: 'signal.original_event.created', + values: ['2020-11-25T15:36:39.922Z'], + originalValue: '2020-11-25T15:36:39.922Z', + }, + { + category: 'signal', + field: 'signal.original_event.kind', + values: ['event'], + originalValue: 'event', + }, + { + category: 'signal', + field: 'signal.original_event.module', + values: ['security'], + originalValue: 'security', + }, + { + category: 'signal', + field: 'signal.original_event.action', + values: ['logon-failed'], + originalValue: 'logon-failed', + }, + { + category: 'signal', + field: 'signal.original_event.type', + values: ['start'], + originalValue: 'start', + }, + { + category: 'signal', + field: 'signal.original_event.category', + values: ['authentication'], + originalValue: 'authentication', + }, + { + category: 'signal', + field: 'signal.original_event.outcome', + values: ['failure'], + originalValue: 'failure', + }, + { + category: '_index', + field: '_index', + values: ['.siem-signals-angelachuang-default-000004'], + originalValue: '.siem-signals-angelachuang-default-000004', + }, + { + category: '_id', + field: '_id', + values: ['5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31'], + originalValue: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', + }, + { category: '_score', field: '_score', values: [1], originalValue: 1 }, + { + category: 'fields', + field: 'fields.agent.name', + values: ['windows-native'], + originalValue: ['windows-native'], + }, + { + category: 'fields', + field: 'fields.cloud.machine.type', + values: ['e2-medium'], + originalValue: ['e2-medium'], + }, + { category: 'fields', field: 'fields.cloud.provider', values: ['gcp'], originalValue: ['gcp'] }, + { + category: 'fields', + field: 'fields.agent.id', + values: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + originalValue: ['abfe4a35-d5b4-42a0-a539-bd054c791769'], + }, + { + category: 'fields', + field: 'fields.cloud.instance.id', + values: ['5896613765949631815'], + originalValue: ['5896613765949631815'], + }, + { + category: 'fields', + field: 'fields.agent.type', + values: ['winlogbeat'], + originalValue: ['winlogbeat'], + }, + { + category: 'fields', + field: 'fields.@timestamp', + values: ['2020-11-25T15:42:39.417Z'], + originalValue: ['2020-11-25T15:42:39.417Z'], + }, + { + category: 'fields', + field: 'fields.agent.ephemeral_id', + values: ['b9850845-c000-4ddd-bd51-9978a07b7e7d'], + originalValue: ['b9850845-c000-4ddd-bd51-9978a07b7e7d'], + }, + { + category: 'fields', + field: 'fields.cloud.instance.name', + values: ['windows-native'], + originalValue: ['windows-native'], + }, + { + category: 'fields', + field: 'fields.cloud.availability_zone', + values: ['us-central1-a'], + originalValue: ['us-central1-a'], + }, + { + category: 'fields', + field: 'fields.agent.version', + values: ['7.10.0'], + originalValue: ['7.10.0'], + }, +]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap index 9ca9cd6cce389..211b61f6e9702 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -688,6 +688,12 @@ In other use cases the message field can be used to concatenate different values "902", ], }, + Object { + "field": "event.kind", + "values": Array [ + "event", + ], + }, ] } eventId="Y-6TfmcB0WOhS6qyMv3s" @@ -1381,6 +1387,12 @@ In other use cases the message field can be used to concatenate different values "902", ], }, + Object { + "field": "event.kind", + "values": Array [ + "event", + ], + }, ] } eventId="Y-6TfmcB0WOhS6qyMv3s" @@ -1535,6 +1547,12 @@ In other use cases the message field can be used to concatenate different values "902", ], }, + Object { + "field": "event.kind", + "values": Array [ + "event", + ], + }, ] } />, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index bafe3df1a9cc7..750c21c48fa3d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -19,6 +19,8 @@ import { import { EventDetails, View } from './event_details'; import { mockBrowserFields } from '../../containers/source/mock'; import { useMountAppended } from '../../utils/use_mount_appended'; +import { mockAlertDetailsData } from './__mocks__'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; jest.mock('../link_to'); describe('EventDetails', () => { @@ -34,12 +36,24 @@ describe('EventDetails', () => { timelineId: 'test', toggleColumn: jest.fn(), }; + + const alertsProps = { + ...defaultProps, + data: mockAlertDetailsData as TimelineEventsDetailsItem[], + }; + const wrapper = mount( ); + const alertsWrapper = mount( + + + + ); + describe('rendering', () => { test('should match snapshot', () => { const shallowWrap = shallow(); @@ -65,4 +79,27 @@ describe('EventDetails', () => { ).toEqual('Table'); }); }); + + describe('alerts tabs', () => { + ['Summary', 'Table', 'JSON View'].forEach((tab) => { + test(`it renders the ${tab} tab`, () => { + expect( + alertsWrapper + .find('[data-test-subj="eventDetails"]') + .find('[role="tablist"]') + .containsMatchingElement({tab}) + ).toBeTruthy(); + }); + }); + + test('the Summary tab is selected by default', () => { + expect( + alertsWrapper + .find('[data-test-subj="eventDetails"]') + .find('.euiTab-isSelected') + .first() + .text() + ).toEqual('Summary'); + }); + }); }); 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 new file mode 100644 index 0000000000000..9c609ebb2618c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mount } from 'enzyme'; + +import { SummaryViewComponent } from './summary_view'; +import { mockAlertDetailsData } from './__mocks__'; +import { BrowserFields } from '../../containers/source'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; + +jest.mock('../../../timelines/components/timeline/body/renderers/formatted_field', () => { + return { + FormattedFieldValue: jest.fn(() =>
), + }; +}); + +const props = { + data: mockAlertDetailsData, + browserFields: {}, + eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', + timelineId: 'detections-page', +} as { + browserFields: BrowserFields; + data: TimelineEventsDetailsItem[]; + eventId: string; + timelineId: string; +}; + +describe('SummaryViewComponent', () => { + test('render correct items', () => { + const wrapper = mount(); + expect(wrapper.find('[data-test-subj="summary-view"]').exists()).toEqual(true); + }); + + test('render Investigation guide', () => { + const wrapper = mount(); + expect(wrapper.find('[data-test-subj="summary-view-message"]').exists()).toEqual(true); + }); +}); 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 3521f21fa27ee..b4e8a055bd8bc 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 @@ -127,7 +127,7 @@ const getDescription = ({ ); }; -const SummaryViewComponent: React.FC<{ +export const SummaryViewComponent: React.FC<{ browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; eventId: string; @@ -199,11 +199,16 @@ const SummaryViewComponent: React.FC<{ return ( <> - + {message != null && ( <> - + {i18n.INVESTIGATION_GUIDE} {isExpanded ? ( @@ -214,7 +219,7 @@ const SummaryViewComponent: React.FC<{ {isOverflow && ( - + {readMoreButtonText} )} diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts index c5d881c540eec..d8b0ac4bd51d9 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts +++ b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts @@ -109,4 +109,8 @@ export const mockDetailItemData: TimelineEventsDetailsItem[] = [ originalValue: 902, values: ['902'], }, + { + field: 'event.kind', + values: ['event'], + }, ]; From 441aef9ba593d88e0323f70ea51fae9266695e37 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Nov 2020 18:32:57 +0000 Subject: [PATCH 07/60] unit test --- .../public/timelines/components/timeline/timeline.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx index 900699503a3bb..6b874f9d8f9a2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/timeline.test.tsx @@ -65,6 +65,10 @@ jest.mock('../../../common/lib/kibana', () => { useGetUserSavedObjectPermissions: jest.fn(), }; }); +jest.mock('./event_details', () => { + return { EventDetails: jest.fn(() =>
) }; +}); + describe('Timeline', () => { let props = {} as TimelineComponentProps; const sort: Sort = { From bb67c9640c4fb26f7a5e5de3d6bef26c77871ad4 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 26 Nov 2020 23:26:30 +0000 Subject: [PATCH 08/60] fix unit test --- .../event_details/__snapshots__/event_details.test.tsx.snap | 3 +++ .../event_details/__snapshots__/json_view.test.tsx.snap | 3 +++ .../public/common/components/event_details/json_view.test.tsx | 3 +++ .../security_solution/public/common/mock/mock_detail_item.ts | 1 + 4 files changed, 10 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap index 211b61f6e9702..578e9e1bb6b29 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/event_details.test.tsx.snap @@ -690,6 +690,7 @@ In other use cases the message field can be used to concatenate different values }, Object { "field": "event.kind", + "originalValue": "event", "values": Array [ "event", ], @@ -1389,6 +1390,7 @@ In other use cases the message field can be used to concatenate different values }, Object { "field": "event.kind", + "originalValue": "event", "values": Array [ "event", ], @@ -1549,6 +1551,7 @@ In other use cases the message field can be used to concatenate different values }, Object { "field": "event.kind", + "originalValue": "event", "values": Array [ "event", ], diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap index caa7853fd9ec0..e75cdabf314dd 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__snapshots__/json_view.test.tsx.snap @@ -44,6 +44,9 @@ exports[`JSON View rendering should match snapshot 1`] = ` \\"ip\\": \\"10.47.8.200\\", \\"packets\\": 4, \\"port\\": 902 + }, + \\"event\\": { + \\"kind\\": \\"event\\" } }" width="100%" diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx index 0cf158c8ea90b..da93670d647a8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/json_view.test.tsx @@ -54,6 +54,9 @@ describe('JSON View', () => { packets: 4, port: 902, }, + event: { + kind: 'event', + }, }; expect(buildJsonView(mockDetailItemData)).toEqual(expectedData); }); diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts index d8b0ac4bd51d9..f074495e65b64 100644 --- a/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts +++ b/x-pack/plugins/security_solution/public/common/mock/mock_detail_item.ts @@ -111,6 +111,7 @@ export const mockDetailItemData: TimelineEventsDetailsItem[] = [ }, { field: 'event.kind', + originalValue: 'event', values: ['event'], }, ]; From 5ad64e9b2e06d3f52c3c22529d77eb90c57bc711 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 30 Nov 2020 12:50:48 +0000 Subject: [PATCH 09/60] functional test --- .../cypress/integration/alerts_timeline.spec.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index 31d8e4666d91d..d564cd02459b5 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -4,14 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ALERT_ID } from '../screens/alerts'; import { PROVIDER_BADGE } from '../screens/timeline'; -import { - expandFirstAlert, - investigateFirstAlertInTimeline, - waitForAlertsPanelToBeLoaded, -} from '../tasks/alerts'; +import { investigateFirstAlertInTimeline, waitForAlertsPanelToBeLoaded } from '../tasks/alerts'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPage } from '../tasks/login'; @@ -29,13 +24,13 @@ describe('Alerts timeline', () => { it('Investigate alert in default timeline', () => { waitForAlertsPanelToBeLoaded(); - expandFirstAlert(); - cy.get(ALERT_ID) + investigateFirstAlertInTimeline(); + cy.get(PROVIDER_BADGE) .first() .invoke('text') .then((eventId) => { investigateFirstAlertInTimeline(); - cy.get(PROVIDER_BADGE).should('have.text', `_id: "${eventId}"`); + cy.get(PROVIDER_BADGE).should('have.text', eventId); }); }); }); From c323f6416b34dcc7f5d33bf91a34dbf737abb2f3 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 1 Dec 2020 16:05:44 +0000 Subject: [PATCH 10/60] isolate lineClamp component --- .../components/event_details/summary_view.tsx | 71 ++--------------- .../components/event_details/translations.ts | 8 -- .../common/components/line_clamp/index.tsx | 76 +++++++++++++++++++ .../components/line_clamp/translations.ts | 15 ++++ .../timeline/body/events/stateful_event.tsx | 2 +- .../timeline/expandable_event/index.tsx | 27 ++++--- 6 files changed, 112 insertions(+), 87 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/line_clamp/translations.ts 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 b4e8a055bd8bc..f066e94e867c0 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 @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react'; +import React, { useMemo } from 'react'; import { EuiBadge, @@ -11,11 +11,10 @@ import { EuiSpacer, EuiDescriptionListTitle, EuiDescriptionListDescription, - EuiButtonEmpty, } from '@elastic/eui'; -import styled from 'styled-components'; import { get, getOr } from 'lodash/fp'; +import styled from 'styled-components'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; import * as i18n from './translations'; @@ -35,6 +34,7 @@ import { Ip, SOURCE_IP_FIELD_NAME, } from '../../../network/components/ip'; +import { LineClamp } from '../line_clamp'; type Summary = Array<{ title: string; description: JSX.Element }>; @@ -43,7 +43,7 @@ const fields = [ { id: '@timestamp' }, { id: SIGNAL_RULE_NAME_FIELD_NAME, - linkField: 'signal.rule.description', + linkField: 'signal.rule.name', label: ALERTS_HEADERS_RULE, }, { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, @@ -53,27 +53,6 @@ const fields = [ { id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, ]; -const LINE_CLAMP = 3; -const LINE_CLAMP_HEIGHT = 4.5; - -const LineClamp = styled.div` - display: -webkit-box; - -webkit-line-clamp: ${LINE_CLAMP}; - -webkit-box-orient: vertical; - overflow: hidden; - max-height: ${`${LINE_CLAMP_HEIGHT}em`}; - height: ${`${LINE_CLAMP_HEIGHT}em`}; -`; - -const StyledDescription = styled(EuiDescriptionListDescription)` - word-break: break-all; -`; - -const ReadMore = styled(EuiButtonEmpty)` - span.euiButtonContent { - padding: 0; - } -`; const getDescription = ({ contextId, @@ -168,33 +147,6 @@ export const SummaryViewComponent: React.FC<{ const messageData = (data || []).find((item) => item.field === MESSAGE_FIELD_NAME); const message = get('values.0', messageData); - const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); - const [isOverflow, setIsOverflow] = useState(null); - const [isExpanded, setIsReadMoreClicked] = useState(null); - const descriptionRef = useRef(null); - const toggleReadMore = useCallback(() => { - setIsReadMoreClicked((prevState) => !prevState); - setReadMoreButtonText((prevState) => - prevState === i18n.READ_MORE ? i18n.READ_LESS : i18n.READ_MORE - ); - }, []); - - useEffect(() => { - if (message != null && descriptionRef?.current?.clientHeight != null) { - if ( - (descriptionRef?.current?.scrollHeight ?? 0) > (descriptionRef?.current?.clientHeight ?? 0) - ) { - setIsOverflow(true); - } - - if ( - ((message == null || descriptionRef?.current?.scrollHeight) ?? 0) <= - (descriptionRef?.current?.clientHeight ?? 0) - ) { - setIsOverflow(false); - } - } - }, [message, descriptionRef?.current?.clientHeight]); return ( <> @@ -210,19 +162,10 @@ export const SummaryViewComponent: React.FC<{ {i18n.INVESTIGATION_GUIDE} - - {isExpanded ? ( -

{message}

- ) : ( - {message} - )} -
+ + +
- {isOverflow && ( - - {readMoreButtonText} - - )} )} 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 6fb49a6016c65..76ae2cd4a88a8 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 @@ -17,14 +17,6 @@ export const INVESTIGATION_GUIDE = i18n.translate( } ); -export const READ_MORE = i18n.translate('xpack.securitySolution.alertDetails.summary.readMore', { - defaultMessage: 'Read More', -}); - -export const READ_LESS = i18n.translate('xpack.securitySolution.alertDetails.summary.readLess', { - defaultMessage: 'Read Less', -}); - export const TABLE = i18n.translate('xpack.securitySolution.eventDetails.table', { defaultMessage: 'Table', }); diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx new file mode 100644 index 0000000000000..b40c62dd17432 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useRef, useState, useEffect, useCallback } from 'react'; + +import { EuiButtonEmpty } from '@elastic/eui'; +import styled from 'styled-components'; +import * as i18n from './translations'; + +const LINE_CLAMP = 3; +const LINE_CLAMP_HEIGHT = 4.5; + +const StyledLineClamp = styled.div` + display: -webkit-box; + -webkit-line-clamp: ${LINE_CLAMP}; + -webkit-box-orient: vertical; + overflow: hidden; + max-height: ${`${LINE_CLAMP_HEIGHT}em`}; + height: ${`${LINE_CLAMP_HEIGHT}em`}; +`; + +const ReadMore = styled(EuiButtonEmpty)` + span.euiButtonContent { + padding: 0; + } +`; + +const LineClampComponent: React.FC<{ content?: string }> = ({ content }) => { + const [isOverflow, setIsOverflow] = useState(null); + const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); + const [isExpanded, setIsExpanded] = useState(null); + const descriptionRef = useRef(null); + const toggleReadMore = useCallback(() => { + setIsExpanded((prevState) => !prevState); + setReadMoreButtonText((prevState) => + prevState === i18n.READ_MORE ? i18n.READ_LESS : i18n.READ_MORE + ); + }, []); + + useEffect(() => { + if (content != null && descriptionRef?.current?.clientHeight != null) { + if ( + (descriptionRef?.current?.scrollHeight ?? 0) > (descriptionRef?.current?.clientHeight ?? 0) + ) { + setIsOverflow(true); + } + + if ( + ((content == null || descriptionRef?.current?.scrollHeight) ?? 0) <= + (descriptionRef?.current?.clientHeight ?? 0) + ) { + setIsOverflow(false); + } + } + }, [content, descriptionRef?.current?.clientHeight]); + + return ( + <> + {isExpanded ? ( +

{content}

+ ) : ( + {content} + )} + {isOverflow && ( + + {readMoreButtonText} + + )} + + ); +}; + +export const LineClamp = React.memo(LineClampComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/translations.ts b/x-pack/plugins/security_solution/public/common/components/line_clamp/translations.ts new file mode 100644 index 0000000000000..e332d1a2d2b5c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const READ_MORE = i18n.translate('xpack.securitySolution.alertDetails.summary.readMore', { + defaultMessage: 'Read More', +}); + +export const READ_LESS = i18n.translate('xpack.securitySolution.alertDetails.summary.readLess', { + defaultMessage: 'Read Less', +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 44cc061f93368..58a57ac86b4ed 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -126,7 +126,7 @@ const StatefulEventComponent: React.FC = ({ if (timelineId === TimelineId.active) { activeTimeline.toggleExpandedEvent({ eventId, indexName, loading: false }); } - }, [dispatch, timelineId, event]); + }, [dispatch, event._id, event._index, timelineId]); const associateNote = useCallback( (noteId: string) => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 973d7ec9b73d7..7053ce4d00fa2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -10,7 +10,6 @@ import { EuiFlyoutHeader, EuiLoadingContent, EuiTitle, - EuiSpacer, } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; @@ -109,24 +108,24 @@ export const ExpandableEvent = React.memo( return {i18n.EVENT_DETAILS_PLACEHOLDER}; } + if (loading) { + return ; + } + return ( <> - {loading && } - {!loading && } + - {loading && } - {!loading && ( - - - - )} + + + ); From b9144ab54dd4a327ddb9b89dffec555c3b5fa7a0 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 1 Dec 2020 16:38:26 +0000 Subject: [PATCH 11/60] review --- .../common/ecs/event/index.ts | 8 ++++++ .../event_details/event_details.tsx | 5 ++-- .../components/event_details/summary_view.tsx | 25 +++++++++++-------- .../common/components/line_clamp/index.tsx | 6 ++--- .../timeline/expandable_event/index.tsx | 3 ++- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/common/ecs/event/index.ts b/x-pack/plugins/security_solution/common/ecs/event/index.ts index cb18a8c5881e8..e91d0efea6371 100644 --- a/x-pack/plugins/security_solution/common/ecs/event/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/event/index.ts @@ -4,6 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +export enum EventKind { + alert = 'alert', + event = 'event', + metric = 'metric', + state = 'state', + pipeline_error = 'pipeline_error', + signal = 'pipeline_error', +} export interface EventEcs { action?: string[]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index bdbd4237ecf07..a970f410a5527 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -10,6 +10,7 @@ import styled from 'styled-components'; import { get } from 'lodash/fp'; import { BrowserFields } from '../../containers/source'; +import { EventKind } from '../../../../common/ecs/event'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { OnUpdateColumns } from '../../../timelines/components/timeline/events'; @@ -75,7 +76,7 @@ export const EventDetails = React.memo( ); const tabs: EuiTabbedContentTab[] = useMemo( () => [ - ...(eventKind !== 'event' ? alerts : []), + ...(eventKind !== EventKind.event ? alerts : []), { id: EventsViewType.tableView, name: i18n.TABLE, @@ -111,7 +112,7 @@ export const EventDetails = React.memo( ); useEffect(() => { - if (data != null && eventKind !== 'event') { + if (data != null && eventKind !== EventKind.event) { setView(EventsViewType.summaryView); } }, [data, eventKind]); 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 f066e94e867c0..6e3dfeca1f518 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 @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { EuiBadge, @@ -14,7 +14,6 @@ import { } from '@elastic/eui'; import { get, getOr } from 'lodash/fp'; -import styled from 'styled-components'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; import * as i18n from './translations'; @@ -26,7 +25,6 @@ import { } from '../../../detections/components/alerts_table/translations'; import { IP_FIELD_TYPE, - MESSAGE_FIELD_NAME, SIGNAL_RULE_NAME_FIELD_NAME, } from '../../../timelines/components/timeline/body/renderers/constants'; import { @@ -35,6 +33,7 @@ import { SOURCE_IP_FIELD_NAME, } from '../../../network/components/ip'; import { LineClamp } from '../line_clamp'; +import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; type Summary = Array<{ title: string; description: JSX.Element }>; @@ -112,9 +111,12 @@ export const SummaryViewComponent: React.FC<{ eventId: string; timelineId: string; }> = ({ data, eventId, timelineId, browserFields }) => { + const [note, setNote] = useState(null); + + const ruleIdField = data.find((d) => d.field === 'signal.rule.id'); + const ruleId = getOr(null, 'values.0', ruleIdField); + const { rule: maybeRule } = useRuleAsync(ruleId); const summaryList = useMemo(() => { - const ruleIdField = data.find((d) => d.field === 'signal.rule.rule_id'); - const ruleId = getOr(null, 'values.0', ruleIdField); return data != null ? fields.reduce((acc, item) => { const field = data.find((d) => d.field === item.id || d.field === item.linkField); @@ -143,10 +145,13 @@ export const SummaryViewComponent: React.FC<{ ]; }, []) : []; - }, [browserFields, data, eventId, timelineId]); + }, [browserFields, data, eventId, timelineId, ruleId]); - const messageData = (data || []).find((item) => item.field === MESSAGE_FIELD_NAME); - const message = get('values.0', messageData); + useEffect(() => { + if (maybeRule != null && maybeRule.note != null) { + setNote(maybeRule.note); + } + }, [maybeRule]); return ( <> @@ -157,13 +162,13 @@ export const SummaryViewComponent: React.FC<{ listItems={summaryList} compressed /> - {message != null && ( + {note != null && ( <> {i18n.INVESTIGATION_GUIDE} - + diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx index b40c62dd17432..f47ad6dc9d84d 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -28,7 +28,7 @@ const ReadMore = styled(EuiButtonEmpty)` } `; -const LineClampComponent: React.FC<{ content?: string }> = ({ content }) => { +const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) => { const [isOverflow, setIsOverflow] = useState(null); const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); const [isExpanded, setIsExpanded] = useState(null); @@ -57,7 +57,7 @@ const LineClampComponent: React.FC<{ content?: string }> = ({ content }) => { } }, [content, descriptionRef?.current?.clientHeight]); - return ( + return content != null ? ( <> {isExpanded ? (

{content}

@@ -70,7 +70,7 @@ const LineClampComponent: React.FC<{ content?: string }> = ({ content }) => { )} - ); + ) : null; }; export const LineClamp = React.memo(LineClampComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 7053ce4d00fa2..5157808d71199 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -27,6 +27,7 @@ import { getColumnHeaders } from '../body/column_headers/helpers'; import { timelineDefaults } from '../../../store/timeline/defaults'; import * as i18n from './translations'; import { EventDetails } from '../../../../common/components/event_details/event_details'; +import { EventKind } from '../../../../../common/ecs/event'; const ExpandableDetails = styled.div` .euiAccordion__button { @@ -115,7 +116,7 @@ export const ExpandableEvent = React.memo( return ( <> - + From d9a094cf79c3bf24cba76dd4712a481dfccafa82 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 1 Dec 2020 17:27:20 +0000 Subject: [PATCH 12/60] unit test --- .../event_details/summary_view.test.tsx | 34 +++++++++++++++++-- .../components/event_details/summary_view.tsx | 2 +- 2 files changed, 33 insertions(+), 3 deletions(-) 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 9c609ebb2618c..de8f412e085d1 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 @@ -5,11 +5,13 @@ */ import React from 'react'; import { mount } from 'enzyme'; +import { waitFor } from '@testing-library/react'; import { SummaryViewComponent } from './summary_view'; import { mockAlertDetailsData } from './__mocks__'; import { BrowserFields } from '../../containers/source'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; jest.mock('../../../timelines/components/timeline/body/renderers/formatted_field', () => { return { @@ -17,6 +19,12 @@ jest.mock('../../../timelines/components/timeline/body/renderers/formatted_field }; }); +jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { + return { + useRuleAsync: jest.fn(), + }; +}); + const props = { data: mockAlertDetailsData, browserFields: {}, @@ -30,13 +38,35 @@ const props = { }; describe('SummaryViewComponent', () => { + beforeEach(() => { + jest.clearAllMocks(); + (useRuleAsync as jest.Mock).mockReturnValue({ + rule: { + note: 'investigation guide', + }, + }); + }); test('render correct items', () => { const wrapper = mount(); expect(wrapper.find('[data-test-subj="summary-view"]').exists()).toEqual(true); }); - test('render Investigation guide', () => { + test('render investigation guide', async () => { + const wrapper = mount(); + await waitFor(() => { + expect(wrapper.find('[data-test-subj="summary-view-guide"]').exists()).toEqual(true); + }); + }); + + test("render no investigation guide if it doesn't exist", async () => { + (useRuleAsync as jest.Mock).mockReturnValue({ + rule: { + note: null, + }, + }); const wrapper = mount(); - expect(wrapper.find('[data-test-subj="summary-view-message"]').exists()).toEqual(true); + await waitFor(() => { + expect(wrapper.find('[data-test-subj="summary-view-guide"]').exists()).toEqual(false); + }); }); }); 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 6e3dfeca1f518..f2476bb805bc0 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 @@ -165,7 +165,7 @@ export const SummaryViewComponent: React.FC<{ {note != null && ( <> - + {i18n.INVESTIGATION_GUIDE} From 19ceb828d405a3bf662bd58ba16fd77118cc8b38 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 2 Dec 2020 13:44:30 +0000 Subject: [PATCH 13/60] fix rule name in events table --- .../common/ecs/event/index.ts | 8 --- .../components/event_details/columns.tsx | 3 + .../event_details/event_details.tsx | 28 +++++---- .../event_details/event_fields_browser.tsx | 23 ++++++- .../components/event_details/summary_view.tsx | 18 +++--- .../fields_browser/field_items.test.tsx | 62 +++++++++++++++++++ .../components/fields_browser/field_items.tsx | 2 +- .../components/fields_browser/helpers.tsx | 9 ++- .../renderers/formatted_field_helpers.tsx | 9 +++ .../timeline/expandable_event/index.tsx | 9 ++- 10 files changed, 130 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/security_solution/common/ecs/event/index.ts b/x-pack/plugins/security_solution/common/ecs/event/index.ts index e91d0efea6371..cb18a8c5881e8 100644 --- a/x-pack/plugins/security_solution/common/ecs/event/index.ts +++ b/x-pack/plugins/security_solution/common/ecs/event/index.ts @@ -4,14 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum EventKind { - alert = 'alert', - event = 'event', - metric = 'metric', - state = 'state', - pipeline_error = 'pipeline_error', - signal = 'pipeline_error', -} export interface EventEcs { action?: string[]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx index 35cb8f7b1c91f..2013910927431 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/columns.tsx @@ -58,6 +58,7 @@ export const getColumns = ({ onUpdateColumns, contextId, toggleColumn, + getLinkValue, }: { browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; @@ -65,6 +66,7 @@ export const getColumns = ({ onUpdateColumns: OnUpdateColumns; contextId: string; toggleColumn: (column: ColumnHeaderOptions) => void; + getLinkValue: (field: string) => string | null; }) => [ { field: 'field', @@ -194,6 +196,7 @@ export const getColumns = ({ fieldName={data.field} fieldType={data.type} value={value} + linkValue={getLinkValue(data.field)} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index a970f410a5527..fc1961b4c7cf6 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -7,10 +7,9 @@ import { EuiLink, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; - import { get } from 'lodash/fp'; + import { BrowserFields } from '../../containers/source'; -import { EventKind } from '../../../../common/ecs/event'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { OnUpdateColumns } from '../../../timelines/components/timeline/events'; @@ -50,11 +49,14 @@ Details.displayName = 'Details'; export const EventDetails = React.memo( ({ browserFields, columnHeaders, data, id, onUpdateColumns, timelineId, toggleColumn }) => { - const eventKindData = useMemo(() => (data || []).find((item) => item.field === 'event.kind'), [ - data, - ]); - const eventKind = get('values.0', eventKindData); - const [view, setView] = useState(EventsViewType.tableView); + const ruleIdField = useMemo( + () => (data ?? []).find((item) => item.field === 'signal.rule.id'), + [data] + ); + const isSignal = get('values.0', ruleIdField); + const [view, setView] = useState( + isSignal ? EventsViewType.summaryView : EventsViewType.tableView + ); const handleTabClick = useCallback((e) => setView(e.id), [setView]); const alerts = useMemo( @@ -76,7 +78,7 @@ export const EventDetails = React.memo( ); const tabs: EuiTabbedContentTab[] = useMemo( () => [ - ...(eventKind !== EventKind.event ? alerts : []), + ...(isSignal ? alerts : []), { id: EventsViewType.tableView, name: i18n.TABLE, @@ -107,21 +109,23 @@ export const EventDetails = React.memo( timelineId, toggleColumn, alerts, - eventKind, + isSignal, ] ); useEffect(() => { - if (data != null && eventKind !== EventKind.event) { + if (data != null && isSignal) { setView(EventsViewType.summaryView); } - }, [data, eventKind]); + }, [data, isSignal]); const selectedTab = useMemo(() => tabs.find((tab) => tab.id === view), [tabs, view]); return (
- + {tabs && ( + + )}
); } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index 79250ae9bec52..eb3f6cd22329e 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -6,8 +6,9 @@ import { sortBy } from 'lodash'; import { EuiInMemoryTable } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { getOr } from 'lodash/fp'; import { ColumnHeaderOptions } from '../../../timelines/store/timeline/model'; import { BrowserFields, getAllFieldsByName } from '../../containers/source'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; @@ -29,6 +30,15 @@ interface Props { /** Renders a table view or JSON view of the `ECS` `data` */ export const EventFieldsBrowser = React.memo( ({ browserFields, columnHeaders, data, eventId, onUpdateColumns, timelineId, toggleColumn }) => { + const getLinkValue = useCallback( + (field: string) => { + const ruleIdField = (data ?? []).find((d) => d.field === 'signal.rule.id'); + const ruleId = getOr(null, 'values.0', ruleIdField); + return field === 'signal.rule.name' ? ruleId : null; + }, + [data] + ); + const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); const items = useMemo( () => @@ -48,8 +58,17 @@ export const EventFieldsBrowser = React.memo( onUpdateColumns, contextId: timelineId, toggleColumn, + getLinkValue, }), - [browserFields, columnHeaders, eventId, onUpdateColumns, timelineId, toggleColumn] + [ + browserFields, + columnHeaders, + eventId, + onUpdateColumns, + timelineId, + toggleColumn, + getLinkValue, + ] ); return ( 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 f2476bb805bc0..c1de4ab9cc9b5 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 @@ -42,7 +42,7 @@ const fields = [ { id: '@timestamp' }, { id: SIGNAL_RULE_NAME_FIELD_NAME, - linkField: 'signal.rule.name', + linkField: 'signal.rule.id', label: ALERTS_HEADERS_RULE, }, { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, @@ -113,27 +113,29 @@ export const SummaryViewComponent: React.FC<{ }> = ({ data, eventId, timelineId, browserFields }) => { const [note, setNote] = useState(null); - const ruleIdField = data.find((d) => d.field === 'signal.rule.id'); + const ruleIdField = useMemo(() => data.find((d) => d.field === 'signal.rule.id'), [data]); const ruleId = getOr(null, 'values.0', ruleIdField); const { rule: maybeRule } = useRuleAsync(ruleId); const summaryList = useMemo(() => { return data != null ? fields.reduce((acc, item) => { - const field = data.find((d) => d.field === item.id || d.field === item.linkField); + const field = data.find((d) => d.field === item.id); if (!field) { return acc; } - - const fieldValue = getOr(null, 'values.0', field); + const linkValueField = + item.linkField != null && data.find((d) => d.field === item.linkField); + const linkValue = getOr(null, 'values.0', linkValueField); + const value = getOr(null, 'values.0', field); const category = field.category; const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; const description = getDescription({ contextId: timelineId, eventId, fieldName: item.id, - value: fieldValue, + value, fieldType: item.fieldType ?? fieldType, - linkValue: item.id === SIGNAL_RULE_NAME_FIELD_NAME ? ruleId : undefined, + linkValue: linkValue ?? undefined, }); return [ @@ -145,7 +147,7 @@ export const SummaryViewComponent: React.FC<{ ]; }, []) : []; - }, [browserFields, data, eventId, timelineId, ruleId]); + }, [browserFields, data, eventId, timelineId]); useEffect(() => { if (maybeRule != null && maybeRule.note != null) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx index f4f8adc9f0419..57d155bb848b8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx @@ -6,6 +6,7 @@ import { omit } from 'lodash/fp'; import React from 'react'; +import { waitFor } from '@testing-library/react'; import { mockBrowserFields } from '../../../common/containers/source/mock'; import { TestProviders } from '../../../common/mock'; @@ -204,6 +205,67 @@ describe('field_items', () => { }); }); + test('it returns the expected signal column settings', async () => { + const mockSelectedCategoryId = 'signal'; + const mockBrowserFieldsWithSignal = { + ...mockBrowserFields, + signal: { + fields: { + 'signal.rule.name': { + aggregatable: true, + category: 'signal', + description: 'rule name', + example: '2016-05-23T08:05:34.853Z', + format: '', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: 'signal.rule.name', + searchable: true, + type: 'string', + }, + }, + }, + }; + const toggleColumn = jest.fn(); + const wrapper = mount( + + + + ); + wrapper + .find(`[data-test-subj="field-signal.rule.name-checkbox"]`) + .last() + .simulate('change', { + target: { checked: true }, + }); + + await waitFor(() => { + expect(toggleColumn).toBeCalledWith({ + columnHeaderType: 'not-filtered', + id: 'signal.rule.name', + label: 'Rule', + linkField: 'signal.rule.id', + width: 180, + }); + }); + }); + test('it renders the expected icon for a field', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx index a75089b58b86b..3e18d92beb9c6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx @@ -128,7 +128,7 @@ export const getFieldItems = ({ columnHeaderType: defaultColumnHeaderType, id: field.name || '', width: DEFAULT_COLUMN_MIN_WIDTH, - ...getAlertColumnHeader(timelineId, field.name || ''), + ...getAlertColumnHeader(field.name || ''), }) } /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx index ba9ade096c4d2..f582a3749bffc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx @@ -7,7 +7,6 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { filter, get, pickBy } from 'lodash/fp'; import styled from 'styled-components'; -import { TimelineId } from '../../../../common/types/timeline'; import { BrowserField, BrowserFields } from '../../../common/containers/source'; import { alertsHeaders } from '../../../detections/components/alerts_table/default_config'; @@ -144,7 +143,7 @@ export const mergeBrowserFieldsWithDefaultCategory = ( }), }); -export const getAlertColumnHeader = (timelineId: string, fieldId: string) => - timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage - ? alertsHeaders.find((c) => c.id === fieldId) ?? {} - : {}; +export const getAlertColumnHeader = (fieldId: string) => + alertsHeaders.find( + (c) => c.id === fieldId && defaultHeaders.find((d) => d.id === c.id) == null + ) ?? {}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index 8e64b484ffd2d..f34f7386e4082 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -77,6 +77,15 @@ export const RenderRuleName: React.FC = ({ {content} + ) : value != null ? ( + + {value} + ) : ( getEmptyTagValue() ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 5157808d71199..fe71eb2d2a628 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -27,7 +27,6 @@ import { getColumnHeaders } from '../body/column_headers/helpers'; import { timelineDefaults } from '../../../store/timeline/defaults'; import * as i18n from './translations'; import { EventDetails } from '../../../../common/components/event_details/event_details'; -import { EventKind } from '../../../../../common/ecs/event'; const ExpandableDetails = styled.div` .euiAccordion__button { @@ -71,11 +70,11 @@ export const ExpandableEvent = React.memo( skip: !event.eventId, }); - const eventKindData = useMemo( - () => (detailsData || []).find((item) => item.field === 'event.kind'), + const ruleIdField = useMemo( + () => (detailsData ?? []).find((item) => item.field === 'signal.rule.id'), [detailsData] ); - const eventKind = get('values.0', eventKindData); + const isSignal = get('values.0', ruleIdField); const onUpdateColumns = useCallback( (columns) => dispatch(timelineActions.updateColumns({ id: timelineId, columns })), @@ -116,7 +115,7 @@ export const ExpandableEvent = React.memo( return ( <> - + From f5447a16c6036fcdf670b59fae7d093b98444ea8 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 8 Dec 2020 09:44:18 +0800 Subject: [PATCH 14/60] originalvalue --- .../event_details/event_details.tsx | 2 +- .../event_details/event_fields_browser.tsx | 22 +++++++++++-------- .../components/event_details/summary_view.tsx | 6 ++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 0a6b354011a4f..254048e655574 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -97,7 +97,7 @@ export const EventDetailsComponent: React.FC = ({ name: i18n.TABLE, content: ( <> - + ( ({ browserFields, data, eventId, timelineId }) => { - const getLinkValue = useCallback( - (field: string) => { - const ruleIdField = (data ?? []).find((d) => d.field === 'signal.rule.id'); - const ruleId = getOr(null, 'values.0', ruleIdField); - return field === 'signal.rule.name' ? ruleId : null; - }, - [data] - ); - const dispatch = useDispatch(); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); @@ -101,6 +92,19 @@ export const EventFieldsBrowser = React.memo( return getColumnHeaders(columns, browserFields); }); + const getLinkValue = useCallback( + (field: string) => { + const linkField = (columnHeaders.find((col) => col.id === field) ?? {}).linkField; + if (!linkField) { + return null; + } + const linkFieldData = (data ?? []).find((d) => d.field === linkField); + const linkFieldValue = getOr(null, 'originalValue', linkFieldData); + return linkFieldValue; + }, + [data, columnHeaders] + ); + const toggleColumn = useCallback( (column: ColumnHeaderOptions) => { if (columnHeaders.some((c) => c.id === column.id)) { 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 f3ceeed74dba2..8f8f537037dd2 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 @@ -114,7 +114,7 @@ export const SummaryViewComponent: React.FC<{ const [investigationGuide, setInvestigationGuide] = useState(null); const ruleIdField = useMemo(() => data.find((d) => d.field === 'signal.rule.id'), [data]); - const ruleId = getOr(null, 'values.0', ruleIdField); + const ruleId = getOr(null, 'originalValue', ruleIdField); const { rule: maybeRule } = useRuleAsync(ruleId); const summaryList = useMemo(() => { return data != null @@ -125,8 +125,8 @@ export const SummaryViewComponent: React.FC<{ } const linkValueField = item.linkField != null && data.find((d) => d.field === item.linkField); - const linkValue = getOr(null, 'values.0', linkValueField); - const value = getOr(null, 'values.0', field); + const linkValue = getOr(null, 'originalValue', linkValueField); + const value = getOr(null, 'originalValue', field); const category = field.category; const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; const description = getDescription({ From db91dd08eb73f78b22a71bddb973b3c10509b4da Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 8 Dec 2020 17:43:13 +0800 Subject: [PATCH 15/60] unit test --- .../event_details/event_details.test.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 750c21c48fa3d..092c02ae09b06 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -9,14 +9,9 @@ import React from 'react'; import '../../mock/match_media'; import '../../mock/react_beautiful_dnd'; -import { - defaultHeaders, - mockDetailItemData, - mockDetailItemDataId, - TestProviders, -} from '../../mock'; +import { mockDetailItemData, mockDetailItemDataId, TestProviders } from '../../mock'; -import { EventDetails, View } from './event_details'; +import { EventDetails, EventsViewType } from './event_details'; import { mockBrowserFields } from '../../containers/source/mock'; import { useMountAppended } from '../../utils/use_mount_appended'; import { mockAlertDetailsData } from './__mocks__'; @@ -27,14 +22,11 @@ describe('EventDetails', () => { const mount = useMountAppended(); const defaultProps = { browserFields: mockBrowserFields, - columnHeaders: defaultHeaders, data: mockDetailItemData, id: mockDetailItemDataId, - view: 'table-view' as View, - onUpdateColumns: jest.fn(), onViewSelected: jest.fn(), timelineId: 'test', - toggleColumn: jest.fn(), + view: EventsViewType.summaryView, }; const alertsProps = { From b4bfd3d8761c186eb1ad1dcf334f44aecd3f60c4 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 8 Dec 2020 20:21:39 +0800 Subject: [PATCH 16/60] add close event details button --- .../events_viewer/event_details_flyout.tsx | 2 +- .../timeline/body/actions/index.tsx | 23 +++++--- .../components/timeline/body/translations.ts | 7 +++ .../components/timeline/event_details.tsx | 10 +++- .../timeline/expandable_event/index.tsx | 58 +++++++++++++++---- .../expandable_event/translations.tsx | 7 +++ .../timeline/query_tab_content/index.tsx | 7 ++- .../test/security_solution_cypress/runner.ts | 2 +- 8 files changed, 91 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index 66f5d70b1dcca..210814bd496b4 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -79,7 +79,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ return ( - + = ({ )} - + + + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts index c57002023b79d..11549c21604c0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts @@ -73,6 +73,13 @@ export const EXPAND = i18n.translate( } ); +export const EXPAND_EVENT = i18n.translate( + 'xpack.securitySolution.timeline.body.actions.expandEventTooltip', + { + defaultMessage: 'Expand event', + } +); + export const COLLAPSE = i18n.translate( 'xpack.securitySolution.timeline.body.actions.collapseAriaLabel', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index c99ba815c9fad..f58b7b45a922a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -19,6 +19,7 @@ import { BrowserFields, DocValueFields } from '../../../common/containers/source import { ExpandableEvent, ExpandableEventTitle, + OnEventDetailsClose, } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; @@ -28,12 +29,14 @@ interface EventDetailsProps { browserFields: BrowserFields; docValueFields: DocValueFields[]; timelineId: string; + onEventDetailsClose?: OnEventDetailsClose; } const EventDetailsComponent: React.FC = ({ browserFields, docValueFields, timelineId, + onEventDetailsClose, }) => { const expandedEvent = useDeepEqualSelector( (state) => state.timeline.timelineById[timelineId]?.expandedEvent @@ -61,7 +64,12 @@ const EventDetailsComponent: React.FC = ({ return ( <> - + void; interface Props { browserFields: BrowserFields; detailsData: TimelineEventsDetailsItem[] | null; @@ -36,11 +41,38 @@ interface Props { } export const ExpandableEventTitle = React.memo( - ({ isAlert, loading }: { isAlert: boolean; loading: boolean }) => ( - - {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>} -
- ) + ({ + isAlert, + loading, + timelineId, + onEventDetailsClose, + }: { + isAlert: boolean; + loading: boolean; + timelineId: string; + onEventDetailsClose?: OnEventDetailsClose; + }) => { + return ( +
+ + + + {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>} +
+
+ {onEventDetailsClose && ( + + + + )} +
+
+ ); + } ); ExpandableEventTitle.displayName = 'ExpandableEventTitle'; @@ -72,12 +104,14 @@ export const ExpandableEvent = React.memo( return ( <> - - {i18n.MESSAGE} - - - - + {message && ( + + {i18n.MESSAGE} + + + + + )} = ({ }) => { const [showEventDetailsColumn, setShowEventDetailsColumn] = useState(false); + const onEventDetailsClose = useCallback(() => { + setShowEventDetailsColumn(false); + }, [setShowEventDetailsColumn]); + useEffect(() => { // it should changed only once to true and then stay visible till the component umount setShowEventDetailsColumn((current) => { @@ -331,6 +335,7 @@ export const QueryTabContentComponent: React.FC = ({ browserFields={browserFields} docValueFields={docValueFields} timelineId={timelineId} + onEventDetailsClose={onEventDetailsClose} /> diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index a1a1a3916ef7f..8d20e9b1c08e5 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -22,7 +22,7 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:run'], + args: ['cypress:open'], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', From 2582064afe90d079f2c2f43a952cb39d819c7757 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 8 Dec 2020 20:49:07 +0800 Subject: [PATCH 17/60] rollback cypress configs --- x-pack/test/security_solution_cypress/runner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 8d20e9b1c08e5..a1a1a3916ef7f 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -22,7 +22,7 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:open'], + args: ['cypress:run'], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', From c94cd98305badb8c5a161dcca6a5cb5cc0aa7a39 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 8 Dec 2020 21:09:45 +0800 Subject: [PATCH 18/60] cypress --- .../cypress/integration/alerts_timeline.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index 124745e0a2d3a..2bfe72033135b 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -30,7 +30,7 @@ describe('Alerts timeline', () => { .invoke('text') .then((eventId) => { investigateFirstAlertInTimeline(); - cy.get(PROVIDER_BADGE).filter(':visible').should('have.text', `_id: "${eventId}"`); + cy.get(PROVIDER_BADGE).filter(':visible').should('have.text', eventId); }); }); }); From 952f9a26c136311a79aaa056dee72e75987bdbdd Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 8 Dec 2020 23:33:22 +0800 Subject: [PATCH 19/60] close events details --- .../components/event_details/summary_view.tsx | 2 +- .../components/timeline/event_details.tsx | 41 +++++++++++++++++-- .../timelines/store/timeline/actions.ts | 2 +- .../timelines/store/timeline/reducer.ts | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) 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 8f8f537037dd2..17fc246033900 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 @@ -157,7 +157,7 @@ export const SummaryViewComponent: React.FC<{ return ( <> - + = ({ timelineId, onEventDetailsClose, }) => { - const expandedEvent = useDeepEqualSelector( - (state) => state.timeline.timelineById[timelineId]?.expandedEvent + const dispatch = useDispatch(); + + const { expandedEvent, isSaving } = useDeepEqualSelector( + (state) => state.timeline.timelineById[timelineId] ); const [loading, detailsData] = useTimelineEventsDetails({ @@ -62,13 +68,40 @@ const EventDetailsComponent: React.FC = ({ return null; }, [detailsData]); + const handleOnEventClosed = useCallback(() => { + dispatch( + timelineActions.toggleExpandedEvent({ + timelineId, + event: null, + }) + ); + + if (timelineId === TimelineId.active) { + activeTimeline.toggleExpandedEvent({ + eventId: expandedEvent.eventId, + indexName: '', + loading: false, + }); + } + + if (onEventDetailsClose) { + onEventDetailsClose(); + } + }, [dispatch, timelineId, expandedEvent.eventId, onEventDetailsClose]); + + useEffect(() => { + if (isSaving) { + handleOnEventClosed(); + } + }, [isSaving, onEventDetailsClose, handleOnEventClosed]); + return ( <> ('TOGGLE_EXPANDED_EVENT'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index daf57505b6baf..05796e68d71db 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -183,7 +183,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state.timelineById, [timelineId]: { ...state.timelineById[timelineId], - expandedEvent: event, + expandedEvent: event ?? {}, }, }, })) From f5b4772ede879a8069376fa4e5bd4cbd3498460e Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 00:02:59 +0800 Subject: [PATCH 20/60] remove Ip --- .../components/event_details/summary_view.tsx | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) 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 17fc246033900..810d71d3130bc 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 @@ -27,11 +27,7 @@ import { IP_FIELD_TYPE, SIGNAL_RULE_NAME_FIELD_NAME, } from '../../../timelines/components/timeline/body/renderers/constants'; -import { - DESTINATION_IP_FIELD_NAME, - Ip, - SOURCE_IP_FIELD_NAME, -} from '../../../network/components/ip'; +import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../network/components/ip'; import { LineClamp } from '../line_clamp'; import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; @@ -68,17 +64,6 @@ const getDescription = ({ fieldType?: string; linkValue?: string; }) => { - if (fieldType === IP_FIELD_TYPE) { - return ( - - ); - } - if (fieldName === 'signal.status') { return ( From bfc39a15b1bb7df5543bc2ffff9dcbf925684eb1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 00:38:47 +0800 Subject: [PATCH 21/60] review --- .../event_details/event_details.test.tsx | 2 ++ .../event_details/event_details.tsx | 20 ++++--------------- .../components/event_details/summary_view.tsx | 2 +- .../events_viewer/event_details_flyout.tsx | 9 +++++---- .../components/timeline/event_details.tsx | 9 +++++---- .../timeline/expandable_event/index.tsx | 4 +++- 6 files changed, 20 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 092c02ae09b06..20fa6e54e044d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -24,6 +24,7 @@ describe('EventDetails', () => { browserFields: mockBrowserFields, data: mockDetailItemData, id: mockDetailItemDataId, + isAlert: false, onViewSelected: jest.fn(), timelineId: 'test', view: EventsViewType.summaryView, @@ -32,6 +33,7 @@ describe('EventDetails', () => { const alertsProps = { ...defaultProps, data: mockAlertDetailsData as TimelineEventsDetailsItem[], + isAlert: true, }; const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 254048e655574..09cba078e55b2 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -7,7 +7,6 @@ import { EuiTabbedContent, EuiTabbedContentTab, EuiSpacer } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; -import { find } from 'lodash/fp'; import { BrowserFields } from '../../containers/source'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; @@ -27,6 +26,7 @@ interface Props { browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; id: string; + isAlert: boolean; view: EventsViewType; onViewSelected: (selected: EventsViewType) => void; timelineId: string; @@ -53,20 +53,8 @@ export const EventDetailsComponent: React.FC = ({ view, onViewSelected, timelineId, + isAlert, }) => { - const ruleId = useMemo(() => { - if (data) { - const signalField = find({ category: 'signal', field: 'signal.rule.id' }, data) as - | TimelineEventsDetailsItem - | undefined; - - if (signalField?.originalValue) { - return signalField?.originalValue; - } - } - return null; - }, [data]); - const handleTabClick = useCallback((e) => onViewSelected(e.id), [onViewSelected]); const alerts = useMemo( @@ -91,7 +79,7 @@ export const EventDetailsComponent: React.FC = ({ ); const tabs: EuiTabbedContentTab[] = useMemo( () => [ - ...(ruleId != null ? alerts : []), + ...(isAlert ? alerts : []), { id: EventsViewType.tableView, name: i18n.TABLE, @@ -118,7 +106,7 @@ export const EventDetailsComponent: React.FC = ({ ), }, ], - [alerts, browserFields, data, id, ruleId, timelineId] + [alerts, browserFields, data, id, isAlert, timelineId] ); const selectedTab = useMemo(() => tabs.find((t) => t.id === view) ?? tabs[0], [tabs, 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 810d71d3130bc..6f926de40f1dd 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 @@ -149,7 +149,7 @@ export const SummaryViewComponent: React.FC<{ listItems={summaryList} compressed /> - {investigationGuide != null && ( + {investigationGuide && ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index 210814bd496b4..6cf2004229c86 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -50,17 +50,17 @@ const EventDetailsFlyoutComponent: React.FC = ({ skip: !expandedEvent.eventId, }); - const ruleId = useMemo(() => { + const isAlert = useMemo(() => { if (detailsData) { const signalField = find({ category: 'signal', field: 'signal.rule.id' }, detailsData) as | TimelineEventsDetailsItem | undefined; if (signalField?.originalValue) { - return signalField?.originalValue; + return true; } } - return null; + return false; }, [detailsData]); const handleClearSelection = useCallback(() => { @@ -79,13 +79,14 @@ const EventDetailsFlyoutComponent: React.FC = ({ return ( - + diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index 4e0d0467ce32a..e5a38a5d54d29 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -55,17 +55,17 @@ const EventDetailsComponent: React.FC = ({ skip: !expandedEvent.eventId, }); - const ruleId = useMemo(() => { + const isAlert = useMemo(() => { if (detailsData) { const signalField = find({ category: 'signal', field: 'signal.rule.id' }, detailsData) as | TimelineEventsDetailsItem | undefined; if (signalField?.originalValue) { - return signalField?.originalValue; + return true; } } - return null; + return false; }, [detailsData]); const handleOnEventClosed = useCallback(() => { @@ -98,7 +98,7 @@ const EventDetailsComponent: React.FC = ({ return ( <> = ({ browserFields={browserFields} detailsData={detailsData} event={expandedEvent} + isAlert={isAlert} loading={loading} timelineId={timelineId} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index c3c06727157ed..519d1e13991bf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -36,6 +36,7 @@ interface Props { browserFields: BrowserFields; detailsData: TimelineEventsDetailsItem[] | null; event: TimelineExpandedEvent; + isAlert: boolean; loading: boolean; timelineId: string; } @@ -78,7 +79,7 @@ export const ExpandableEventTitle = React.memo( ExpandableEventTitle.displayName = 'ExpandableEventTitle'; export const ExpandableEvent = React.memo( - ({ browserFields, event, timelineId, loading, detailsData }) => { + ({ browserFields, event, timelineId, isAlert, loading, detailsData }) => { const [view, setView] = useState(EventsViewType.summaryView); const message = useMemo(() => { @@ -117,6 +118,7 @@ export const ExpandableEvent = React.memo( browserFields={browserFields} data={detailsData!} id={event.eventId!} + isAlert={isAlert} onViewSelected={setView} timelineId={timelineId} view={view} From 9bbcfba518dcf543c48ea271b18b146ac3cdadd9 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 02:46:37 +0800 Subject: [PATCH 22/60] review --- .../components/timeline/query_tab_content/index.test.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 6eea9f775602a..4019f46b8c07b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -61,9 +61,6 @@ jest.mock('../../../../common/lib/kibana', () => { useGetUserSavedObjectPermissions: jest.fn(), }; }); -jest.mock('../event_details', () => { - return { EventDetails: jest.fn(() =>
) }; -}); describe('Timeline', () => { let props = {} as QueryTabContentComponentProps; From 66a366204dbe98934e4ffc82cb41337be2f554b2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 03:32:22 +0800 Subject: [PATCH 23/60] review --- .../event_details/event_details.tsx | 2 +- .../event_details/event_fields_browser.tsx | 5 +- .../timeline/expandable_event/index.tsx | 50 +++++++++---------- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 09cba078e55b2..291893fe682b4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -46,7 +46,7 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)` } `; -export const EventDetailsComponent: React.FC = ({ +const EventDetailsComponent: React.FC = ({ browserFields, data, id, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index 8e6eb5e859b0c..c5a03b5646e87 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sortBy } from 'lodash'; import { EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { getOr } from 'lodash/fp'; +import { getOr, sortBy } from 'lodash/fp'; import { useDispatch } from 'react-redux'; import { rgba } from 'polished'; import styled from 'styled-components'; @@ -78,7 +77,7 @@ export const EventFieldsBrowser = React.memo( const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); const items = useMemo( () => - sortBy(data, ['field']).map((item) => ({ + sortBy(['field'], data).map((item) => ({ ...item, ...fieldsByName[item.field], valuesConcatenated: item.values != null ? item.values.join() : '', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 519d1e13991bf..7a321c33dbf83 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -20,6 +20,7 @@ import { } from '@elastic/eui'; import { find } from 'lodash/fp'; +import styled from 'styled-components'; import { TimelineExpandedEvent } from '../../../../../common/types/timeline'; import { BrowserFields } from '../../../../common/containers/source'; import { @@ -41,37 +42,32 @@ interface Props { timelineId: string; } +interface ExpandableEventTitleProps { + isAlert: boolean; + loading: boolean; + timelineId: string; + onEventDetailsClose?: OnEventDetailsClose; +} + +const StyledEuiFlexGroup = styled(EuiFlexGroup)` + flex: 0; +`; + export const ExpandableEventTitle = React.memo( - ({ - isAlert, - loading, - timelineId, - onEventDetailsClose, - }: { - isAlert: boolean; - loading: boolean; - timelineId: string; - onEventDetailsClose?: OnEventDetailsClose; - }) => { + ({ isAlert, loading, timelineId, onEventDetailsClose }: ExpandableEventTitleProps) => { return ( -
- + + + + {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>} +
+
+ {onEventDetailsClose && ( - - {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>} -
+
- {onEventDetailsClose && ( - - - - )} -
-
+ )} + ); } ); From cd6180efbd33ec2b035411c6ae619a92d8f1d5f2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 08:13:29 +0800 Subject: [PATCH 24/60] review --- .../components/event_details/summary_view.tsx | 108 ++++++++++-------- .../events_viewer/event_details_flyout.tsx | 13 +-- .../common/components/line_clamp/index.tsx | 7 +- .../timeline/body/renderers/constants.tsx | 1 + .../body/renderers/formatted_field.tsx | 21 +++- .../renderers/formatted_field_helpers.tsx | 11 +- .../components/timeline/event_details.tsx | 27 ++--- .../timeline/expandable_event/index.tsx | 27 ++--- .../timelines/store/timeline/actions.ts | 2 +- .../timelines/store/timeline/reducer.ts | 2 +- 10 files changed, 114 insertions(+), 105 deletions(-) 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 6f926de40f1dd..8c80982a6663a 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 @@ -6,7 +6,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import { - EuiBadge, EuiDescriptionList, EuiSpacer, EuiDescriptionListTitle, @@ -64,20 +63,6 @@ const getDescription = ({ fieldType?: string; linkValue?: string; }) => { - if (fieldName === 'signal.status') { - return ( - - - - ); - } - return ( { + return data != null + ? fields.reduce((acc, item) => { + const field = data.find((d) => d.field === item.id); + if (!field) { + return acc; + } + const linkValueField = + item.linkField != null && data.find((d) => d.field === item.linkField); + const linkValue = getOr(null, 'originalValue', linkValueField); + const value = getOr(null, 'originalValue', field); + const category = field.category; + const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; + const description = getDescription({ + contextId: timelineId, + eventId, + fieldName: item.id, + value, + fieldType: item.fieldType ?? fieldType, + linkValue: linkValue ?? undefined, + }); + + return [ + ...acc, + { + title: item.label ?? item.id, + description, + }, + ]; + }, []) + : []; +}; + export const SummaryViewComponent: React.FC<{ browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; @@ -98,41 +126,22 @@ export const SummaryViewComponent: React.FC<{ }> = ({ data, eventId, timelineId, browserFields }) => { const [investigationGuide, setInvestigationGuide] = useState(null); - const ruleIdField = useMemo(() => data.find((d) => d.field === 'signal.rule.id'), [data]); - const ruleId = getOr(null, 'originalValue', ruleIdField); + const ruleId = useMemo( + () => + getOr( + null, + 'originalValue', + data.find((d) => d.field === 'signal.rule.id') + ), + [data] + ); const { rule: maybeRule } = useRuleAsync(ruleId); - const summaryList = useMemo(() => { - return data != null - ? fields.reduce((acc, item) => { - const field = data.find((d) => d.field === item.id); - if (!field) { - return acc; - } - const linkValueField = - item.linkField != null && data.find((d) => d.field === item.linkField); - const linkValue = getOr(null, 'originalValue', linkValueField); - const value = getOr(null, 'originalValue', field); - const category = field.category; - const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; - const description = getDescription({ - contextId: timelineId, - eventId, - fieldName: item.id, - value, - fieldType: item.fieldType ?? fieldType, - linkValue: linkValue ?? undefined, - }); - - return [ - ...acc, - { - title: item.label ?? item.id, - description, - }, - ]; - }, []) - : []; - }, [browserFields, data, eventId, timelineId]); + const summaryList = useMemo(() => getSummary({ browserFields, data, eventId, timelineId }), [ + browserFields, + data, + eventId, + timelineId, + ]); useEffect(() => { if (maybeRule != null && maybeRule.note != null) { @@ -142,7 +151,6 @@ export const SummaryViewComponent: React.FC<{ return ( <> - theme.eui.euiZLevel7}; @@ -52,13 +51,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ const isAlert = useMemo(() => { if (detailsData) { - const signalField = find({ category: 'signal', field: 'signal.rule.id' }, detailsData) as - | TimelineEventsDetailsItem - | undefined; - - if (signalField?.originalValue) { - return true; - } + return findIndex({ category: 'signal', field: 'signal.rule.id' }, detailsData) >= 0; } return false; }, [detailsData]); @@ -79,7 +72,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ return ( - + = ({ content }) => { const [isOverflow, setIsOverflow] = useState(null); - const [readMoreButtonText, setReadMoreButtonText] = useState(i18n.READ_MORE); const [isExpanded, setIsExpanded] = useState(null); const descriptionRef = useRef(null); const toggleReadMore = useCallback(() => { setIsExpanded((prevState) => !prevState); - setReadMoreButtonText((prevState) => - prevState === i18n.READ_MORE ? i18n.READ_LESS : i18n.READ_MORE - ); }, []); useEffect(() => { @@ -55,6 +51,7 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) setIsOverflow(false); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [content, descriptionRef?.current?.clientHeight]); return content != null ? ( @@ -66,7 +63,7 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) )} {isOverflow && ( - {readMoreButtonText} + {isExpanded ? i18n.READ_LESS : i18n.READ_MORE} )} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx index 445f2d8e62c82..874308b163732 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/constants.tsx @@ -13,3 +13,4 @@ export const RULE_REFERENCE_FIELD_NAME = 'rule.reference'; export const REFERENCE_URL_FIELD_NAME = 'reference.url'; export const EVENT_URL_FIELD_NAME = 'event.url'; export const SIGNAL_RULE_NAME_FIELD_NAME = 'signal.rule.name'; +export const SIGNAL_STATUS_FIELD_NAME = 'signal.status'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 04709458a7428..9b2d59b8c73cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { isNumber, isString, isEmpty } from 'lodash/fp'; -import React from 'react'; +import React, { useMemo } from 'react'; import { DefaultDraggable } from '../../../../../common/components/draggables'; import { Bytes, BYTES_FORMAT } from './bytes'; @@ -31,8 +31,14 @@ import { SIGNAL_RULE_NAME_FIELD_NAME, REFERENCE_URL_FIELD_NAME, EVENT_URL_FIELD_NAME, + SIGNAL_STATUS_FIELD_NAME, } from './constants'; -import { RenderRuleName, renderEventModule, renderUrl } from './formatted_field_helpers'; +import { + RenderRuleName, + renderEventModule, + renderUrl, + getSingalStatusBadge, +} from './formatted_field_helpers'; // simple black-list to prevent dragging and dropping fields such as message name const columnNamesNotDraggable = [MESSAGE_FIELD_NAME]; @@ -47,6 +53,8 @@ const FormattedFieldValueComponent: React.FC<{ value: string | number | undefined | null; linkValue?: string | null | undefined; }> = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value, linkValue }) => { + const signalStatusBadgeColor = useMemo(() => getSingalStatusBadge(value), [value]); + if (fieldType === IP_FIELD_TYPE) { return ( {contentValue} : contentValue; - - return ( + const defaultDraggable = ( ); + if (fieldName === SIGNAL_STATUS_FIELD_NAME) { + return {defaultDraggable}; + } + return defaultDraggable; } }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index f34f7386e4082..16052b127b1c5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -5,7 +5,7 @@ */ import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; -import { isString, isEmpty } from 'lodash/fp'; +import { isString, isEmpty, getOr } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; @@ -196,3 +196,12 @@ export const renderUrl = ({ getEmptyTagValue() ); }; + +export const getSingalStatusBadge = (status: string | number | null | undefined) => { + const mapping = { + open: 'primary', + 'in progress': 'warnging', + }; + + return status ? getOr('default', status, mapping) : 'default'; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index e5a38a5d54d29..a45fbafaeb1cb 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -13,7 +13,7 @@ import { EuiSpacer } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; -import { find } from 'lodash/fp'; +import { findIndex, pick } from 'lodash/fp'; import { useDispatch } from 'react-redux'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; @@ -23,11 +23,11 @@ import { OnEventDetailsClose, } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import { useTimelineEventsDetails } from '../../containers/details'; import { TimelineId } from '../../../../common/types/timeline'; import { activeTimeline } from '../../containers/active_timeline_context'; -import { timelineActions } from '../../store/timeline'; +import { timelineActions, timelineSelectors } from '../../store/timeline'; +import { timelineDefaults } from '../../store/timeline/defaults'; interface EventDetailsProps { browserFields: BrowserFields; @@ -43,9 +43,9 @@ const EventDetailsComponent: React.FC = ({ onEventDetailsClose, }) => { const dispatch = useDispatch(); - - const { expandedEvent, isSaving } = useDeepEqualSelector( - (state) => state.timeline.timelineById[timelineId] + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const { expandedEvent, isSaving } = useDeepEqualSelector((state) => + pick(['expandedEvent', 'isSaving'], getTimeline(state, timelineId) ?? timelineDefaults) ); const [loading, detailsData] = useTimelineEventsDetails({ @@ -57,13 +57,7 @@ const EventDetailsComponent: React.FC = ({ const isAlert = useMemo(() => { if (detailsData) { - const signalField = find({ category: 'signal', field: 'signal.rule.id' }, detailsData) as - | TimelineEventsDetailsItem - | undefined; - - if (signalField?.originalValue) { - return true; - } + return findIndex({ category: 'signal', field: 'signal.rule.id' }, detailsData) >= 0; } return false; }, [detailsData]); @@ -72,14 +66,14 @@ const EventDetailsComponent: React.FC = ({ dispatch( timelineActions.toggleExpandedEvent({ timelineId, - event: null, + event: {}, }) ); if (timelineId === TimelineId.active) { activeTimeline.toggleExpandedEvent({ eventId: expandedEvent.eventId, - indexName: '', + indexName: expandedEvent.indexName, loading: false, }); } @@ -87,7 +81,7 @@ const EventDetailsComponent: React.FC = ({ if (onEventDetailsClose) { onEventDetailsClose(); } - }, [dispatch, timelineId, expandedEvent.eventId, onEventDetailsClose]); + }, [dispatch, timelineId, expandedEvent.indexName, expandedEvent.eventId, onEventDetailsClose]); useEffect(() => { if (isSaving) { @@ -100,7 +94,6 @@ const EventDetailsComponent: React.FC = ({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 7a321c33dbf83..b519f9105604a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -45,7 +45,6 @@ interface Props { interface ExpandableEventTitleProps { isAlert: boolean; loading: boolean; - timelineId: string; onEventDetailsClose?: OnEventDetailsClose; } @@ -54,22 +53,20 @@ const StyledEuiFlexGroup = styled(EuiFlexGroup)` `; export const ExpandableEventTitle = React.memo( - ({ isAlert, loading, timelineId, onEventDetailsClose }: ExpandableEventTitleProps) => { - return ( - + ({ isAlert, loading, onEventDetailsClose }: ExpandableEventTitleProps) => ( + + + + {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>} +
+
+ {onEventDetailsClose && ( - - {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>} -
+
- {onEventDetailsClose && ( - - - - )} -
- ); - } + )} +
+ ) ); ExpandableEventTitle.displayName = 'ExpandableEventTitle'; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 896a3b97fb9ca..17e8b9b3b9a34 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -37,7 +37,7 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI interface ToggleExpandedEvent { timelineId: string; - event: TimelineExpandedEvent | null; + event: TimelineExpandedEvent | {}; } export const toggleExpandedEvent = actionCreator('TOGGLE_EXPANDED_EVENT'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index 05796e68d71db..daf57505b6baf 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -183,7 +183,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state.timelineById, [timelineId]: { ...state.timelineById[timelineId], - expandedEvent: event ?? {}, + expandedEvent: event, }, }, })) From a24635afc391e825516e7d4c70abe17d2bb3a4f2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 09:40:39 +0800 Subject: [PATCH 25/60] review --- .../event_details/summary_view.test.tsx | 6 -- .../components/event_details/summary_view.tsx | 84 +++++++++++++------ .../common/components/line_clamp/index.tsx | 6 +- .../components/fields_browser/field_items.tsx | 2 +- .../components/fields_browser/helpers.tsx | 9 +- 5 files changed, 69 insertions(+), 38 deletions(-) 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 de8f412e085d1..8658825ed08ac 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 @@ -13,12 +13,6 @@ import { BrowserFields } from '../../containers/source'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; -jest.mock('../../../timelines/components/timeline/body/renderers/formatted_field', () => { - return { - FormattedFieldValue: jest.fn(() =>
), - }; -}); - jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { return { useRuleAsync: jest.fn(), 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 8c80982a6663a..f114f4d289946 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 @@ -6,13 +6,16 @@ import React, { useEffect, useMemo, useState } from 'react'; import { + EuiTitle, EuiDescriptionList, EuiSpacer, EuiDescriptionListTitle, EuiDescriptionListDescription, + EuiInMemoryTable, } from '@elastic/eui'; import { get, getOr } from 'lodash/fp'; +import styled from 'styled-components'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; import * as i18n from './translations'; @@ -30,7 +33,18 @@ import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../networ import { LineClamp } from '../line_clamp'; import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; -type Summary = Array<{ title: string; description: JSX.Element }>; +interface SummaryRow { + title: string; + description: { + contextId: string; + eventId: string; + fieldName: string; + value: string; + fieldType: string; + linkValue: string | undefined; + }; +} +type Summary = SummaryRow[]; const fields = [ { id: 'signal.status' }, @@ -48,6 +62,21 @@ const fields = [ { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, ]; +const StyledEuiInMemoryTable = styled(EuiInMemoryTable)` + .euiTableHeaderCell { + border: none; + } + .euiTableRowCell { + border: none; + } +`; + +const getTitle = (title: SummaryRow['title']) => ( + +
{title}
+
+); + const getDescription = ({ contextId, eventId, @@ -55,25 +84,16 @@ const getDescription = ({ value, fieldType = '', linkValue, -}: { - contextId: string; - eventId: string; - fieldName: string; - value?: string | null; - fieldType?: string; - linkValue?: string; -}) => { - return ( - - ); -}; +}: SummaryRow['description']) => ( + +); const getSummary = ({ data, @@ -98,14 +118,14 @@ const getSummary = ({ const value = getOr(null, 'originalValue', field); const category = field.category; const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; - const description = getDescription({ + const description = { contextId: timelineId, eventId, fieldName: item.id, value, fieldType: item.fieldType ?? fieldType, linkValue: linkValue ?? undefined, - }); + }; return [ ...acc, @@ -118,6 +138,20 @@ const getSummary = ({ : []; }; +const summaryColumns = [ + { + field: 'title', + truncateText: false, + render: getTitle, + width: 120, + }, + { + field: 'description', + truncateText: false, + render: getDescription, + }, +]; + export const SummaryViewComponent: React.FC<{ browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; @@ -151,10 +185,10 @@ export const SummaryViewComponent: React.FC<{ return ( <> - {investigationGuide && ( diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx index e7f762d3aebc3..f280c3110c12c 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -6,7 +6,7 @@ import React, { useRef, useState, useEffect, useCallback } from 'react'; -import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiButtonEmpty, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import * as i18n from './translations'; @@ -58,8 +58,10 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) <> {isExpanded ? (

{content}

- ) : ( + ) : isOverflow == null || isOverflow === true ? ( {content} + ) : ( + {content} )} {isOverflow && ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx index 3e18d92beb9c6..a75089b58b86b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.tsx @@ -128,7 +128,7 @@ export const getFieldItems = ({ columnHeaderType: defaultColumnHeaderType, id: field.name || '', width: DEFAULT_COLUMN_MIN_WIDTH, - ...getAlertColumnHeader(field.name || ''), + ...getAlertColumnHeader(timelineId, field.name || ''), }) } /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx index f582a3749bffc..ba9ade096c4d2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/helpers.tsx @@ -7,6 +7,7 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { filter, get, pickBy } from 'lodash/fp'; import styled from 'styled-components'; +import { TimelineId } from '../../../../common/types/timeline'; import { BrowserField, BrowserFields } from '../../../common/containers/source'; import { alertsHeaders } from '../../../detections/components/alerts_table/default_config'; @@ -143,7 +144,7 @@ export const mergeBrowserFieldsWithDefaultCategory = ( }), }); -export const getAlertColumnHeader = (fieldId: string) => - alertsHeaders.find( - (c) => c.id === fieldId && defaultHeaders.find((d) => d.id === c.id) == null - ) ?? {}; +export const getAlertColumnHeader = (timelineId: string, fieldId: string) => + timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage + ? alertsHeaders.find((c) => c.id === fieldId) ?? {} + : {}; From 2a74c406c92f4f64ba0b3c2a8987278f50b1055a Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 22:13:30 +0800 Subject: [PATCH 26/60] review --- .../event_details/summary_view.test.tsx | 35 +++++++++----- .../components/event_details/summary_view.tsx | 10 ++-- .../fields_browser/field_items.test.tsx | 4 +- .../timeline/body/actions/index.tsx | 5 +- .../body/renderers/formatted_field.tsx | 24 ++++------ .../renderers/formatted_field_helpers.tsx | 9 ---- .../timeline/body/renderers/rule_status.tsx | 43 +++++++++++++++++ .../components/timeline/body/translations.ts | 7 --- .../components/timeline/event_details.tsx | 47 ++++--------------- .../timeline/expandable_event/index.tsx | 10 ++-- .../timeline/query_tab_content/index.tsx | 43 +++++++++++++++-- 11 files changed, 136 insertions(+), 101 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx 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 8658825ed08ac..4a0e1a695f39e 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 @@ -4,15 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { mount } from 'enzyme'; import { waitFor } from '@testing-library/react'; import { SummaryViewComponent } from './summary_view'; import { mockAlertDetailsData } from './__mocks__'; -import { BrowserFields } from '../../containers/source'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; +import { TestProviders } from '../../mock'; +import { mockBrowserFields } from '../../containers/source/mock'; +import { useMountAppended } from '../../utils/use_mount_appended'; + jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { return { useRuleAsync: jest.fn(), @@ -20,18 +22,15 @@ jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async' }); const props = { - data: mockAlertDetailsData, - browserFields: {}, + data: mockAlertDetailsData as TimelineEventsDetailsItem[], + browserFields: mockBrowserFields, eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', timelineId: 'detections-page', -} as { - browserFields: BrowserFields; - data: TimelineEventsDetailsItem[]; - eventId: string; - timelineId: string; }; describe('SummaryViewComponent', () => { + const mount = useMountAppended(); + beforeEach(() => { jest.clearAllMocks(); (useRuleAsync as jest.Mock).mockReturnValue({ @@ -41,12 +40,20 @@ describe('SummaryViewComponent', () => { }); }); test('render correct items', () => { - const wrapper = mount(); + const wrapper = mount( + + + + ); expect(wrapper.find('[data-test-subj="summary-view"]').exists()).toEqual(true); }); test('render investigation guide', async () => { - const wrapper = mount(); + const wrapper = mount( + + + + ); await waitFor(() => { expect(wrapper.find('[data-test-subj="summary-view-guide"]').exists()).toEqual(true); }); @@ -58,7 +65,11 @@ describe('SummaryViewComponent', () => { note: null, }, }); - const wrapper = mount(); + const wrapper = mount( + + + + ); await waitFor(() => { expect(wrapper.find('[data-test-subj="summary-view-guide"]').exists()).toEqual(false); }); 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 f114f4d289946..523996b9b43c0 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 @@ -12,6 +12,7 @@ import { EuiDescriptionListTitle, EuiDescriptionListDescription, EuiInMemoryTable, + EuiBasicTableColumn, } from '@elastic/eui'; import { get, getOr } from 'lodash/fp'; @@ -62,7 +63,8 @@ const fields = [ { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, ]; -const StyledEuiInMemoryTable = styled(EuiInMemoryTable)` +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` .euiTableHeaderCell { border: none; } @@ -138,17 +140,19 @@ const getSummary = ({ : []; }; -const summaryColumns = [ +const summaryColumns: Array> = [ { field: 'title', truncateText: false, render: getTitle, - width: 120, + width: '120px', + name: '', }, { field: 'description', truncateText: false, render: getDescription, + name: '', }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx index 57d155bb848b8..0e92491be8d18 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/fields_browser/field_items.test.tsx @@ -215,7 +215,7 @@ describe('field_items', () => { aggregatable: true, category: 'signal', description: 'rule name', - example: '2016-05-23T08:05:34.853Z', + example: '', format: '', indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: 'signal.rule.name', @@ -259,8 +259,6 @@ describe('field_items', () => { expect(toggleColumn).toBeCalledWith({ columnHeaderType: 'not-filtered', id: 'signal.rule.name', - label: 'Rule', - linkField: 'signal.rule.id', width: 180, }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index 98c0c5f632bbe..acf1bf38caae3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -66,10 +66,7 @@ const ActionsComponent: React.FC = ({ )} - + = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value, linkValue }) => { - const signalStatusBadgeColor = useMemo(() => getSingalStatusBadge(value), [value]); - if (fieldType === IP_FIELD_TYPE) { return ( + ); } else if ( [RULE_REFERENCE_FIELD_NAME, REFERENCE_URL_FIELD_NAME, EVENT_URL_FIELD_NAME].includes(fieldName) ) { @@ -147,7 +145,7 @@ const FormattedFieldValueComponent: React.FC<{ } else { const contentValue = getOrEmptyTagFromValue(value); const content = truncate ? {contentValue} : contentValue; - const defaultDraggable = ( + return ( ); - if (fieldName === SIGNAL_STATUS_FIELD_NAME) { - return {defaultDraggable}; - } - return defaultDraggable; } }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index 16052b127b1c5..69d1abe7e5ce9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -196,12 +196,3 @@ export const renderUrl = ({ getEmptyTagValue() ); }; - -export const getSingalStatusBadge = (status: string | number | null | undefined) => { - const mapping = { - open: 'primary', - 'in progress': 'warnging', - }; - - return status ? getOr('default', status, mapping) : 'default'; -}; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx new file mode 100644 index 0000000000000..33abf533ea873 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo } from 'react'; +import { EuiBadge } from '@elastic/eui'; +import { getOr } from 'lodash/fp'; + +import { DefaultDraggable } from '../../../../../common/components/draggables'; + +const mapping = { + open: 'primary', + 'in progress': 'warning', + closed: 'default', +}; + +interface Props { + contextId: string; + eventId: string; + fieldName: string; + value: string | number | undefined | null; +} + +export const RenderRuleStatus: React.FC = ({ contextId, eventId, fieldName, value }) => { + const color = useMemo(() => getOr('default', `${value}`, mapping), [value]); + return ( + + + {value} + + + ); +}; + +export const RuleStatus = React.memo(RenderRuleStatus); +RuleStatus.displayName = 'RuleStatus'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts index 11549c21604c0..fa39130987096 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/translations.ts @@ -87,13 +87,6 @@ export const COLLAPSE = i18n.translate( } ); -export const COLLAPSE_EVENT = i18n.translate( - 'xpack.securitySolution.timeline.body.actions.collapseEventTooltip', - { - defaultMessage: 'Collapse event', - } -); - export const ACTION_INVESTIGATE_IN_RESOLVER = i18n.translate( 'xpack.securitySolution.timeline.body.actions.investigateInResolverTooltip', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index a45fbafaeb1cb..34b191cb1c527 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -11,41 +11,37 @@ */ import { EuiSpacer } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo } from 'react'; +import React, { useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; import { findIndex, pick } from 'lodash/fp'; -import { useDispatch } from 'react-redux'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; import { ExpandableEvent, ExpandableEventTitle, - OnEventDetailsClose, + HandleOnEventClosed, } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; import { useTimelineEventsDetails } from '../../containers/details'; -import { TimelineId } from '../../../../common/types/timeline'; -import { activeTimeline } from '../../containers/active_timeline_context'; -import { timelineActions, timelineSelectors } from '../../store/timeline'; +import { timelineSelectors } from '../../store/timeline'; import { timelineDefaults } from '../../store/timeline/defaults'; interface EventDetailsProps { browserFields: BrowserFields; docValueFields: DocValueFields[]; timelineId: string; - onEventDetailsClose?: OnEventDetailsClose; + handleOnEventClosed?: HandleOnEventClosed; } const EventDetailsComponent: React.FC = ({ browserFields, docValueFields, timelineId, - onEventDetailsClose, + handleOnEventClosed, }) => { - const dispatch = useDispatch(); const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { expandedEvent, isSaving } = useDeepEqualSelector((state) => - pick(['expandedEvent', 'isSaving'], getTimeline(state, timelineId) ?? timelineDefaults) + const { expandedEvent } = useDeepEqualSelector((state) => + pick(['expandedEvent'], getTimeline(state, timelineId) ?? timelineDefaults) ); const [loading, detailsData] = useTimelineEventsDetails({ @@ -62,39 +58,12 @@ const EventDetailsComponent: React.FC = ({ return false; }, [detailsData]); - const handleOnEventClosed = useCallback(() => { - dispatch( - timelineActions.toggleExpandedEvent({ - timelineId, - event: {}, - }) - ); - - if (timelineId === TimelineId.active) { - activeTimeline.toggleExpandedEvent({ - eventId: expandedEvent.eventId, - indexName: expandedEvent.indexName, - loading: false, - }); - } - - if (onEventDetailsClose) { - onEventDetailsClose(); - } - }, [dispatch, timelineId, expandedEvent.indexName, expandedEvent.eventId, onEventDetailsClose]); - - useEffect(() => { - if (isSaving) { - handleOnEventClosed(); - } - }, [isSaving, onEventDetailsClose, handleOnEventClosed]); - return ( <> void; +export type HandleOnEventClosed = () => void; interface Props { browserFields: BrowserFields; detailsData: TimelineEventsDetailsItem[] | null; @@ -45,7 +45,7 @@ interface Props { interface ExpandableEventTitleProps { isAlert: boolean; loading: boolean; - onEventDetailsClose?: OnEventDetailsClose; + handleOnEventClosed?: HandleOnEventClosed; } const StyledEuiFlexGroup = styled(EuiFlexGroup)` @@ -53,16 +53,16 @@ const StyledEuiFlexGroup = styled(EuiFlexGroup)` `; export const ExpandableEventTitle = React.memo( - ({ isAlert, loading, onEventDetailsClose }: ExpandableEventTitleProps) => ( + ({ isAlert, loading, handleOnEventClosed }: ExpandableEventTitleProps) => ( {!loading ?

{isAlert ? i18n.ALERT_DETAILS : i18n.EVENT_DETAILS}

: <>}
- {onEventDetailsClose && ( + {handleOnEventClosed && ( - + )}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 12c7e9509d437..44206a48838cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -13,11 +13,11 @@ import { EuiFlyoutFooter, EuiSpacer, } from '@elastic/eui'; -import { isEmpty } from 'lodash/fp'; +import { isEmpty, pick } from 'lodash/fp'; import React, { useState, useMemo, useEffect, useCallback } from 'react'; import styled from 'styled-components'; import { Dispatch } from 'redux'; -import { connect, ConnectedProps } from 'react-redux'; +import { connect, ConnectedProps, useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; @@ -32,7 +32,7 @@ import { combineQueries } from '../helpers'; import { TimelineRefetch } from '../refetch_timeline'; import { esQuery, FilterManager } from '../../../../../../../../src/plugins/data/public'; import { useManageTimeline } from '../../manage_timeline'; -import { TimelineEventsType } from '../../../../../common/types/timeline'; +import { TimelineEventsType, TimelineId } from '../../../../../common/types/timeline'; import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config'; import { SuperDatePicker } from '../../../../common/components/super_date_picker'; import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context'; @@ -45,6 +45,8 @@ import { useSourcererScope } from '../../../../common/containers/sourcerer'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { EventDetails } from '../event_details'; import { TimelineDatePickerLock } from '../date_picker_lock'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { activeTimeline } from '../../../containers/active_timeline_context'; const TimelineHeaderContainer = styled.div` margin-top: 6px; @@ -250,10 +252,43 @@ export const QueryTabContentComponent: React.FC = ({ timerangeKind, }); + const dispatch = useDispatch(); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const { expandedEvent } = useDeepEqualSelector((state) => + pick(['expandedEvent'], getTimeline(state, timelineId) ?? timelineDefaults) + ); + + const handleOnEventClosed = useCallback(() => { + dispatch( + timelineActions.toggleExpandedEvent({ + timelineId, + event: {}, + }) + ); + + if (timelineId === TimelineId.active) { + activeTimeline.toggleExpandedEvent({ + eventId: expandedEvent.eventId, + indexName: expandedEvent.indexName, + loading: false, + }); + } + + if (onEventDetailsClose) { + onEventDetailsClose(); + } + }, [dispatch, timelineId, expandedEvent.indexName, expandedEvent.eventId, onEventDetailsClose]); + useEffect(() => { setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer }); }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); + useEffect(() => { + if (isQueryLoading && expandedEvent) { + handleOnEventClosed(); + } + }, [isQueryLoading, expandedEvent, handleOnEventClosed]); + return ( <> = ({ browserFields={browserFields} docValueFields={docValueFields} timelineId={timelineId} - onEventDetailsClose={onEventDetailsClose} + handleOnEventClosed={handleOnEventClosed} /> From 190251d0273ae40376980508611b87b13985d9d2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 22:58:47 +0800 Subject: [PATCH 27/60] review --- .../components/event_details/summary_view.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) 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 523996b9b43c0..05432dfb835ac 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 @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { EuiTitle, @@ -162,8 +162,6 @@ export const SummaryViewComponent: React.FC<{ eventId: string; timelineId: string; }> = ({ data, eventId, timelineId, browserFields }) => { - const [investigationGuide, setInvestigationGuide] = useState(null); - const ruleId = useMemo( () => getOr( @@ -181,12 +179,6 @@ export const SummaryViewComponent: React.FC<{ timelineId, ]); - useEffect(() => { - if (maybeRule != null && maybeRule.note != null) { - setInvestigationGuide(maybeRule.note); - } - }, [maybeRule]); - return ( <> - {investigationGuide && ( + {maybeRule?.note && ( <> {i18n.INVESTIGATION_GUIDE} - + From cd57475913964197d174c0463587d2bc561b275a Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 23:14:35 +0800 Subject: [PATCH 28/60] fix i18n check --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1f36a5a71537b..ba19e6766c6e5 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17968,7 +17968,6 @@ "xpack.securitySolution.timeline.autosave.warning.refresh.title": "タイムラインを更新", "xpack.securitySolution.timeline.autosave.warning.title": "更新されるまで自動保存は無効です", "xpack.securitySolution.timeline.body.actions.collapseAriaLabel": "縮小", - "xpack.securitySolution.timeline.body.actions.collapseEventTooltip": "イベントを折りたたむ", "xpack.securitySolution.timeline.body.actions.expandAriaLabel": "拡張", "xpack.securitySolution.timeline.body.actions.investigateInResolverTooltip": "イベントを分析します", "xpack.securitySolution.timeline.body.copyToClipboardButtonLabel": "クリップボードにコピー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3151b863cc4a1..35bd1b7e30368 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17986,7 +17986,6 @@ "xpack.securitySolution.timeline.autosave.warning.refresh.title": "刷新时间线", "xpack.securitySolution.timeline.autosave.warning.title": "刷新后才会启用自动保存", "xpack.securitySolution.timeline.body.actions.collapseAriaLabel": "折叠", - "xpack.securitySolution.timeline.body.actions.collapseEventTooltip": "折叠事件", "xpack.securitySolution.timeline.body.actions.expandAriaLabel": "展开", "xpack.securitySolution.timeline.body.actions.investigateInResolverTooltip": "分析事件", "xpack.securitySolution.timeline.body.copyToClipboardButtonLabel": "复制到剪贴板", From 76a4cfcdbb4decd23eec8aec52230b76949ded6c Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 9 Dec 2020 23:17:23 +0800 Subject: [PATCH 29/60] fix import --- .../timeline/body/renderers/formatted_field_helpers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx index 69d1abe7e5ce9..f34f7386e4082 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field_helpers.tsx @@ -5,7 +5,7 @@ */ import { EuiLink, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui'; -import { isString, isEmpty, getOr } from 'lodash/fp'; +import { isString, isEmpty } from 'lodash/fp'; import React, { useCallback } from 'react'; import styled from 'styled-components'; From 5c889b8265ba54b3090d919be1e747f02a432b23 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 00:17:07 +0800 Subject: [PATCH 30/60] fix eslint --- .../public/common/components/line_clamp/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx index f280c3110c12c..2f880a8570569 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -51,8 +51,7 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) setIsOverflow(false); } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [content, descriptionRef?.current?.clientHeight]); + }, [content]); return content != null ? ( <> From eff198be4c2574f1107cd64367418348379277bb Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 05:46:40 +0800 Subject: [PATCH 31/60] use connect --- .../timeline/body/renderers/rule_status.tsx | 23 +++---- .../timeline/query_tab_content/index.tsx | 60 ++++++++----------- .../timelines/store/timeline/actions.ts | 2 +- 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx index 33abf533ea873..38d76753356d6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx @@ -8,6 +8,7 @@ import React, { useMemo } from 'react'; import { EuiBadge } from '@elastic/eui'; import { getOr } from 'lodash/fp'; +import styled from 'styled-components'; import { DefaultDraggable } from '../../../../../common/components/draggables'; const mapping = { @@ -16,6 +17,10 @@ const mapping = { closed: 'default', }; +const StyledEuiBadge = styled(EuiBadge)` + text-transform: capitalize; +`; + interface Props { contextId: string; eventId: string; @@ -26,16 +31,14 @@ interface Props { export const RenderRuleStatus: React.FC = ({ contextId, eventId, fieldName, value }) => { const color = useMemo(() => getOr('default', `${value}`, mapping), [value]); return ( - - - {value} - - + + {value} + ); }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 44206a48838cc..df1106a5dde69 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -13,11 +13,11 @@ import { EuiFlyoutFooter, EuiSpacer, } from '@elastic/eui'; -import { isEmpty, pick } from 'lodash/fp'; +import { isEmpty, findIndex } from 'lodash/fp'; import React, { useState, useMemo, useEffect, useCallback } from 'react'; import styled from 'styled-components'; import { Dispatch } from 'redux'; -import { connect, ConnectedProps, useDispatch } from 'react-redux'; +import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; @@ -45,7 +45,6 @@ import { useSourcererScope } from '../../../../common/containers/sourcerer'; import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { EventDetails } from '../event_details'; import { TimelineDatePickerLock } from '../date_picker_lock'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { activeTimeline } from '../../../containers/active_timeline_context'; const TimelineHeaderContainer = styled.div` @@ -143,6 +142,7 @@ export const QueryTabContentComponent: React.FC = ({ dataProviders, end, eventType, + expandedEvent, filters, timelineId, isLive, @@ -150,6 +150,7 @@ export const QueryTabContentComponent: React.FC = ({ itemsPerPageOptions, kqlMode, kqlQueryExpression, + onEventClosed, showCallOutUnauthorizedMsg, showEventDetails, start, @@ -160,10 +161,6 @@ export const QueryTabContentComponent: React.FC = ({ }) => { const [showEventDetailsColumn, setShowEventDetailsColumn] = useState(false); - const onEventDetailsClose = useCallback(() => { - setShowEventDetailsColumn(false); - }, [setShowEventDetailsColumn]); - useEffect(() => { // it should changed only once to true and then stay visible till the component umount setShowEventDetailsColumn((current) => { @@ -252,42 +249,20 @@ export const QueryTabContentComponent: React.FC = ({ timerangeKind, }); - const dispatch = useDispatch(); - const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { expandedEvent } = useDeepEqualSelector((state) => - pick(['expandedEvent'], getTimeline(state, timelineId) ?? timelineDefaults) - ); - const handleOnEventClosed = useCallback(() => { - dispatch( - timelineActions.toggleExpandedEvent({ - timelineId, - event: {}, - }) - ); - - if (timelineId === TimelineId.active) { - activeTimeline.toggleExpandedEvent({ - eventId: expandedEvent.eventId, - indexName: expandedEvent.indexName, - loading: false, - }); - } - - if (onEventDetailsClose) { - onEventDetailsClose(); - } - }, [dispatch, timelineId, expandedEvent.indexName, expandedEvent.eventId, onEventDetailsClose]); + onEventClosed({ indexName: expandedEvent.indexName, eventId: expandedEvent.eventId }); + setShowEventDetailsColumn(false); + }, [expandedEvent.indexName, expandedEvent.eventId, onEventClosed]); useEffect(() => { setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer }); }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); useEffect(() => { - if (isQueryLoading && expandedEvent) { + if (findIndex((e) => e._id === expandedEvent.eventId, events) < 0) { handleOnEventClosed(); } - }, [isQueryLoading, expandedEvent, handleOnEventClosed]); + }, [expandedEvent, handleOnEventClosed, events]); return ( <> @@ -414,6 +389,7 @@ const makeMapStateToProps = () => { dataProviders, eventType: eventType ?? 'raw', end: input.timerange.to, + expandedEvent, filters: timelineFilter, timelineId, isLive: input.policy.kind === 'interval', @@ -443,6 +419,22 @@ const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({ }) ); }, + onEventClosed: ({ indexName, eventId }: { indexName: string; eventId: string }) => { + dispatch( + timelineActions.toggleExpandedEvent({ + timelineId, + event: {}, + }) + ); + + if (timelineId === TimelineId.active) { + activeTimeline.toggleExpandedEvent({ + eventId, + indexName, + loading: false, + }); + } + }, }); const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 17e8b9b3b9a34..b8dfa698a9307 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -37,7 +37,7 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI interface ToggleExpandedEvent { timelineId: string; - event: TimelineExpandedEvent | {}; + event: TimelineExpandedEvent; } export const toggleExpandedEvent = actionCreator('TOGGLE_EXPANDED_EVENT'); From 63d1558b0e65dc1f92d1e981be685b3b9bcabdf1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 06:56:15 +0800 Subject: [PATCH 32/60] close flyout when expanded event doesn't exist in the list --- .../events_viewer/events_viewer.tsx | 14 +++++++++-- .../common/components/events_viewer/index.tsx | 24 ++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 208d60ac73865..dc3100c5085dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import { isEmpty } from 'lodash/fp'; +import { isEmpty, findIndex } from 'lodash/fp'; import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -35,7 +35,7 @@ import { inputsModel } from '../../store'; import { useManageTimeline } from '../../../timelines/components/manage_timeline'; import { ExitFullScreen } from '../exit_full_screen'; import { useFullScreen } from '../../containers/use_full_screen'; -import { TimelineId } from '../../../../common/types/timeline'; +import { TimelineExpandedEvent, TimelineId } from '../../../../common/types/timeline'; import { GraphOverlay } from '../../../timelines/components/graph_overlay'; export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px @@ -101,6 +101,7 @@ interface Props { deletedEventIds: Readonly; docValueFields: DocValueFields[]; end: string; + expandedEvent: TimelineExpandedEvent; filters: Filter[]; headerFilterGroup?: React.ReactNode; height?: number; @@ -113,6 +114,7 @@ interface Props { itemsPerPageOptions: number[]; kqlMode: KqlMode; query: Query; + onFlyoutCollapsed: (args: { indexName: string; eventId: string }) => void; onRuleChange?: () => void; start: string; sort: Sort; @@ -128,6 +130,7 @@ const EventsViewerComponent: React.FC = ({ deletedEventIds, docValueFields, end, + expandedEvent, filters, headerFilterGroup, id, @@ -140,6 +143,7 @@ const EventsViewerComponent: React.FC = ({ kqlMode, query, onRuleChange, + onFlyoutCollapsed, start, sort, utilityBar, @@ -225,6 +229,12 @@ const EventsViewerComponent: React.FC = ({ skip: !canQueryTimeline, }); + useEffect(() => { + if (findIndex((e) => e._id === expandedEvent.eventId, events) < 0) { + onFlyoutCollapsed({ indexName: expandedEvent.indexName, eventId: expandedEvent.eventId }); + } + }, [expandedEvent, onFlyoutCollapsed, events]); + const totalCountMinusDeleted = useMemo( () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), [deletedEventIds.length, totalCount] diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index ec3cbbdef98ad..cdebc8932e518 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -5,6 +5,7 @@ */ import React, { useMemo, useEffect } from 'react'; +import { Dispatch } from 'redux'; import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import styled from 'styled-components'; @@ -20,6 +21,8 @@ import { useFullScreen } from '../../containers/use_full_screen'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererScope } from '../../containers/sourcerer'; import { EventDetailsFlyout } from './event_details_flyout'; +import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; +import { TimelineId } from '../../../../common/types/timeline'; const DEFAULT_EVENTS_VIEWER_HEIGHT = 652; @@ -51,6 +54,7 @@ const StatefulEventsViewerComponent: React.FC = ({ deletedEventIds, deleteEventQuery, end, + expandedEvent, excludedRowRendererIds, filters, headerFilterGroup, @@ -62,6 +66,7 @@ const StatefulEventsViewerComponent: React.FC = ({ pageFilters, query, onRuleChange, + onFlyoutCollapsed, start, scopeId, showCheckboxes, @@ -111,6 +116,7 @@ const StatefulEventsViewerComponent: React.FC = ({ dataProviders={dataProviders!} deletedEventIds={deletedEventIds} end={end} + expandedEvent={expandedEvent} isLoadingIndexPattern={isLoadingIndexPattern} filters={globalFilters} headerFilterGroup={headerFilterGroup} @@ -122,6 +128,7 @@ const StatefulEventsViewerComponent: React.FC = ({ kqlMode={kqlMode} query={query} onRuleChange={onRuleChange} + onFlyoutCollapsed={onFlyoutCollapsed} start={start} sort={sort} utilityBar={utilityBar} @@ -147,6 +154,8 @@ const makeMapStateToProps = () => { const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); const events: TimelineModel = getEvents(state, id) ?? defaultModel; + const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; + const { expandedEvent, graphEventId } = timeline; const { columns, dataProviders, @@ -163,6 +172,7 @@ const makeMapStateToProps = () => { columns, dataProviders, deletedEventIds, + expandedEvent, excludedRowRendererIds, filters: getGlobalFiltersQuerySelector(state), id, @@ -175,16 +185,24 @@ const makeMapStateToProps = () => { showCheckboxes, // Used to determine whether the footer should show (since it is hidden if the graph is showing.) // `getTimeline` actually returns `TimelineModel | undefined` - graphEventId: (getTimeline(state, id) as TimelineModel | undefined)?.graphEventId, + graphEventId, }; }; return mapStateToProps; }; -const mapDispatchToProps = { +const mapDispatchToProps = (dispatch: Dispatch) => ({ createTimeline: timelineActions.createTimeline, deleteEventQuery: inputsActions.deleteOneQuery, -}; + onFlyoutCollapsed: ({ indexName, eventId }: { indexName: string; eventId: string }) => { + dispatch( + timelineActions.toggleExpandedEvent({ + timelineId: TimelineId.detectionsPage, + event: {}, + }) + ); + }, +}); const connector = connect(makeMapStateToProps, mapDispatchToProps); From a8773e386e1e84d0e761b4fb2b7a67d4ccf0df3f Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Wed, 9 Dec 2020 22:58:25 +0000 Subject: [PATCH 33/60] Update x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patryk Kopyciński --- .../timelines/components/timeline/expandable_event/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index e9346e4159d6a..e40936ce0b2b8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -52,7 +52,7 @@ const StyledEuiFlexGroup = styled(EuiFlexGroup)` flex: 0; `; -export const ExpandableEventTitle = React.memo( +export const ExpandableEventTitle = React.memo( ({ isAlert, loading, handleOnEventClosed }: ExpandableEventTitleProps) => ( From 067e18fcf3348fd75295ff1c0685607ab2b96e36 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 08:03:15 +0800 Subject: [PATCH 34/60] fix types --- .../common/components/events_viewer/events_viewer.test.tsx | 2 ++ .../components/timeline/query_tab_content/index.test.tsx | 2 ++ .../public/timelines/store/timeline/epic_local_storage.test.tsx | 2 ++ 3 files changed, 6 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 7132add229edb..d6d567c6e004d 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -65,6 +65,7 @@ const eventsViewerDefaultProps = { deletedEventIds: [], docValueFields: [], end: to, + expandedEvent: {}, filters: [], id: TimelineId.detectionsPage, indexNames: mockIndexNames, @@ -78,6 +79,7 @@ const eventsViewerDefaultProps = { query: '', language: 'kql', }, + onFlyoutCollapsed: jest.fn(), start: from, sort: { columnId: 'foo', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 4019f46b8c07b..26723a8ad1c15 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -92,6 +92,7 @@ describe('Timeline', () => { columns: defaultHeaders, dataProviders: mockDataProviders, end: endDate, + expandedEvent: {}, eventType: 'all', showEventDetails: false, filters: [], @@ -101,6 +102,7 @@ describe('Timeline', () => { itemsPerPageOptions: [5, 10, 20], kqlMode: 'search' as QueryTabContentComponentProps['kqlMode'], kqlQueryExpression: '', + onEventClosed: jest.fn(), showCallOutUnauthorizedMsg: false, sort, start: startDate, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index a2bccaddb309e..a95c2f7da0839 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -78,12 +78,14 @@ describe('epicLocalStorage', () => { dataProviders: mockDataProviders, end: endDate, eventType: 'all', + expandedEvent: {}, filters: [], isLive: false, itemsPerPage: 5, itemsPerPageOptions: [5, 10, 20], kqlMode: 'search' as QueryTabContentComponentProps['kqlMode'], kqlQueryExpression: '', + onEventClosed: jest.fn(), showCallOutUnauthorizedMsg: false, showEventDetails: false, start: startDate, From a0cfb5165f6abff106355220cc4660b412d3c24b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 10:07:35 +0800 Subject: [PATCH 35/60] unit test --- .../common/components/events_viewer/events_viewer.test.tsx | 1 + .../public/common/components/events_viewer/index.tsx | 1 - .../public/timelines/components/timeline/body/index.tsx | 6 ++++-- .../query_tab_content/__snapshots__/index.test.tsx.snap | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index d6d567c6e004d..3b92bf61d0714 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -98,6 +98,7 @@ describe('EventsViewer', () => { id: 'test-stateful-events-viewer', start: from, scopeId: SourcererScopeName.timeline, + onFlyoutCollapsed: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index cdebc8932e518..1b7f7c0e381a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -167,7 +167,6 @@ const makeMapStateToProps = () => { sort, showCheckboxes, } = events; - return { columns, dataProviders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 45641a34f2cf4..c183f5bef21a1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -27,6 +27,7 @@ import { EventsTable, TimelineBody, TimelineBodyGlobalStyle } from '../styles'; import { ColumnHeaders } from './column_headers'; import { Events } from './events'; import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; +import { eventsDefaultModel } from '../../../../common/components/events_viewer/default_model'; interface OwnProps { browserFields: BrowserFields; @@ -206,12 +207,13 @@ const makeMapStateToProps = () => { headers: ColumnHeaderOptions[], browserFields: BrowserFields ) => ColumnHeaderOptions[] = memoizeOne(getColumnHeaders); - + const getEvents = timelineSelectors.getEventsByIdSelector(); const getTimeline = timelineSelectors.getTimelineByIdSelector(); const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { + const events: TimelineModel = getEvents(state, id) ?? eventsDefaultModel; const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; + const { columns } = events; const { - columns, eventIdToNoteIds, excludedRowRendererIds, isSelectAllChecked, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap index c726e92455f25..524eb488665b2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap @@ -261,6 +261,7 @@ In other use cases the message field can be used to concatenate different values } end="2018-03-24T03:33:52.253Z" eventType="all" + expandedEvent={Object {}} filters={Array []} isLive={false} itemsPerPage={5} @@ -273,6 +274,7 @@ In other use cases the message field can be used to concatenate different values } kqlMode="search" kqlQueryExpression="" + onEventClosed={[MockFunction]} showCallOutUnauthorizedMsg={false} showEventDetails={false} sort={ From 00f93009de7e49a4e8d143c0371a049b9e2f4153 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 10:10:00 +0800 Subject: [PATCH 36/60] fix rule status badge --- .../components/timeline/body/renderers/rule_status.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx index 38d76753356d6..b86745a2e7cc6 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx @@ -13,7 +13,7 @@ import { DefaultDraggable } from '../../../../../common/components/draggables'; const mapping = { open: 'primary', - 'in progress': 'warning', + 'in-progress': 'warning', closed: 'default', }; From 3112dbb358080b18cff739d810e780f238005a4d Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 18:48:21 +0800 Subject: [PATCH 37/60] isolate host name renderer --- .../body/renderers/formatted_field.tsx | 26 ++---------- .../timeline/body/renderers/host_name.tsx | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index bf38d4eec6118..9c1169608ccae 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -5,19 +5,15 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import { isNumber, isString, isEmpty } from 'lodash/fp'; +import { isNumber, isEmpty } from 'lodash/fp'; import React from 'react'; import { DefaultDraggable } from '../../../../../common/components/draggables'; import { Bytes, BYTES_FORMAT } from './bytes'; import { Duration, EVENT_DURATION_FIELD_NAME } from '../../../duration'; -import { - getOrEmptyTagFromValue, - getEmptyTagValue, -} from '../../../../../common/components/empty_value'; +import { getOrEmptyTagFromValue } from '../../../../../common/components/empty_value'; import { FormattedDate } from '../../../../../common/components/formatted_date'; import { FormattedIp } from '../../../../components/formatted_ip'; -import { HostDetailsLink } from '../../../../../common/components/links'; import { Port, PORT_NAMES } from '../../../../../network/components/port'; import { TruncatableText } from '../../../../../common/components/truncatable_text'; @@ -36,6 +32,7 @@ import { } from './constants'; import { RenderRuleName, renderEventModule, renderUrl } from './formatted_field_helpers'; import { RuleStatus } from './rule_status'; +import { HostName } from './host_name'; // simple black-list to prevent dragging and dropping fields such as message name const columnNamesNotDraggable = [MESSAGE_FIELD_NAME]; @@ -82,22 +79,7 @@ const FormattedFieldValueComponent: React.FC<{ ); } else if (fieldName === HOST_NAME_FIELD_NAME) { - const hostname = `${value}`; - - return isString(value) && hostname.length > 0 ? ( - - - {value} - - - ) : ( - getEmptyTagValue() - ); + return ; } else if (fieldFormat === BYTES_FORMAT) { return ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx new file mode 100644 index 0000000000000..a295b1507789c --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/host_name.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { isString } from 'lodash/fp'; + +import { DefaultDraggable } from '../../../../../common/components/draggables'; +import { getEmptyTagValue } from '../../../../../common/components/empty_value'; +import { HostDetailsLink } from '../../../../../common/components/links'; +import { TruncatableText } from '../../../../../common/components/truncatable_text'; + +interface Props { + contextId: string; + eventId: string; + fieldName: string; + value: string | number | undefined | null; +} + +const RenderHostName: React.FC = ({ fieldName, contextId, eventId, value }) => { + const hostname = `${value}`; + + return isString(value) && hostname.length > 0 ? ( + + + {value} + + + ) : ( + getEmptyTagValue() + ); +}; + +export const HostName = React.memo(RenderHostName); +HostName.displayName = 'HostName'; From 7a9a767397872b68e1f879741e3fee3495eae29b Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 20:38:58 +0800 Subject: [PATCH 38/60] fixup --- .../public/common/components/events_viewer/events_viewer.tsx | 2 +- .../timelines/components/timeline/query_tab_content/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 306a5d2515eaa..87a1ef50db318 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -231,7 +231,7 @@ const EventsViewerComponent: React.FC = ({ }); useEffect(() => { - if (findIndex((e) => e._id === expandedEvent.eventId, events) < 0) { + if (!events || findIndex((e) => e._id === expandedEvent?.eventId, events) < 0) { onFlyoutCollapsed({ indexName: expandedEvent.indexName, eventId: expandedEvent.eventId }); } }, [expandedEvent, onFlyoutCollapsed, events]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index affae0bc1df8c..16b84fd80a43b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -260,7 +260,7 @@ export const QueryTabContentComponent: React.FC = ({ }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); useEffect(() => { - if (findIndex((e) => e._id === expandedEvent.eventId, events) < 0) { + if (!events || findIndex((e) => e._id === expandedEvent.eventId, events) < 0) { handleOnEventClosed(); } }, [expandedEvent, handleOnEventClosed, events]); From 0d3436433d92b4eb42653d7f983ad52edc4a15d9 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 20:56:32 +0800 Subject: [PATCH 39/60] cypress --- .../public/common/components/events_viewer/index.tsx | 2 +- .../components/timeline/body/events/stateful_event.tsx | 3 ++- .../public/timelines/store/timeline/reducer.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 1b7f7c0e381a3..f79f1086a13f8 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -193,7 +193,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch: Dispatch) => ({ createTimeline: timelineActions.createTimeline, deleteEventQuery: inputsActions.deleteOneQuery, - onFlyoutCollapsed: ({ indexName, eventId }: { indexName: string; eventId: string }) => { + onFlyoutCollapsed: () => { dispatch( timelineActions.toggleExpandedEvent({ timelineId: TimelineId.detectionsPage, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 3b029948f0bdc..416a9aaab2cb4 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -28,6 +28,7 @@ import { EventColumnView } from './event_column_view'; import { inputsModel } from '../../../../../common/store'; import { timelineActions } from '../../../../store/timeline'; import { activeTimeline } from '../../../../containers/active_timeline_context'; +import { timelineDefaults } from '../../../../store/timeline/defaults'; interface Props { actionsColumnWidth: number; @@ -78,7 +79,7 @@ const StatefulEventComponent: React.FC = ({ const dispatch = useDispatch(); const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); const expandedEvent = useDeepEqualSelector( - (state) => state.timeline.timelineById[timelineId].expandedEvent + (state) => (state.timeline.timelineById[timelineId] ?? timelineDefaults).expandedEvent ); const divElement = useRef(null); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index daf57505b6baf..8b1bec8805b3f 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -103,6 +103,7 @@ import { import { TimelineState, EMPTY_TIMELINE_BY_ID } from './types'; import { TimelineType } from '../../../../common/types/timeline'; +import { timelineDefaults } from './defaults'; export const initialTimelineState: TimelineState = { timelineById: EMPTY_TIMELINE_BY_ID, @@ -182,7 +183,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) timelineById: { ...state.timelineById, [timelineId]: { - ...state.timelineById[timelineId], + ...(state.timelineById[timelineId] ?? timelineDefaults), expandedEvent: event, }, }, From 8e5fb1a961150bc919fc3c6116434c45d96a90cd Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 21:31:59 +0800 Subject: [PATCH 40/60] cypress --- .../timelines/store/timeline/reducer.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index 8b1bec8805b3f..b86387cd07a8a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -102,8 +102,9 @@ import { } from './helpers'; import { TimelineState, EMPTY_TIMELINE_BY_ID } from './types'; -import { TimelineType } from '../../../../common/types/timeline'; +import { TimelineId, TimelineType } from '../../../../common/types/timeline'; import { timelineDefaults } from './defaults'; +import { alertsDefaultModel } from '../../../detections/components/alerts_table/default_config'; export const initialTimelineState: TimelineState = { timelineById: EMPTY_TIMELINE_BY_ID, @@ -178,16 +179,19 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }), })) - .case(toggleExpandedEvent, (state, { timelineId, event }) => ({ - ...state, - timelineById: { - ...state.timelineById, - [timelineId]: { - ...(state.timelineById[timelineId] ?? timelineDefaults), - expandedEvent: event, + .case(toggleExpandedEvent, (state, { timelineId, event }) => { + return { + ...state, + timelineById: { + ...state.timelineById, + [timelineId]: { + ...(state.timelineById[timelineId] ?? + (timelineId === TimelineId.detectionsPage ? alertsDefaultModel : timelineDefaults)), + expandedEvent: event, + }, }, - }, - })) + }; + }) .case(addProvider, (state, { id, provider }) => ({ ...state, timelineById: addTimelineProvider({ id, provider, timelineById: state.timelineById }), From f72c67e8ad5ce99bd5f211f027d5e9c2e3f95c87 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 10 Dec 2020 23:20:11 +0800 Subject: [PATCH 41/60] defaultModel --- .../public/common/components/events_viewer/index.tsx | 6 +++++- .../public/timelines/store/timeline/actions.ts | 9 ++++++++- .../public/timelines/store/timeline/reducer.ts | 5 ++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index f79f1086a13f8..aba292d29f8a3 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -190,7 +190,10 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch: Dispatch) => ({ +const mapDispatchToProps = ( + dispatch: Dispatch, + { defaultModel }: { defaultModel: SubsetTimelineModel } +) => ({ createTimeline: timelineActions.createTimeline, deleteEventQuery: inputsActions.deleteOneQuery, onFlyoutCollapsed: () => { @@ -198,6 +201,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ timelineActions.toggleExpandedEvent({ timelineId: TimelineId.detectionsPage, event: {}, + defaultModel, }) ); }, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 479c289cdd21d..7ca6e1560255a 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -15,7 +15,13 @@ import { } from '../../../timelines/components/timeline/data_providers/data_provider'; import { SerializedFilterQuery } from '../../../common/store/types'; -import { KqlMode, TimelineModel, ColumnHeaderOptions, TimelineTabs } from './model'; +import { + KqlMode, + TimelineModel, + ColumnHeaderOptions, + TimelineTabs, + SubsetTimelineModel, +} from './model'; import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline'; import { TimelineEventsType, @@ -38,6 +44,7 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI interface ToggleExpandedEvent { timelineId: string; event: TimelineExpandedEvent; + defaultModel?: SubsetTimelineModel; } export const toggleExpandedEvent = actionCreator('TOGGLE_EXPANDED_EVENT'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index b86387cd07a8a..db4ecff553f49 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -179,14 +179,13 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }), })) - .case(toggleExpandedEvent, (state, { timelineId, event }) => { + .case(toggleExpandedEvent, (state, { timelineId, event, defaultModel }) => { return { ...state, timelineById: { ...state.timelineById, [timelineId]: { - ...(state.timelineById[timelineId] ?? - (timelineId === TimelineId.detectionsPage ? alertsDefaultModel : timelineDefaults)), + ...(state.timelineById[timelineId] ?? defaultModel ?? timelineDefaults), expandedEvent: event, }, }, From 3b574dcfea437bada4b100e948590775683204a1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 01:45:01 +0800 Subject: [PATCH 42/60] review comments --- .../events_viewer/event_details_flyout.tsx | 20 +++------ .../events_viewer/events_viewer.test.tsx | 2 +- .../events_viewer/events_viewer.tsx | 21 +++++++--- .../common/components/events_viewer/index.tsx | 41 +++++++++---------- .../timeline/body/actions/index.tsx | 2 +- .../timeline/body/renderers/host_name.tsx | 4 +- .../timeline/body/renderers/rule_status.tsx | 4 +- .../components/timeline/event_details.tsx | 6 +-- .../timeline/expandable_event/index.tsx | 2 +- .../timeline/query_tab_content/index.tsx | 30 ++++++-------- .../timelines/store/timeline/actions.ts | 2 +- .../timelines/store/timeline/reducer.ts | 3 +- 12 files changed, 66 insertions(+), 71 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index c2200eb36e405..d0880d3affb30 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -5,13 +5,11 @@ */ import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody } from '@elastic/eui'; -import React, { useMemo, useCallback } from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { useDispatch } from 'react-redux'; import { findIndex } from 'lodash/fp'; -import { timelineActions } from '../../../timelines/store/timeline'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { ExpandableEvent, @@ -20,6 +18,8 @@ import { import { useDeepEqualSelector } from '../../hooks/use_selector'; import { useTimelineEventsDetails } from '../../../timelines/containers/details'; +export type HandleCloseExpandedEvent = () => void; + const StyledEuiFlyout = styled(EuiFlyout)` z-index: ${({ theme }) => theme.eui.euiZLevel7}; `; @@ -28,6 +28,7 @@ interface EventDetailsFlyoutProps { browserFields: BrowserFields; docValueFields: DocValueFields[]; timelineId: string; + handleCloseExpandedEvent: HandleCloseExpandedEvent; } const emptyExpandedEvent = {}; @@ -36,8 +37,8 @@ const EventDetailsFlyoutComponent: React.FC = ({ browserFields, docValueFields, timelineId, + handleCloseExpandedEvent, }) => { - const dispatch = useDispatch(); const expandedEvent = useDeepEqualSelector( (state) => state.timeline.timelineById[timelineId]?.expandedEvent ?? emptyExpandedEvent ); @@ -56,21 +57,12 @@ const EventDetailsFlyoutComponent: React.FC = ({ return false; }, [detailsData]); - const handleClearSelection = useCallback(() => { - dispatch( - timelineActions.toggleExpandedEvent({ - timelineId, - event: emptyExpandedEvent, - }) - ); - }, [dispatch, timelineId]); - if (!expandedEvent.eventId) { return null; } return ( - + diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 94b0542ecf972..d7400b308625d 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -79,7 +79,7 @@ const eventsViewerDefaultProps = { query: '', language: 'kql', }, - onFlyoutCollapsed: jest.fn(), + handleCloseExpandedEvent: jest.fn(), start: from, sort: [ { diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 87a1ef50db318..7daeec4c48863 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -14,7 +14,11 @@ import { Direction } from '../../../../common/search_strategy'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { useTimelineEvents } from '../../../timelines/containers'; import { useKibana } from '../../lib/kibana'; -import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; +import { + ColumnHeaderOptions, + KqlMode, + SubsetTimelineModel, +} from '../../../timelines/store/timeline/model'; import { HeaderSection } from '../header_section'; import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers'; import { Sort } from '../../../timelines/components/timeline/body/sort'; @@ -37,6 +41,7 @@ import { ExitFullScreen } from '../exit_full_screen'; import { useFullScreen } from '../../containers/use_full_screen'; import { TimelineExpandedEvent, TimelineId } from '../../../../common/types/timeline'; import { GraphOverlay } from '../../../timelines/components/graph_overlay'; +import { ToggleExpandedEvent } from '../../../timelines/store/timeline/actions'; export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px const UTILITY_BAR_HEIGHT = 19; // px @@ -99,6 +104,7 @@ interface Props { columns: ColumnHeaderOptions[]; dataProviders: DataProvider[]; deletedEventIds: Readonly; + defaultModel?: SubsetTimelineModel; docValueFields: DocValueFields[]; end: string; expandedEvent: TimelineExpandedEvent; @@ -114,7 +120,7 @@ interface Props { itemsPerPageOptions: number[]; kqlMode: KqlMode; query: Query; - onFlyoutCollapsed: (args: { indexName: string; eventId: string }) => void; + handleCloseExpandedEvent: (args: ToggleExpandedEvent) => void; onRuleChange?: () => void; start: string; sort: Sort[]; @@ -127,6 +133,7 @@ const EventsViewerComponent: React.FC = ({ browserFields, columns, dataProviders, + defaultModel, deletedEventIds, docValueFields, end, @@ -143,7 +150,7 @@ const EventsViewerComponent: React.FC = ({ kqlMode, query, onRuleChange, - onFlyoutCollapsed, + handleCloseExpandedEvent, start, sort, utilityBar, @@ -232,9 +239,13 @@ const EventsViewerComponent: React.FC = ({ useEffect(() => { if (!events || findIndex((e) => e._id === expandedEvent?.eventId, events) < 0) { - onFlyoutCollapsed({ indexName: expandedEvent.indexName, eventId: expandedEvent.eventId }); + handleCloseExpandedEvent({ + timelineId: id, + event: {}, + defaultModel, + }); } - }, [expandedEvent, onFlyoutCollapsed, events]); + }, [events, expandedEvent, handleCloseExpandedEvent, id, defaultModel]); const totalCountMinusDeleted = useMemo( () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index aba292d29f8a3..bcfd4696cc65f 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useEffect } from 'react'; -import { Dispatch } from 'redux'; -import { connect, ConnectedProps } from 'react-redux'; +import React, { useMemo, useEffect, useCallback } from 'react'; +import { connect, ConnectedProps, useDispatch } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import styled from 'styled-components'; @@ -21,8 +20,6 @@ import { useFullScreen } from '../../containers/use_full_screen'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererScope } from '../../containers/sourcerer'; import { EventDetailsFlyout } from './event_details_flyout'; -import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { TimelineId } from '../../../../common/types/timeline'; const DEFAULT_EVENTS_VIEWER_HEIGHT = 652; @@ -51,6 +48,7 @@ const StatefulEventsViewerComponent: React.FC = ({ createTimeline, columns, dataProviders, + defaultModel, deletedEventIds, deleteEventQuery, end, @@ -103,6 +101,15 @@ const StatefulEventsViewerComponent: React.FC = ({ }, []); const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]); + const dispatch = useDispatch(); + const handleCloseExpandedEvent = useCallback(() => { + dispatch( + onFlyoutCollapsed({ + timelineId: id, + event: {}, + }) + ); + }, [dispatch, id, onFlyoutCollapsed]); return ( <> @@ -111,6 +118,7 @@ const StatefulEventsViewerComponent: React.FC = ({ = ({ kqlMode={kqlMode} query={query} onRuleChange={onRuleChange} - onFlyoutCollapsed={onFlyoutCollapsed} + handleCloseExpandedEvent={handleCloseExpandedEvent} start={start} sort={sort} utilityBar={utilityBar} @@ -140,6 +148,7 @@ const StatefulEventsViewerComponent: React.FC = ({ browserFields={browserFields} docValueFields={docValueFields} timelineId={id} + handleCloseExpandedEvent={handleCloseExpandedEvent} /> ); @@ -150,17 +159,16 @@ const makeMapStateToProps = () => { const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); const getEvents = timelineSelectors.getEventsByIdSelector(); - const getTimeline = timelineSelectors.getTimelineByIdSelector(); const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); const events: TimelineModel = getEvents(state, id) ?? defaultModel; - const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; - const { expandedEvent, graphEventId } = timeline; const { columns, dataProviders, deletedEventIds, excludedRowRendererIds, + expandedEvent, + graphEventId, itemsPerPage, itemsPerPageOptions, kqlMode, @@ -190,21 +198,10 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = ( - dispatch: Dispatch, - { defaultModel }: { defaultModel: SubsetTimelineModel } -) => ({ +const mapDispatchToProps = () => ({ createTimeline: timelineActions.createTimeline, deleteEventQuery: inputsActions.deleteOneQuery, - onFlyoutCollapsed: () => { - dispatch( - timelineActions.toggleExpandedEvent({ - timelineId: TimelineId.detectionsPage, - event: {}, - defaultModel, - }) - ); - }, + onFlyoutCollapsed: timelineActions.toggleExpandedEvent, }); const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index acf1bf38caae3..e7b6a9e005ca7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -66,7 +66,7 @@ const ActionsComponent: React.FC = ({ )} - + = ({ fieldName, contextId, eventId, value }) => { +const HostNameComponent: React.FC = ({ fieldName, contextId, eventId, value }) => { const hostname = `${value}`; return isString(value) && hostname.length > 0 ? ( @@ -38,5 +38,5 @@ const RenderHostName: React.FC = ({ fieldName, contextId, eventId, value ); }; -export const HostName = React.memo(RenderHostName); +export const HostName = React.memo(HostNameComponent); HostName.displayName = 'HostName'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx index b86745a2e7cc6..4dc6d3b2e8e8d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx @@ -28,7 +28,7 @@ interface Props { value: string | number | undefined | null; } -export const RenderRuleStatus: React.FC = ({ contextId, eventId, fieldName, value }) => { +const RuleStatusComponent: React.FC = ({ contextId, eventId, fieldName, value }) => { const color = useMemo(() => getOr('default', `${value}`, mapping), [value]); return ( = ({ contextId, eventId, fieldNam ); }; -export const RuleStatus = React.memo(RenderRuleStatus); +export const RuleStatus = React.memo(RuleStatusComponent); RuleStatus.displayName = 'RuleStatus'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index 34b191cb1c527..43c7ae3c8e7a5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -13,7 +13,7 @@ import { EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; -import { findIndex, pick } from 'lodash/fp'; +import { findIndex } from 'lodash/fp'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; import { @@ -40,8 +40,8 @@ const EventDetailsComponent: React.FC = ({ handleOnEventClosed, }) => { const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); - const { expandedEvent } = useDeepEqualSelector((state) => - pick(['expandedEvent'], getTimeline(state, timelineId) ?? timelineDefaults) + const expandedEvent = useDeepEqualSelector( + (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent ); const [loading, detailsData] = useTimelineEventsDetails({ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index e40936ce0b2b8..4adb987000233 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -53,7 +53,7 @@ const StyledEuiFlexGroup = styled(EuiFlexGroup)` `; export const ExpandableEventTitle = React.memo( - ({ isAlert, loading, handleOnEventClosed }: ExpandableEventTitleProps) => ( + ({ isAlert, loading, handleOnEventClosed }) => ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 16b84fd80a43b..72b97f85e580d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -46,6 +46,7 @@ import { TimelineModel } from '../../../../timelines/store/timeline/model'; import { EventDetails } from '../event_details'; import { TimelineDatePickerLock } from '../date_picker_lock'; import { activeTimeline } from '../../../containers/active_timeline_context'; +import { ToggleExpandedEvent } from '../../../store/timeline/actions'; const TimelineHeaderContainer = styled.div` margin-top: 6px; @@ -251,9 +252,17 @@ export const QueryTabContentComponent: React.FC = ({ }); const handleOnEventClosed = useCallback(() => { - onEventClosed({ indexName: expandedEvent.indexName, eventId: expandedEvent.eventId }); + onEventClosed({ timelineId, event: {} }); setShowEventDetailsColumn(false); - }, [expandedEvent.indexName, expandedEvent.eventId, onEventClosed]); + + if (timelineId === TimelineId.active) { + activeTimeline.toggleExpandedEvent({ + eventId: expandedEvent.eventId!, + indexName: expandedEvent.indexName!, + loading: false, + }); + } + }, [timelineId, onEventClosed, expandedEvent.eventId, expandedEvent.indexName]); useEffect(() => { setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer }); @@ -420,21 +429,8 @@ const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({ }) ); }, - onEventClosed: ({ indexName, eventId }: { indexName: string; eventId: string }) => { - dispatch( - timelineActions.toggleExpandedEvent({ - timelineId, - event: {}, - }) - ); - - if (timelineId === TimelineId.active) { - activeTimeline.toggleExpandedEvent({ - eventId, - indexName, - loading: false, - }); - } + onEventClosed: (args: ToggleExpandedEvent) => { + dispatch(timelineActions.toggleExpandedEvent(args)); }, }); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts index 7ca6e1560255a..68920f93559a7 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts @@ -41,7 +41,7 @@ export const addNoteToEvent = actionCreator<{ id: string; noteId: string; eventI 'ADD_NOTE_TO_EVENT' ); -interface ToggleExpandedEvent { +export interface ToggleExpandedEvent { timelineId: string; event: TimelineExpandedEvent; defaultModel?: SubsetTimelineModel; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index db4ecff553f49..d4ac3089109e4 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -102,9 +102,8 @@ import { } from './helpers'; import { TimelineState, EMPTY_TIMELINE_BY_ID } from './types'; -import { TimelineId, TimelineType } from '../../../../common/types/timeline'; +import { TimelineType } from '../../../../common/types/timeline'; import { timelineDefaults } from './defaults'; -import { alertsDefaultModel } from '../../../detections/components/alerts_table/default_config'; export const initialTimelineState: TimelineState = { timelineById: EMPTY_TIMELINE_BY_ID, From 1e25d9ed27149c6e44226b7dc025112c65c0f5ec Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 02:09:55 +0800 Subject: [PATCH 43/60] unit test --- .../common/components/events_viewer/events_viewer.test.tsx | 1 - .../public/common/components/events_viewer/index.tsx | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index d7400b308625d..5e5bdebffa182 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -100,7 +100,6 @@ describe('EventsViewer', () => { id: 'test-stateful-events-viewer', start: from, scopeId: SourcererScopeName.timeline, - onFlyoutCollapsed: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index bcfd4696cc65f..08245899d91e9 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -107,9 +107,10 @@ const StatefulEventsViewerComponent: React.FC = ({ onFlyoutCollapsed({ timelineId: id, event: {}, + defaultModel, }) ); - }, [dispatch, id, onFlyoutCollapsed]); + }, [dispatch, id, onFlyoutCollapsed, defaultModel]); return ( <> From 69850a38e1aa844a71ebbda59acf540573642c34 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 02:26:11 +0800 Subject: [PATCH 44/60] replace findIndex with some --- .../common/components/event_details/summary_view.tsx | 2 ++ .../components/events_viewer/event_details_flyout.tsx | 7 ++++--- .../public/timelines/components/timeline/event_details.tsx | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) 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 05432dfb835ac..3e46a896d8cda 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 @@ -79,6 +79,8 @@ const getTitle = (title: SummaryRow['title']) => ( ); +getTitle.displayName = 'getTitle'; + const getDescription = ({ contextId, eventId, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index d0880d3affb30..21f4ebc7b8bad 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -8,7 +8,7 @@ import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody } from '@elastic/eui'; import React, { useMemo } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { findIndex } from 'lodash/fp'; +import { some } from 'lodash/fp'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { @@ -52,7 +52,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ const isAlert = useMemo(() => { if (detailsData) { - return findIndex({ category: 'signal', field: 'signal.rule.id' }, detailsData) >= 0; + return some({ category: 'signal', field: 'signal.rule.id' }, detailsData); } return false; }, [detailsData]); @@ -85,5 +85,6 @@ export const EventDetailsFlyout = React.memo( (prevProps, nextProps) => deepEqual(prevProps.browserFields, nextProps.browserFields) && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && - prevProps.timelineId === nextProps.timelineId + prevProps.timelineId === nextProps.timelineId && + prevProps.handleCloseExpandedEvent === nextProps.handleCloseExpandedEvent ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index 43c7ae3c8e7a5..fd2c1aa163622 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -13,7 +13,7 @@ import { EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; -import { findIndex } from 'lodash/fp'; +import { some } from 'lodash/fp'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; import { @@ -53,7 +53,7 @@ const EventDetailsComponent: React.FC = ({ const isAlert = useMemo(() => { if (detailsData) { - return findIndex({ category: 'signal', field: 'signal.rule.id' }, detailsData) >= 0; + return some({ category: 'signal', field: 'signal.rule.id' }, detailsData); } return false; }, [detailsData]); From 08d6d8ced4412f95fc8093dd28ba2da9d8a2ad70 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 03:07:38 +0800 Subject: [PATCH 45/60] review --- .../event_details/event_fields_browser.tsx | 1 - .../events_viewer/events_viewer.tsx | 17 ++++++++-------- .../common/components/events_viewer/index.tsx | 4 ++-- .../common/components/line_clamp/index.tsx | 8 ++++++-- .../timeline/body/actions/index.tsx | 2 +- .../components/timeline/body/index.tsx | 5 +---- .../components/timeline/event_details.tsx | 3 ++- .../timeline/expandable_event/index.tsx | 16 ++++++++------- .../timeline/query_tab_content/index.tsx | 5 +++-- .../timelines/store/timeline/reducer.ts | 20 +++++++++---------- 10 files changed, 41 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index c5a03b5646e87..6b017a247d6ae 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -6,7 +6,6 @@ import { EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; - import { getOr, sortBy } from 'lodash/fp'; import { useDispatch } from 'react-redux'; import { rgba } from 'polished'; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 7daeec4c48863..9ff5630c18397 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable complexity */ + import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; -import { isEmpty, findIndex } from 'lodash/fp'; +import { isEmpty, some } from 'lodash/fp'; import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; @@ -41,7 +43,6 @@ import { ExitFullScreen } from '../exit_full_screen'; import { useFullScreen } from '../../containers/use_full_screen'; import { TimelineExpandedEvent, TimelineId } from '../../../../common/types/timeline'; import { GraphOverlay } from '../../../timelines/components/graph_overlay'; -import { ToggleExpandedEvent } from '../../../timelines/store/timeline/actions'; export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px const UTILITY_BAR_HEIGHT = 19; // px @@ -120,7 +121,7 @@ interface Props { itemsPerPageOptions: number[]; kqlMode: KqlMode; query: Query; - handleCloseExpandedEvent: (args: ToggleExpandedEvent) => void; + handleCloseExpandedEvent: () => void; onRuleChange?: () => void; start: string; sort: Sort[]; @@ -238,12 +239,8 @@ const EventsViewerComponent: React.FC = ({ }); useEffect(() => { - if (!events || findIndex((e) => e._id === expandedEvent?.eventId, events) < 0) { - handleCloseExpandedEvent({ - timelineId: id, - event: {}, - defaultModel, - }); + if (!events || !some((e) => e._id === expandedEvent?.eventId, events)) { + handleCloseExpandedEvent(); } }, [events, expandedEvent, handleCloseExpandedEvent, id, defaultModel]); @@ -351,9 +348,11 @@ export const EventsViewer = React.memo( prevProps.columns === nextProps.columns && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && prevProps.dataProviders === nextProps.dataProviders && + deepEqual(prevProps.defaultModel, nextProps.defaultModel) && prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && deepEqual(prevProps.filters, nextProps.filters) && + prevProps.handleCloseExpandedEvent === nextProps.handleCloseExpandedEvent && prevProps.headerFilterGroup === nextProps.headerFilterGroup && prevProps.id === nextProps.id && deepEqual(prevProps.indexPattern, nextProps.indexPattern) && diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 08245899d91e9..7f99440684726 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -199,11 +199,11 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = () => ({ +const mapDispatchToProps = { createTimeline: timelineActions.createTimeline, deleteEventQuery: inputsActions.deleteOneQuery, onFlyoutCollapsed: timelineActions.toggleExpandedEvent, -}); +}; const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx index 2f880a8570569..d2f16c0abaf92 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -53,7 +53,11 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content }) } }, [content]); - return content != null ? ( + if (!content) { + return null; + } + + return ( <> {isExpanded ? (

{content}

@@ -68,7 +72,7 @@ const LineClampComponent: React.FC<{ content?: string | null }> = ({ content })
)} - ) : null; + ); }; export const LineClamp = React.memo(LineClampComponent); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index e7b6a9e005ca7..b853dc8c81c00 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -68,7 +68,7 @@ const ActionsComponent: React.FC = ({ { headers: ColumnHeaderOptions[], browserFields: BrowserFields ) => ColumnHeaderOptions[] = memoizeOne(getColumnHeaders); - const getEvents = timelineSelectors.getEventsByIdSelector(); const getTimeline = timelineSelectors.getTimelineByIdSelector(); const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { - const events: TimelineModel = getEvents(state, id) ?? eventsDefaultModel; const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; - const { columns } = events; const { + columns, eventIdToNoteIds, excludedRowRendererIds, isSelectAllChecked, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index fd2c1aa163622..e4689e2554ec1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -83,5 +83,6 @@ export const EventDetails = React.memo( (prevProps, nextProps) => deepEqual(prevProps.browserFields, nextProps.browserFields) && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && - prevProps.timelineId === nextProps.timelineId + prevProps.timelineId === nextProps.timelineId && + prevProps.handleOnEventClosed === nextProps.handleOnEventClosed ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 4adb987000233..07b1d780fb6de 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -99,14 +99,16 @@ export const ExpandableEvent = React.memo( return ( <> {message && ( - - {i18n.MESSAGE} - - - - + <> + + {i18n.MESSAGE} + + + + + + )} - = ({ }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); useEffect(() => { - if (!events || findIndex((e) => e._id === expandedEvent.eventId, events) < 0) { + if (!events || !some((e) => e._id === expandedEvent.eventId, events)) { handleOnEventClosed(); } }, [expandedEvent, handleOnEventClosed, events]); @@ -448,6 +448,7 @@ const QueryTabContent = connector( prevProps.itemsPerPage === nextProps.itemsPerPage && prevProps.kqlMode === nextProps.kqlMode && prevProps.kqlQueryExpression === nextProps.kqlQueryExpression && + prevProps.onEventClosed === nextProps.onEventClosed && prevProps.showCallOutUnauthorizedMsg === nextProps.showCallOutUnauthorizedMsg && prevProps.showEventDetails === nextProps.showEventDetails && prevProps.status === nextProps.status && diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index d4ac3089109e4..22a06cbf5fb6e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -178,18 +178,16 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }), })) - .case(toggleExpandedEvent, (state, { timelineId, event, defaultModel }) => { - return { - ...state, - timelineById: { - ...state.timelineById, - [timelineId]: { - ...(state.timelineById[timelineId] ?? defaultModel ?? timelineDefaults), - expandedEvent: event, - }, + .case(toggleExpandedEvent, (state, { timelineId, event, defaultModel }) => ({ + ...state, + timelineById: { + ...state.timelineById, + [timelineId]: { + ...(state.timelineById[timelineId] ?? defaultModel ?? timelineDefaults), + expandedEvent: event, }, - }; - }) + }, + })) .case(addProvider, (state, { id, provider }) => ({ ...state, timelineById: addTimelineProvider({ id, provider, timelineById: state.timelineById }), From 5a36ca9623c7cd69074a1c6f8cf13554b4a133ac Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 04:34:28 +0800 Subject: [PATCH 46/60] remove defaultModel from toggle event action --- .../public/cases/components/case_view/index.tsx | 10 ++++++++++ .../events_viewer/events_viewer.test.tsx | 1 + .../components/events_viewer/events_viewer.tsx | 15 ++++++++------- .../common/components/events_viewer/index.tsx | 7 ++++--- .../components/timeline/body/index.test.tsx | 2 ++ .../timelines/components/timeline/body/index.tsx | 13 +++++++++---- .../timeline/query_tab_content/index.tsx | 6 +++++- .../public/timelines/store/timeline/actions.ts | 9 +-------- .../public/timelines/store/timeline/reducer.ts | 5 ++--- 9 files changed, 42 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index e5a673b03449f..6e86b2efcb015 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -360,6 +360,15 @@ export const CaseComponent = React.memo( [dispatch] ); + const handleCloseExpandedEvent = useCallback(() => { + dispatch( + timelineActions.toggleExpandedEvent({ + timelineId: TimelineId.casePage, + event: {}, + }) + ); + }, [dispatch]); + // useEffect used for component's initialization useEffect(() => { if (init.current) { @@ -483,6 +492,7 @@ export const CaseComponent = React.memo( browserFields={browserFields} docValueFields={docValueFields} timelineId={TimelineId.casePage} + handleCloseExpandedEvent={handleCloseExpandedEvent} /> diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 5e5bdebffa182..eb13a14614bc8 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -62,6 +62,7 @@ const eventsViewerDefaultProps = { browserFields: {}, columns: [], dataProviders: [], + defaultModel: eventsDefaultModel, deletedEventIds: [], docValueFields: [], end: to, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 9ff5630c18397..a069e203ea9f5 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable complexity */ - import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { isEmpty, some } from 'lodash/fp'; import React, { useEffect, useMemo, useState } from 'react'; @@ -104,8 +102,8 @@ interface Props { browserFields: BrowserFields; columns: ColumnHeaderOptions[]; dataProviders: DataProvider[]; + defaultModel: SubsetTimelineModel; deletedEventIds: Readonly; - defaultModel?: SubsetTimelineModel; docValueFields: DocValueFields[]; end: string; expandedEvent: TimelineExpandedEvent; @@ -133,8 +131,8 @@ interface Props { const EventsViewerComponent: React.FC = ({ browserFields, columns, - dataProviders, defaultModel, + dataProviders, deletedEventIds, docValueFields, end, @@ -239,10 +237,13 @@ const EventsViewerComponent: React.FC = ({ }); useEffect(() => { - if (!events || !some((e) => e._id === expandedEvent?.eventId, events)) { + if ( + !events || + (expandedEvent.eventId && !some((e) => e._id === expandedEvent.eventId, events)) + ) { handleCloseExpandedEvent(); } - }, [events, expandedEvent, handleCloseExpandedEvent, id, defaultModel]); + }, [events, expandedEvent, handleCloseExpandedEvent, id]); const totalCountMinusDeleted = useMemo( () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), @@ -310,6 +311,7 @@ const EventsViewerComponent: React.FC = ({ = ({ onFlyoutCollapsed({ timelineId: id, event: {}, - defaultModel, }) ); - }, [dispatch, id, onFlyoutCollapsed, defaultModel]); + }, [dispatch, id, onFlyoutCollapsed]); return ( <> @@ -162,7 +162,7 @@ const makeMapStateToProps = () => { const getEvents = timelineSelectors.getEventsByIdSelector(); const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); - const events: TimelineModel = getEvents(state, id) ?? defaultModel; + const events: TimelineModel = getEvents(state, id) ?? eventsDefaultModel; const { columns, dataProviders, @@ -179,6 +179,7 @@ const makeMapStateToProps = () => { return { columns, dataProviders, + defaultModel, deletedEventIds, expandedEvent, excludedRowRendererIds, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 704af61b4a12f..e63b3dfd8795c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -17,6 +17,7 @@ import { BodyComponent, StatefulBodyProps } from '.'; import { Sort } from './sort'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; +import { timelineDefaults } from '../../../store/timeline/defaults'; const mockSort: Sort[] = [ { @@ -64,6 +65,7 @@ describe('Body', () => { browserFields: mockBrowserFields, clearSelected: (jest.fn() as unknown) as StatefulBodyProps['clearSelected'], columnHeaders: defaultHeaders, + defaultModel: timelineDefaults, data: mockTimelineData, eventIdToNoteIds: {}, excludedRowRendererIds: [], diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index bf648a481a9ba..8098ef4ac4545 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -14,8 +14,11 @@ import { BrowserFields } from '../../../../common/containers/source'; import { TimelineItem } from '../../../../../common/search_strategy/timeline'; import { inputsModel, State } from '../../../../common/store'; import { useManageTimeline } from '../../manage_timeline'; -import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model'; -import { timelineDefaults } from '../../../store/timeline/defaults'; +import { + ColumnHeaderOptions, + SubsetTimelineModel, + TimelineModel, +} from '../../../store/timeline/model'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { OnRowSelected, OnSelectAll } from '../events'; import { getActionsColumnWidth, getColumnHeaders } from './column_headers/helpers'; @@ -30,6 +33,7 @@ import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; interface OwnProps { browserFields: BrowserFields; + defaultModel: SubsetTimelineModel; data: TimelineItem[]; id: string; isEventViewer?: boolean; @@ -207,8 +211,8 @@ const makeMapStateToProps = () => { browserFields: BrowserFields ) => ColumnHeaderOptions[] = memoizeOne(getColumnHeaders); const getTimeline = timelineSelectors.getTimelineByIdSelector(); - const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { - const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; + const mapStateToProps = (state: State, { browserFields, id, defaultModel }: OwnProps) => { + const timeline: TimelineModel = getTimeline(state, id) ?? defaultModel; const { columns, eventIdToNoteIds, @@ -222,6 +226,7 @@ const makeMapStateToProps = () => { return { columnHeaders: memoizedColumnHeaders(columns, browserFields), + defaultModel, eventIdToNoteIds, excludedRowRendererIds, isSelectAllChecked, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index f5401c184aeb3..1df3d9813630e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -269,7 +269,10 @@ export const QueryTabContentComponent: React.FC = ({ }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); useEffect(() => { - if (!events || !some((e) => e._id === expandedEvent.eventId, events)) { + if ( + !events || + (expandedEvent.eventId && !some((e) => e._id === expandedEvent.eventId, events)) + ) { handleOnEventClosed(); } }, [expandedEvent, handleOnEventClosed, events]); @@ -318,6 +321,7 @@ export const QueryTabContentComponent: React.FC = ({ className="timeline-flyout-body" > ('TOGGLE_EXPANDED_EVENT'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index 22a06cbf5fb6e..daf57505b6baf 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -103,7 +103,6 @@ import { import { TimelineState, EMPTY_TIMELINE_BY_ID } from './types'; import { TimelineType } from '../../../../common/types/timeline'; -import { timelineDefaults } from './defaults'; export const initialTimelineState: TimelineState = { timelineById: EMPTY_TIMELINE_BY_ID, @@ -178,12 +177,12 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }), })) - .case(toggleExpandedEvent, (state, { timelineId, event, defaultModel }) => ({ + .case(toggleExpandedEvent, (state, { timelineId, event }) => ({ ...state, timelineById: { ...state.timelineById, [timelineId]: { - ...(state.timelineById[timelineId] ?? defaultModel ?? timelineDefaults), + ...state.timelineById[timelineId], expandedEvent: event, }, }, From f4bbbe5e7581bdd2db5a8b2a344cf89d53ec316d Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 05:02:12 +0800 Subject: [PATCH 47/60] review --- .../components/events_viewer/event_details_flyout.tsx | 10 ++++------ .../timelines/components/timeline/event_details.tsx | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index 21f4ebc7b8bad..d4f84ecccf999 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -50,12 +50,10 @@ const EventDetailsFlyoutComponent: React.FC = ({ skip: !expandedEvent.eventId, }); - const isAlert = useMemo(() => { - if (detailsData) { - return some({ category: 'signal', field: 'signal.rule.id' }, detailsData); - } - return false; - }, [detailsData]); + const isAlert = useMemo( + () => some({ category: 'signal', field: 'signal.rule.id' }, detailsData), + [detailsData] + ); if (!expandedEvent.eventId) { return null; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index e4689e2554ec1..4e3bdee8ff150 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -51,12 +51,10 @@ const EventDetailsComponent: React.FC = ({ skip: !expandedEvent.eventId, }); - const isAlert = useMemo(() => { - if (detailsData) { - return some({ category: 'signal', field: 'signal.rule.id' }, detailsData); - } - return false; - }, [detailsData]); + const isAlert = useMemo( + () => some({ category: 'signal', field: 'signal.rule.id' }, detailsData), + [detailsData] + ); return ( <> From f8bd1a4d31baf527d1ff1dfdec70cceb1c0d7135 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 05:32:52 +0800 Subject: [PATCH 48/60] cleanup defaultModel --- .../public/common/components/events_viewer/index.tsx | 4 ++-- .../components/timeline/body/events/stateful_event.tsx | 5 +++-- .../public/timelines/components/timeline/body/index.test.tsx | 1 - .../public/timelines/components/timeline/body/index.tsx | 1 - x-pack/test/security_solution_cypress/runner.ts | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 5c0d4eb2c8e9e..8a9d4ec1c7f5f 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -20,7 +20,6 @@ import { useFullScreen } from '../../containers/use_full_screen'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { useSourcererScope } from '../../containers/sourcerer'; import { EventDetailsFlyout } from './event_details_flyout'; -import { eventsDefaultModel } from './default_model'; const DEFAULT_EVENTS_VIEWER_HEIGHT = 652; @@ -162,7 +161,7 @@ const makeMapStateToProps = () => { const getEvents = timelineSelectors.getEventsByIdSelector(); const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); - const events: TimelineModel = getEvents(state, id) ?? eventsDefaultModel; + const events: TimelineModel = getEvents(state, id) ?? defaultModel; const { columns, dataProviders, @@ -176,6 +175,7 @@ const makeMapStateToProps = () => { sort, showCheckboxes, } = events; + return { columns, dataProviders, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 416a9aaab2cb4..f660754085e9d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -26,7 +26,7 @@ import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; import { inputsModel } from '../../../../../common/store'; -import { timelineActions } from '../../../../store/timeline'; +import { timelineActions, timelineSelectors } from '../../../../store/timeline'; import { activeTimeline } from '../../../../containers/active_timeline_context'; import { timelineDefaults } from '../../../../store/timeline/defaults'; @@ -78,8 +78,9 @@ const StatefulEventComponent: React.FC = ({ }) => { const dispatch = useDispatch(); const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const expandedEvent = useDeepEqualSelector( - (state) => (state.timeline.timelineById[timelineId] ?? timelineDefaults).expandedEvent + (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent ); const divElement = useRef(null); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index e63b3dfd8795c..3447b0d6e2161 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -17,7 +17,6 @@ import { BodyComponent, StatefulBodyProps } from '.'; import { Sort } from './sort'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; -import { timelineDefaults } from '../../../store/timeline/defaults'; const mockSort: Sort[] = [ { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 8098ef4ac4545..4bc6e4ec3e256 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -226,7 +226,6 @@ const makeMapStateToProps = () => { return { columnHeaders: memoizedColumnHeaders(columns, browserFields), - defaultModel, eventIdToNoteIds, excludedRowRendererIds, isSelectAllChecked, diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index a1a1a3916ef7f..8d20e9b1c08e5 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -22,7 +22,7 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:run'], + args: ['cypress:open'], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', From 10371bc85f553c13ccaa7a373bedc84fe9751333 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 05:46:08 +0800 Subject: [PATCH 49/60] unit test --- .../public/timelines/components/timeline/body/index.test.tsx | 1 + x-pack/test/security_solution_cypress/runner.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 3447b0d6e2161..e63b3dfd8795c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -17,6 +17,7 @@ import { BodyComponent, StatefulBodyProps } from '.'; import { Sort } from './sort'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; +import { timelineDefaults } from '../../../store/timeline/defaults'; const mockSort: Sort[] = [ { diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index 8d20e9b1c08e5..a1a1a3916ef7f 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -22,7 +22,7 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:open'], + args: ['cypress:run'], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', From 99b3026b4cac8a6666ae8be807f0dcc98195a0e2 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 06:32:07 +0800 Subject: [PATCH 50/60] rollback handleClearSelection --- .../cases/components/case_view/index.tsx | 10 ---------- .../events_viewer/event_details_flyout.tsx | 18 ++++++++++-------- .../common/components/events_viewer/index.tsx | 1 - 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 6e86b2efcb015..e5a673b03449f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -360,15 +360,6 @@ export const CaseComponent = React.memo( [dispatch] ); - const handleCloseExpandedEvent = useCallback(() => { - dispatch( - timelineActions.toggleExpandedEvent({ - timelineId: TimelineId.casePage, - event: {}, - }) - ); - }, [dispatch]); - // useEffect used for component's initialization useEffect(() => { if (init.current) { @@ -492,7 +483,6 @@ export const CaseComponent = React.memo( browserFields={browserFields} docValueFields={docValueFields} timelineId={TimelineId.casePage} - handleCloseExpandedEvent={handleCloseExpandedEvent} /> diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index d4f84ecccf999..6a291f0db78c2 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -5,11 +5,12 @@ */ import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody } from '@elastic/eui'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; import { some } from 'lodash/fp'; +import { useDispatch } from 'react-redux'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { ExpandableEvent, @@ -17,8 +18,7 @@ import { } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { useTimelineEventsDetails } from '../../../timelines/containers/details'; - -export type HandleCloseExpandedEvent = () => void; +import { timelineActions } from '../../../timelines/store/timeline'; const StyledEuiFlyout = styled(EuiFlyout)` z-index: ${({ theme }) => theme.eui.euiZLevel7}; @@ -28,7 +28,6 @@ interface EventDetailsFlyoutProps { browserFields: BrowserFields; docValueFields: DocValueFields[]; timelineId: string; - handleCloseExpandedEvent: HandleCloseExpandedEvent; } const emptyExpandedEvent = {}; @@ -37,12 +36,16 @@ const EventDetailsFlyoutComponent: React.FC = ({ browserFields, docValueFields, timelineId, - handleCloseExpandedEvent, }) => { + const dispatch = useDispatch(); const expandedEvent = useDeepEqualSelector( (state) => state.timeline.timelineById[timelineId]?.expandedEvent ?? emptyExpandedEvent ); + const handleClearSelection = useCallback(() => { + dispatch(timelineActions.toggleExpandedEvent({ timelineId, event: emptyExpandedEvent })); + }, [dispatch, timelineId]); + const [loading, detailsData] = useTimelineEventsDetails({ docValueFields, indexName: expandedEvent.indexName!, @@ -60,7 +63,7 @@ const EventDetailsFlyoutComponent: React.FC = ({ } return ( - + @@ -83,6 +86,5 @@ export const EventDetailsFlyout = React.memo( (prevProps, nextProps) => deepEqual(prevProps.browserFields, nextProps.browserFields) && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && - prevProps.timelineId === nextProps.timelineId && - prevProps.handleCloseExpandedEvent === nextProps.handleCloseExpandedEvent + prevProps.timelineId === nextProps.timelineId ); diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 8a9d4ec1c7f5f..3e7395d6e399c 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -148,7 +148,6 @@ const StatefulEventsViewerComponent: React.FC = ({ browserFields={browserFields} docValueFields={docValueFields} timelineId={id} - handleCloseExpandedEvent={handleCloseExpandedEvent} /> ); From e49a362c1adbe41f7da1c723b0ee3ca9a1969163 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 18:29:16 +0800 Subject: [PATCH 51/60] fixup --- .../common/components/events_viewer/events_viewer.tsx | 2 ++ .../timelines/components/open_timeline/translations.ts | 7 ------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index a069e203ea9f5..6615d0707984c 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -345,11 +345,13 @@ const EventsViewerComponent: React.FC = ({ export const EventsViewer = React.memo( EventsViewerComponent, + // eslint-disable-next-line complexity (prevProps, nextProps) => deepEqual(prevProps.browserFields, nextProps.browserFields) && prevProps.columns === nextProps.columns && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && prevProps.dataProviders === nextProps.dataProviders && + deepEqual(prevProps.defaultModel, nextProps.defaultModel) && prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && deepEqual(prevProps.filters, nextProps.filters) && diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts index 3f391714bb058..268c874de7d50 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/translations.ts @@ -6,13 +6,6 @@ import { i18n } from '@kbn/i18n'; -export const ALL_ACTIONS = i18n.translate( - 'xpack.securitySolution.open.timeline.allActionsTooltip', - { - defaultMessage: 'All actions', - } -); - export const BATCH_ACTIONS = i18n.translate( 'xpack.securitySolution.open.timeline.batchActionsTitle', { From 56a7e52d28e01bea5b4e4f1bb58c9e6191d9d149 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Fri, 11 Dec 2020 19:03:30 +0800 Subject: [PATCH 52/60] fix i18n --- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 2 files changed, 2 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index cdacfcae2ccbd..7845a003b59ee 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17737,7 +17737,6 @@ "xpack.securitySolution.notes.notesTitle": "メモ", "xpack.securitySolution.notes.previewMarkdownTitle": "プレビュー(マークダウン)", "xpack.securitySolution.notes.search.FilterByUserOrNotePlaceholder": "ユーザーまたはメモでフィルター", - "xpack.securitySolution.open.timeline.allActionsTooltip": "すべてのアクション", "xpack.securitySolution.open.timeline.batchActionsTitle": "一斉アクション", "xpack.securitySolution.open.timeline.cancelButton": "キャンセル", "xpack.securitySolution.open.timeline.collapseButton": "縮小", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9204b2b3ecff3..83c98b794e002 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17755,7 +17755,6 @@ "xpack.securitySolution.notes.notesTitle": "备注", "xpack.securitySolution.notes.previewMarkdownTitle": "预览 (Markdown)", "xpack.securitySolution.notes.search.FilterByUserOrNotePlaceholder": "按用户或备注筛选", - "xpack.securitySolution.open.timeline.allActionsTooltip": "所有操作", "xpack.securitySolution.open.timeline.batchActionsTitle": "批处理操作", "xpack.securitySolution.open.timeline.cancelButton": "取消", "xpack.securitySolution.open.timeline.collapseButton": "折叠", From da31fe2a07fcd87ae4cda0b749bf683c9d8b1e6f Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 11 Dec 2020 13:33:25 +0100 Subject: [PATCH 53/60] cleanup defaultmodel --- .../common/types/timeline/index.ts | 1 - .../cases/components/case_view/index.tsx | 1 - .../event_details/event_fields_browser.tsx | 2 +- .../event_details/summary_view.test.tsx | 1 + .../components/event_details/summary_view.tsx | 6 ++--- .../events_viewer/event_details_flyout.tsx | 14 +++++----- .../events_viewer/events_viewer.test.tsx | 1 - .../events_viewer/events_viewer.tsx | 26 +++++-------------- .../common/components/events_viewer/index.tsx | 25 ++++-------------- .../common/components/line_clamp/index.tsx | 3 +-- .../row_renderers_browser/index.tsx | 18 ++++++------- .../body/events/event_column_view.tsx | 4 ++- .../timeline/body/events/stateful_event.tsx | 4 +-- .../components/timeline/body/index.test.tsx | 2 -- .../components/timeline/body/index.tsx | 13 ++++------ .../components/timeline/event_details.tsx | 2 +- .../timeline/expandable_event/index.tsx | 9 +++---- .../timeline/query_tab_content/index.tsx | 7 +---- .../timelines/store/timeline/actions.ts | 2 +- .../timelines/store/timeline/reducer.ts | 2 +- .../timelines/store/timeline/selectors.ts | 2 -- 21 files changed, 51 insertions(+), 94 deletions(-) diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index e447a004fb51c..aa114ff074898 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -411,7 +411,6 @@ export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom'; export interface TimelineExpandedEventType { eventId: string; indexName: string; - loading: boolean; } // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index e5a673b03449f..0e6226f69fce7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -352,7 +352,6 @@ export const CaseComponent = React.memo( event: { eventId: alertId, indexName: index, - loading: false, }, }) ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx index 6b017a247d6ae..315d8b88d15e2 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_fields_browser.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getOr, sortBy } from 'lodash/fp'; import { EuiInMemoryTable } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; -import { getOr, sortBy } from 'lodash/fp'; import { useDispatch } from 'react-redux'; import { rgba } from 'polished'; import styled from 'styled-components'; 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 4a0e1a695f39e..dec1bd9f3ac69 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 @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import React from 'react'; import { waitFor } from '@testing-library/react'; 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 3e46a896d8cda..58b24d0355f99 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 @@ -3,8 +3,8 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import { get, getOr } from 'lodash/fp'; import { EuiTitle, EuiDescriptionList, @@ -14,9 +14,9 @@ import { EuiInMemoryTable, EuiBasicTableColumn, } from '@elastic/eui'; - -import { get, getOr } from 'lodash/fp'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; + import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx index 6a291f0db78c2..6fecf0d739d1a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/event_details_flyout.tsx @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { some } from 'lodash/fp'; import { EuiFlyout, EuiFlyoutHeader, EuiFlyoutBody } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; -import { some } from 'lodash/fp'; - import { useDispatch } from 'react-redux'; + import { BrowserFields, DocValueFields } from '../../containers/source'; import { ExpandableEvent, @@ -18,7 +18,8 @@ import { } from '../../../timelines/components/timeline/expandable_event'; import { useDeepEqualSelector } from '../../hooks/use_selector'; import { useTimelineEventsDetails } from '../../../timelines/containers/details'; -import { timelineActions } from '../../../timelines/store/timeline'; +import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline'; +import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; const StyledEuiFlyout = styled(EuiFlyout)` z-index: ${({ theme }) => theme.eui.euiZLevel7}; @@ -30,20 +31,19 @@ interface EventDetailsFlyoutProps { timelineId: string; } -const emptyExpandedEvent = {}; - const EventDetailsFlyoutComponent: React.FC = ({ browserFields, docValueFields, timelineId, }) => { const dispatch = useDispatch(); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const expandedEvent = useDeepEqualSelector( - (state) => state.timeline.timelineById[timelineId]?.expandedEvent ?? emptyExpandedEvent + (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent ); const handleClearSelection = useCallback(() => { - dispatch(timelineActions.toggleExpandedEvent({ timelineId, event: emptyExpandedEvent })); + dispatch(timelineActions.toggleExpandedEvent({ timelineId })); }, [dispatch, timelineId]); const [loading, detailsData] = useTimelineEventsDetails({ diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index eb13a14614bc8..5e5bdebffa182 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -62,7 +62,6 @@ const eventsViewerDefaultProps = { browserFields: {}, columns: [], dataProviders: [], - defaultModel: eventsDefaultModel, deletedEventIds: [], docValueFields: [], end: to, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 6615d0707984c..69c75bfbea56a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -9,16 +9,14 @@ import { isEmpty, some } from 'lodash/fp'; import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import deepEqual from 'fast-deep-equal'; +import { useDispatch } from 'react-redux'; import { Direction } from '../../../../common/search_strategy'; import { BrowserFields, DocValueFields } from '../../containers/source'; import { useTimelineEvents } from '../../../timelines/containers'; +import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; -import { - ColumnHeaderOptions, - KqlMode, - SubsetTimelineModel, -} from '../../../timelines/store/timeline/model'; +import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; import { HeaderSection } from '../header_section'; import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers'; import { Sort } from '../../../timelines/components/timeline/body/sort'; @@ -102,7 +100,6 @@ interface Props { browserFields: BrowserFields; columns: ColumnHeaderOptions[]; dataProviders: DataProvider[]; - defaultModel: SubsetTimelineModel; deletedEventIds: Readonly; docValueFields: DocValueFields[]; end: string; @@ -119,7 +116,6 @@ interface Props { itemsPerPageOptions: number[]; kqlMode: KqlMode; query: Query; - handleCloseExpandedEvent: () => void; onRuleChange?: () => void; start: string; sort: Sort[]; @@ -131,7 +127,6 @@ interface Props { const EventsViewerComponent: React.FC = ({ browserFields, columns, - defaultModel, dataProviders, deletedEventIds, docValueFields, @@ -149,12 +144,12 @@ const EventsViewerComponent: React.FC = ({ kqlMode, query, onRuleChange, - handleCloseExpandedEvent, start, sort, utilityBar, graphEventId, }) => { + const dispatch = useDispatch(); const { globalFullScreen, timelineFullScreen } = useFullScreen(); const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; const kibana = useKibana(); @@ -237,13 +232,10 @@ const EventsViewerComponent: React.FC = ({ }); useEffect(() => { - if ( - !events || - (expandedEvent.eventId && !some((e) => e._id === expandedEvent.eventId, events)) - ) { - handleCloseExpandedEvent(); + if (!events || (expandedEvent.eventId && !some(['_id', expandedEvent.eventId], events))) { + dispatch(timelineActions.toggleExpandedEvent({ timelineId: id })); } - }, [events, expandedEvent, handleCloseExpandedEvent, id]); + }, [dispatch, events, expandedEvent, id]); const totalCountMinusDeleted = useMemo( () => (totalCount > 0 ? totalCount - deletedEventIds.length : 0), @@ -311,7 +303,6 @@ const EventsViewerComponent: React.FC = ({ = ({ export const EventsViewer = React.memo( EventsViewerComponent, - // eslint-disable-next-line complexity (prevProps, nextProps) => deepEqual(prevProps.browserFields, nextProps.browserFields) && prevProps.columns === nextProps.columns && deepEqual(prevProps.docValueFields, nextProps.docValueFields) && prevProps.dataProviders === nextProps.dataProviders && - deepEqual(prevProps.defaultModel, nextProps.defaultModel) && prevProps.deletedEventIds === nextProps.deletedEventIds && prevProps.end === nextProps.end && deepEqual(prevProps.filters, nextProps.filters) && - prevProps.handleCloseExpandedEvent === nextProps.handleCloseExpandedEvent && prevProps.headerFilterGroup === nextProps.headerFilterGroup && prevProps.id === nextProps.id && deepEqual(prevProps.indexPattern, nextProps.indexPattern) && diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx index 3e7395d6e399c..2570a2b6d1f37 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useEffect, useCallback } from 'react'; -import { connect, ConnectedProps, useDispatch } from 'react-redux'; +import React, { useMemo, useEffect } from 'react'; +import { connect, ConnectedProps } from 'react-redux'; import deepEqual from 'fast-deep-equal'; import styled from 'styled-components'; @@ -48,7 +48,6 @@ const StatefulEventsViewerComponent: React.FC = ({ createTimeline, columns, dataProviders, - defaultModel, deletedEventIds, deleteEventQuery, end, @@ -64,7 +63,6 @@ const StatefulEventsViewerComponent: React.FC = ({ pageFilters, query, onRuleChange, - onFlyoutCollapsed, start, scopeId, showCheckboxes, @@ -101,15 +99,6 @@ const StatefulEventsViewerComponent: React.FC = ({ }, []); const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]); - const dispatch = useDispatch(); - const handleCloseExpandedEvent = useCallback(() => { - dispatch( - onFlyoutCollapsed({ - timelineId: id, - event: {}, - }) - ); - }, [dispatch, id, onFlyoutCollapsed]); return ( <> @@ -118,7 +107,6 @@ const StatefulEventsViewerComponent: React.FC = ({ = ({ kqlMode={kqlMode} query={query} onRuleChange={onRuleChange} - handleCloseExpandedEvent={handleCloseExpandedEvent} start={start} sort={sort} utilityBar={utilityBar} @@ -157,10 +144,10 @@ const makeMapStateToProps = () => { const getInputsTimeline = inputsSelectors.getTimelineSelector(); const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); - const getEvents = timelineSelectors.getEventsByIdSelector(); + const getTimeline = timelineSelectors.getTimelineByIdSelector(); const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { const input: inputsModel.InputsRange = getInputsTimeline(state); - const events: TimelineModel = getEvents(state, id) ?? defaultModel; + const timeline: TimelineModel = getTimeline(state, id) ?? defaultModel; const { columns, dataProviders, @@ -173,12 +160,11 @@ const makeMapStateToProps = () => { kqlMode, sort, showCheckboxes, - } = events; + } = timeline; return { columns, dataProviders, - defaultModel, deletedEventIds, expandedEvent, excludedRowRendererIds, @@ -202,7 +188,6 @@ const makeMapStateToProps = () => { const mapDispatchToProps = { createTimeline: timelineActions.createTimeline, deleteEventQuery: inputsActions.deleteOneQuery, - onFlyoutCollapsed: timelineActions.toggleExpandedEvent, }; const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx index d2f16c0abaf92..1b59b174add4a 100644 --- a/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/line_clamp/index.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useRef, useState, useEffect, useCallback } from 'react'; - import { EuiButtonEmpty, EuiText } from '@elastic/eui'; +import React, { useRef, useState, useEffect, useCallback } from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx index 00cd5453e9669..2ded93377de93 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/index.tsx @@ -19,16 +19,16 @@ import { EuiFlexItem, EuiInMemoryTable, } from '@elastic/eui'; -import React, { useState, useCallback, useRef } from 'react'; +import React, { useState, useCallback, useMemo, useRef } from 'react'; import { useDispatch } from 'react-redux'; import styled from 'styled-components'; -import { RowRendererId } from '../../../../common/types/timeline'; import { State } from '../../../common/store'; -import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; - +import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { setExcludedRowRendererIds as dispatchSetExcludedRowRendererIds } from '../../store/timeline/actions'; +import { timelineSelectors } from '../../store/timeline'; +import { timelineDefaults } from '../../store/timeline/defaults'; import { renderers } from './catalog'; -import { setExcludedRowRendererIds as dispatchSetExcludedRowRendererIds } from '../../../timelines/store/timeline/actions'; import { RowRenderersBrowser } from './row_renderers_browser'; import * as i18n from './translations'; @@ -78,16 +78,14 @@ interface StatefulRowRenderersBrowserProps { timelineId: string; } -const emptyExcludedRowRendererIds: RowRendererId[] = []; - const StatefulRowRenderersBrowserComponent: React.FC = ({ timelineId, }) => { const tableRef = useRef>(); const dispatch = useDispatch(); - const excludedRowRendererIds = useShallowEqualSelector( - (state: State) => - state.timeline.timelineById[timelineId]?.excludedRowRendererIds || emptyExcludedRowRendererIds + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const excludedRowRendererIds = useDeepEqualSelector( + (state: State) => (getTimeline(state, timelineId) ?? timelineDefaults).excludedRowRendererIds ); const [show, setShow] = useState(false); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index 584350f9f7b66..3297d4d613a2b 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -11,6 +11,7 @@ import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector'; import { Ecs } from '../../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; +import { timelineSelectors } from '../../../../store/timeline'; import { AssociateNote } from '../../../notes/helpers'; import { OnPinEvent, OnRowSelected, OnUnPinEvent } from '../../events'; import { EventsTrData } from '../../styles'; @@ -85,8 +86,9 @@ export const EventColumnView = React.memo( timelineId, toggleShowNotes, }) => { + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); const { timelineType, status } = useDeepEqualSelector((state) => - pick(['timelineType', 'status'], state.timeline.timelineById[timelineId]) + pick(['timelineType', 'status'], getTimeline(state, timelineId)) ); const handlePinClicked = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index f660754085e9d..baaf9aa867d90 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -107,19 +107,19 @@ const StatefulEventComponent: React.FC = ({ const handleOnEventToggled = useCallback(() => { const eventId = event._id; const indexName = event._index!; + dispatch( timelineActions.toggleExpandedEvent({ timelineId, event: { eventId, indexName, - loading: false, }, }) ); if (timelineId === TimelineId.active) { - activeTimeline.toggleExpandedEvent({ eventId, indexName, loading: false }); + activeTimeline.toggleExpandedEvent({ eventId, indexName }); } }, [dispatch, event._id, event._index, timelineId]); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index e63b3dfd8795c..704af61b4a12f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -17,7 +17,6 @@ import { BodyComponent, StatefulBodyProps } from '.'; import { Sort } from './sort'; import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; -import { timelineDefaults } from '../../../store/timeline/defaults'; const mockSort: Sort[] = [ { @@ -65,7 +64,6 @@ describe('Body', () => { browserFields: mockBrowserFields, clearSelected: (jest.fn() as unknown) as StatefulBodyProps['clearSelected'], columnHeaders: defaultHeaders, - defaultModel: timelineDefaults, data: mockTimelineData, eventIdToNoteIds: {}, excludedRowRendererIds: [], diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index 4bc6e4ec3e256..ea397b67c31cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -14,11 +14,8 @@ import { BrowserFields } from '../../../../common/containers/source'; import { TimelineItem } from '../../../../../common/search_strategy/timeline'; import { inputsModel, State } from '../../../../common/store'; import { useManageTimeline } from '../../manage_timeline'; -import { - ColumnHeaderOptions, - SubsetTimelineModel, - TimelineModel, -} from '../../../store/timeline/model'; +import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model'; +import { timelineDefaults } from '../../../store/timeline/defaults'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { OnRowSelected, OnSelectAll } from '../events'; import { getActionsColumnWidth, getColumnHeaders } from './column_headers/helpers'; @@ -33,7 +30,6 @@ import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers'; interface OwnProps { browserFields: BrowserFields; - defaultModel: SubsetTimelineModel; data: TimelineItem[]; id: string; isEventViewer?: boolean; @@ -210,9 +206,10 @@ const makeMapStateToProps = () => { headers: ColumnHeaderOptions[], browserFields: BrowserFields ) => ColumnHeaderOptions[] = memoizeOne(getColumnHeaders); + const getTimeline = timelineSelectors.getTimelineByIdSelector(); - const mapStateToProps = (state: State, { browserFields, id, defaultModel }: OwnProps) => { - const timeline: TimelineModel = getTimeline(state, id) ?? defaultModel; + const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { + const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; const { columns, eventIdToNoteIds, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx index 4e3bdee8ff150..9895f4eda0e6c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/event_details.tsx @@ -10,10 +10,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { some } from 'lodash/fp'; import { EuiSpacer } from '@elastic/eui'; import React, { useMemo } from 'react'; import deepEqual from 'fast-deep-equal'; -import { some } from 'lodash/fp'; import { BrowserFields, DocValueFields } from '../../../common/containers/source'; import { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx index 07b1d780fb6de..5c6bcbccc8e0a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/expandable_event/index.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useState } from 'react'; - +import { find } from 'lodash/fp'; import { EuiButtonIcon, EuiTextColor, @@ -18,9 +17,9 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import { find } from 'lodash/fp'; - +import React, { useMemo, useState } from 'react'; import styled from 'styled-components'; + import { TimelineExpandedEvent } from '../../../../../common/types/timeline'; import { BrowserFields } from '../../../../common/containers/source'; import { @@ -29,8 +28,8 @@ import { View, } from '../../../../common/components/event_details/event_details'; import { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline'; -import * as i18n from './translations'; import { LineClamp } from '../../../../common/components/line_clamp'; +import * as i18n from './translations'; export type HandleOnEventClosed = () => void; interface Props { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 1df3d9813630e..b9e41846b1455 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -259,7 +259,6 @@ export const QueryTabContentComponent: React.FC = ({ activeTimeline.toggleExpandedEvent({ eventId: expandedEvent.eventId!, indexName: expandedEvent.indexName!, - loading: false, }); } }, [timelineId, onEventClosed, expandedEvent.eventId, expandedEvent.indexName]); @@ -269,10 +268,7 @@ export const QueryTabContentComponent: React.FC = ({ }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]); useEffect(() => { - if ( - !events || - (expandedEvent.eventId && !some((e) => e._id === expandedEvent.eventId, events)) - ) { + if (!events || (expandedEvent.eventId && !some(['_id', expandedEvent.eventId], events))) { handleOnEventClosed(); } }, [expandedEvent, handleOnEventClosed, events]); @@ -321,7 +317,6 @@ export const QueryTabContentComponent: React.FC = ({ className="timeline-flyout-body" > ('TOGGLE_EXPANDED_EVENT'); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts index daf57505b6baf..a92a976697eaa 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.ts @@ -177,7 +177,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState) ...state, timelineById: addTimelineNoteToEvent({ id, noteId, eventId, timelineById: state.timelineById }), })) - .case(toggleExpandedEvent, (state, { timelineId, event }) => ({ + .case(toggleExpandedEvent, (state, { timelineId, event = {} }) => ({ ...state, timelineById: { ...state.timelineById, diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts index e379caba323ca..f6386b30d112e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/selectors.ts @@ -41,8 +41,6 @@ export const getTimelines = () => timelineByIdSelector; export const getTimelineByIdSelector = () => createSelector(selectTimeline, (timeline) => timeline); -export const getEventsByIdSelector = () => createSelector(selectTimeline, (timeline) => timeline); - export const getKqlFilterQuerySelector = () => createSelector(selectTimeline, (timeline) => timeline && From 26191ac25fcab201e4a3e138c49c8f283ac622cc Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 11 Dec 2020 13:38:45 +0100 Subject: [PATCH 54/60] cleanup --- .../timelines/components/timeline/query_tab_content/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index b9e41846b1455..35d26fa2bd586 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -252,7 +252,7 @@ export const QueryTabContentComponent: React.FC = ({ }); const handleOnEventClosed = useCallback(() => { - onEventClosed({ timelineId, event: {} }); + onEventClosed({ timelineId }); setShowEventDetailsColumn(false); if (timelineId === TimelineId.active) { From 64a9371fdec23427aed2fd1508bc246eeae3c7eb Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 12 Dec 2020 01:12:50 +0800 Subject: [PATCH 55/60] summary value --- .../public/common/components/event_details/summary_view.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 58b24d0355f99..018a9142f6707 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 @@ -118,8 +118,8 @@ const getSummary = ({ } const linkValueField = item.linkField != null && data.find((d) => d.field === item.linkField); - const linkValue = getOr(null, 'originalValue', linkValueField); - const value = getOr(null, 'originalValue', field); + const linkValue = getOr(null, 'originalValue.0', linkValueField); + const value = getOr(null, 'originalValue.0', field); const category = field.category; const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; const description = { From eed7b8b97912b400f2b7ced253bbab84eb14f4dd Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 11 Dec 2020 19:52:31 +0100 Subject: [PATCH 56/60] fix showing timeline details --- .../timeline/query_tab_content/index.tsx | 17 ++---------- .../public/timelines/containers/index.tsx | 27 ++++++++++++++++++- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 35d26fa2bd586..aa3970bba5884 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -160,18 +160,6 @@ export const QueryTabContentComponent: React.FC = ({ timerangeKind, updateEventTypeAndIndexesName, }) => { - const [showEventDetailsColumn, setShowEventDetailsColumn] = useState(false); - - useEffect(() => { - // it should changed only once to true and then stay visible till the component umount - setShowEventDetailsColumn((current) => { - if (showEventDetails && !current) { - return true; - } - return current; - }); - }, [showEventDetails]); - const { browserFields, docValueFields, @@ -253,7 +241,6 @@ export const QueryTabContentComponent: React.FC = ({ const handleOnEventClosed = useCallback(() => { onEventClosed({ timelineId }); - setShowEventDetailsColumn(false); if (timelineId === TimelineId.active) { activeTimeline.toggleExpandedEvent({ @@ -271,7 +258,7 @@ export const QueryTabContentComponent: React.FC = ({ if (!events || (expandedEvent.eventId && !some(['_id', expandedEvent.eventId], events))) { handleOnEventClosed(); } - }, [expandedEvent, handleOnEventClosed, events]); + }, [expandedEvent, handleOnEventClosed, events, combinedQueries]); return ( <> @@ -346,7 +333,7 @@ export const QueryTabContentComponent: React.FC = ({ ) : null} - {showEventDetailsColumn && ( + {showEventDetails && ( <> diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 3baab2024558f..bf60225ecdade 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -5,7 +5,7 @@ */ import deepEqual from 'fast-deep-equal'; -import { noop } from 'lodash/fp'; +import { isEmpty, noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; @@ -335,5 +335,30 @@ export const useTimelineEvents = ({ timelineSearch(timelineRequest); }, [id, prevTimelineRequest, timelineRequest, timelineSearch, timerangeKind]); + /* + cleanup timeline events response when the filters were removed completely + to avoid displaying previous query results + */ + useEffect(() => { + if (isEmpty(filterQuery)) { + setTimelineResponse({ + id, + inspect: { + dsl: [], + response: [], + }, + refetch: refetchGrid, + totalCount: -1, + pageInfo: { + activePage: 0, + querySize: 0, + }, + events: [], + loadPage: wrappedLoadPage, + updatedAt: 0, + }); + } + }, [filterQuery, id, refetchGrid, wrappedLoadPage]); + return [loading, timelineResponse]; }; From 02ac29719a71e03d759ae22a31b39e8da58b2f39 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 12 Dec 2020 04:41:58 +0800 Subject: [PATCH 57/60] layout --- .../event_details/event_details.tsx | 2 +- .../components/event_details/summary_view.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 291893fe682b4..010556a3edb88 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -42,7 +42,7 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)` display: flex; flex: 1; flex-direction: column; - overflow: hidden; + overflow: scroll-y; } `; 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 018a9142f6707..13d734657acce 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 @@ -8,7 +8,6 @@ import { get, getOr } from 'lodash/fp'; import { EuiTitle, EuiDescriptionList, - EuiSpacer, EuiDescriptionListTitle, EuiDescriptionListDescription, EuiInMemoryTable, @@ -73,6 +72,10 @@ const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` } `; +const StyledEuiDescriptionList = styled(EuiDescriptionList)` + padding: 24px 4px 4px; +`; + const getTitle = (title: SummaryRow['title']) => (
{title}
@@ -190,15 +193,12 @@ export const SummaryViewComponent: React.FC<{ compressed /> {maybeRule?.note && ( - <> - - - {i18n.INVESTIGATION_GUIDE} - - - - - + + {i18n.INVESTIGATION_GUIDE} + + + + )} ); From 0a9e904a4e0349540b4a48f63de96a50234e695a Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 11 Dec 2020 22:05:42 +0100 Subject: [PATCH 58/60] fix timeline memoization --- .../public/timelines/containers/index.tsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index bf60225ecdade..9f5aeea695beb 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -72,14 +72,6 @@ export const initSortDefault = [ }, ]; -function usePreviousRequest(value: TimelineEventsAllRequestOptions | null) { - const ref = useRef(value); - useEffect(() => { - ref.current = value; - }); - return ref.current; -} - export const useTimelineEvents = ({ docValueFields, endDate, @@ -105,7 +97,7 @@ export const useTimelineEvents = ({ const [timelineRequest, setTimelineRequest] = useState( null ); - const prevTimelineRequest = usePreviousRequest(timelineRequest); + const prevTimelineRequest = useRef(null); const clearSignalsState = useCallback(() => { if (id != null && detectionsTimelineIds.some((timelineId) => timelineId === id)) { @@ -159,6 +151,7 @@ export const useTimelineEvents = ({ } let didCancel = false; const asyncSearch = async () => { + prevTimelineRequest.current = request; abortCtrl.current = new AbortController(); setLoading(true); const searchSubscription$ = data.search @@ -223,6 +216,7 @@ export const useTimelineEvents = ({ abortCtrl.current.abort(); setLoading(false); + prevTimelineRequest.current = activeTimeline.getRequest(); refetch.current = asyncSearch.bind(null, activeTimeline.getRequest()); setTimelineResponse((prevResp) => { const resp = activeTimeline.getResponse(); @@ -331,8 +325,9 @@ export const useTimelineEvents = ({ id !== TimelineId.active || timerangeKind === 'absolute' || !deepEqual(prevTimelineRequest, timelineRequest) - ) + ) { timelineSearch(timelineRequest); + } }, [id, prevTimelineRequest, timelineRequest, timelineSearch, timerangeKind]); /* From b5d1ff15f49a811b3bc2d8b9eb9db49b9cc2331f Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Sat, 12 Dec 2020 02:12:54 +0100 Subject: [PATCH 59/60] fix long query --- .../search_strategy/timeline/factory/events/details/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts index 0a011d2bfe878..e5b70e22e90b9 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts @@ -27,7 +27,7 @@ export const timelineEventsDetails: SecuritySolutionTimelineFactory ): Promise => { const { indexName, eventId, docValueFields = [] } = options; - const fieldsData = cloneDeep(response.rawResponse.hits.hits[0].fields ?? {}); + const fieldsData = cloneDeep(response.rawResponse.hits.hits[0]?.fields ?? {}); const hitsData = cloneDeep(response.rawResponse.hits.hits[0] ?? {}); delete hitsData._source; delete hitsData.fields; From b7d38435b82cc55e499ce1e57469fdba55a0def5 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Sat, 12 Dec 2020 10:13:55 +0800 Subject: [PATCH 60/60] styling --- .../public/common/components/event_details/event_details.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 010556a3edb88..291893fe682b4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -42,7 +42,7 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)` display: flex; flex: 1; flex-direction: column; - overflow: scroll-y; + overflow: hidden; } `;