From c4ab6e28733f0e54fe8a369e10085e40bc8869e0 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 21 Apr 2020 17:12:14 -0400 Subject: [PATCH 01/74] cleanup: move submenus into a general component --- .../resolver/view/process_event_dot.tsx | 121 ++++++++++++------ 1 file changed, 82 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 3201e83164dba6..9e246d58cb25da 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { @@ -59,43 +59,80 @@ const nodeAssets = { }, }; -const ChildEventsButton = React.memo(() => { - return ( - ) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - }, [])} - color="ghost" - size="s" - iconType="arrowDown" - iconSide="right" - tabIndex={-1} - > - {i18n.translate('xpack.endpoint.resolver.relatedEvents', { - defaultMessage: 'Events', - })} - - ); -}); +const subMenuAssets = { + relatedAlerts: { + title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { + defaultMessage: 'Related Alerts', + }), + }, + relatedEvents: { + title: i18n.translate('xpack.endpoint.resolver.relatedEvents', { + defaultMessage: 'Events', + }), + }, +}; -const RelatedAlertsButton = React.memo(() => { - return ( - ) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - }, [])} - color="ghost" - size="s" - tabIndex={-1} - > - {i18n.translate('xpack.endpoint.resolver.relatedAlerts', { - defaultMessage: 'Related Alerts', - })} - - ); -}); +const NodeSubMenu = React.memo( + ({ + menuTitle, + menuAction, + optionsWithActions, + }: { menuTitle: string } & ( + | { + menuAction?: undefined; + optionsWithActions: Array<{ + optionTitle: string; + action: () => unknown; + prefix?: number | JSX.Element; + }>; + } + | { menuAction: () => unknown; optionsWithActions?: undefined } + )) => { + if (!optionsWithActions && typeof menuAction === 'function') { + /** + * When called with a `menuAction` + * Render without dropdown and call the supplied action when host button is clicked + */ + return ( + ) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + menuAction(); + }, + [menuAction] + )} + color="ghost" + size="s" + tabIndex={-1} + > + {menuTitle} + + ); + } else { + /** + * When called with a set of `optionsWithActions`: + * Render with a panel of options that appear when the menu host button is clicked + */ + return ( + ) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + }, [])} + color="ghost" + size="s" + iconType="arrowDown" + iconSide="right" + tabIndex={-1} + > + {menuTitle} + + ); + } + } +); /** * An artefact that represents a process node. @@ -357,10 +394,16 @@ export const ProcessEventDot = styled( {magFactorX >= 2 && ( - + {} }]} + /> - + {}} + /> )} From cfd95e826ca5742f21fe4a913dcdd04cfc999eac Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 21 Apr 2020 17:22:05 -0400 Subject: [PATCH 02/74] remove unnecessary useState import --- .../public/embeddables/resolver/view/process_event_dot.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 9e246d58cb25da..6ff1fdd56e0d36 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { From 2c7b279f51de2e11a536cfaba39bcfc143024c97 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 22 Apr 2020 12:55:15 -0400 Subject: [PATCH 03/74] referral for help --- .../resolver/view/process_event_dot.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 6ff1fdd56e0d36..69b2870e052cca 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { @@ -13,6 +13,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, + EuiSelectable, } from '@elastic/eui'; import { useSelector } from 'react-redux'; import { applyMatrix3 } from '../lib/vector2'; @@ -111,11 +112,25 @@ const NodeSubMenu = React.memo( ); } else { + + const OptionList = ()=>{ + const [options, setOptions] = useState([{ label: 'abc'}, { label: 'def'}]); + return useMemo(()=>( + { console.log('reset options'); setOptions(newOptions); }} + > + {list=>list} + + ),[options]) + } /** * When called with a set of `optionsWithActions`: * Render with a panel of options that appear when the menu host button is clicked */ return ( + <> ) => { clickEvent.preventDefault(); @@ -128,11 +143,14 @@ const NodeSubMenu = React.memo( tabIndex={-1} > {menuTitle} + + + ); } } -); +) /** * An artefact that represents a process node. From 5aef930a1260cb1e3170d359eb2d318088c39b96 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 23 Apr 2020 10:00:49 -0400 Subject: [PATCH 04/74] Use EUISelectable for options list --- .../resolver/view/process_event_dot.tsx | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 69b2870e052cca..d018db0db22cb1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -73,6 +73,26 @@ const subMenuAssets = { }, }; +const OptionList = React.memo(() => { + const [options, setOptions] = useState([{ label: 'abc' }, { label: 'def' }]); + return useMemo( + () => ( + { + // eslint-disable-next-line + console.log('reset options'); + setOptions(newOptions); + }} + > + {list => list} + + ), + [options] + ); +}); + const NodeSubMenu = React.memo( ({ menuTitle, @@ -112,45 +132,31 @@ const NodeSubMenu = React.memo( ); } else { - - const OptionList = ()=>{ - const [options, setOptions] = useState([{ label: 'abc'}, { label: 'def'}]); - return useMemo(()=>( - { console.log('reset options'); setOptions(newOptions); }} - > - {list=>list} - - ),[options]) - } /** * When called with a set of `optionsWithActions`: * Render with a panel of options that appear when the menu host button is clicked */ return ( <> - ) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - }, [])} - color="ghost" - size="s" - iconType="arrowDown" - iconSide="right" - tabIndex={-1} - > - {menuTitle} - - - + ) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + }, [])} + color="ghost" + size="s" + iconType="arrowDown" + iconSide="right" + tabIndex={-1} + > + {menuTitle} + + ); } } -) +); /** * An artefact that represents a process node. From 8ea107d7d90743c5375fe6a8768e03e2066f5ecb Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 23 Apr 2020 17:28:17 -0400 Subject: [PATCH 05/74] adding EUISelectable --- .../resolver/view/process_event_dot.tsx | 142 ++++++++++-------- 1 file changed, 82 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index d018db0db22cb1..77e836d2082a6c 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -73,25 +73,37 @@ const subMenuAssets = { }, }; -const OptionList = React.memo(() => { - const [options, setOptions] = useState([{ label: 'abc' }, { label: 'def' }]); - return useMemo( - () => ( - { - // eslint-disable-next-line - console.log('reset options'); - setOptions(newOptions); - }} - > - {list => list} - - ), - [options] - ); -}); +type ResolverSubmenuOptionList = Array<{ + optionTitle: string; + action: () => unknown; + prefix?: number | JSX.Element; +}>; + +const OptionList = React.memo( + ({ subMenuOptions }: { subMenuOptions: ResolverSubmenuOptionList }) => { + const selectableOptions = subMenuOptions.map((opt, index) => { + return { + label: opt.optionTitle, + }; + }); + const [options, setOptions] = useState(selectableOptions); + return useMemo( + () => ( + { + setOptions(newOptions); + }} + listProps={{ showIcons: true, bordered: true }} + > + {list => list} + + ), + [options] + ); + } +); const NodeSubMenu = React.memo( ({ @@ -101,60 +113,57 @@ const NodeSubMenu = React.memo( }: { menuTitle: string } & ( | { menuAction?: undefined; - optionsWithActions: Array<{ - optionTitle: string; - action: () => unknown; - prefix?: number | JSX.Element; - }>; + optionsWithActions: ResolverSubmenuOptionList; } | { menuAction: () => unknown; optionsWithActions?: undefined } )) => { - if (!optionsWithActions && typeof menuAction === 'function') { + const [menuIsOpen, setMenuOpen] = useState(false); + const handleMenuOpenClick = useCallback( + (clickEvent: React.MouseEvent) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + setMenuOpen(!menuIsOpen); + }, + [menuIsOpen] + ); + const handleMenuActionClick = useCallback( + (clickEvent: React.MouseEvent) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + if (typeof menuAction === 'function') menuAction(); + }, + [menuAction] + ); + if (!optionsWithActions) { /** * When called with a `menuAction` * Render without dropdown and call the supplied action when host button is clicked */ return ( + + {menuTitle} + + ); + } + /** + * When called with a set of `optionsWithActions`: + * Render with a panel of options that appear when the menu host button is clicked + */ + return ( + <> ) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - menuAction(); - }, - [menuAction] - )} + onClick={handleMenuOpenClick} color="ghost" size="s" + iconType="arrowDown" + iconSide="right" tabIndex={-1} > {menuTitle} - ); - } else { - /** - * When called with a set of `optionsWithActions`: - * Render with a panel of options that appear when the menu host button is clicked - */ - return ( - <> - ) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - }, [])} - color="ghost" - size="s" - iconType="arrowDown" - iconSide="right" - tabIndex={-1} - > - {menuTitle} - - - - ); - } + {menuIsOpen && } + + ); } ); @@ -420,7 +429,10 @@ export const ProcessEventDot = styled( {} }]} + optionsWithActions={[ + { optionTitle: '10 DNS', action: () => {} }, + { optionTitle: '10 File', action: () => {} }, + ]} /> @@ -466,10 +478,20 @@ export const ProcessEventDot = styled( transition-duration: 1s; stroke-dashoffset: 0; } + + & .euiSelectableListItem { + background-color: black; + } + & .euiSelectableListItem path { + fill: white; + } + & .euiSelectableListItem__text { + color: white; + } `; const processTypeToCube: Record = { - processCreated: 'terminatedProcessCube', + processCreated: 'runningProcessCube', processRan: 'runningProcessCube', processTerminated: 'terminatedProcessCube', unknownProcessEvent: 'runningProcessCube', From ab588234164114cdf1ffdf4e6c2a1c4aac183174 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 27 Apr 2020 12:46:25 -0400 Subject: [PATCH 06/74] user-select off to fix bug Jonny found --- .../plugins/endpoint/public/embeddables/resolver/view/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx index 36155ece57a9c4..28e5fd6b08913e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx @@ -26,6 +26,7 @@ const StyledPanel = styled(Panel)` overflow: auto; width: 25em; max-width: 50%; + user-select: none; `; const StyledGraphControls = styled(GraphControls)` From 42c9a3b81d412bc099a6d10cf3495353e4d01881 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 27 Apr 2020 12:46:53 -0400 Subject: [PATCH 07/74] Revert "user-select off to fix bug Jonny found" This reverts commit ab588234164114cdf1ffdf4e6c2a1c4aac183174. --- .../plugins/endpoint/public/embeddables/resolver/view/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx index 28e5fd6b08913e..36155ece57a9c4 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx @@ -26,7 +26,6 @@ const StyledPanel = styled(Panel)` overflow: auto; width: 25em; max-width: 50%; - user-select: none; `; const StyledGraphControls = styled(GraphControls)` From f166d6dd44f0ce3d9c11787ef5425ac3e6044200 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 27 Apr 2020 12:48:04 -0400 Subject: [PATCH 08/74] straighten corners on menu open to match mocks --- .../resolver/view/process_event_dot.tsx | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 77e836d2082a6c..1dbf0ce94b536a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -105,12 +105,13 @@ const OptionList = React.memo( } ); -const NodeSubMenu = React.memo( +const NodeSubMenu = styled(React.memo( ({ menuTitle, menuAction, optionsWithActions, - }: { menuTitle: string } & ( + className, + }: { menuTitle: string; className?: string } & ( | { menuAction?: undefined; optionsWithActions: ResolverSubmenuOptionList; @@ -140,9 +141,11 @@ const NodeSubMenu = React.memo( * Render without dropdown and call the supplied action when host button is clicked */ return ( - - {menuTitle} - +
+ + {menuTitle} + +
); } /** @@ -150,22 +153,32 @@ const NodeSubMenu = React.memo( * Render with a panel of options that appear when the menu host button is clicked */ return ( - <> +
{menuTitle} {menuIsOpen && } - +
); } -); +))` + margin: 0; + padding: 0; + border: none; + display: flex; + flex-flow: column; + &.is-open .euiButton { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } +` /** * An artefact that represents a process node. @@ -479,6 +492,10 @@ export const ProcessEventDot = styled( stroke-dashoffset: 0; } + & .euiSelectableList-bordered { + border-top-right-radius: 0px; + border-top-left-radius: 0px; + } & .euiSelectableListItem { background-color: black; } From 6cb887b820cd2fc0ccd5da036b2a87603a1fac8b Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 27 Apr 2020 16:13:34 -0400 Subject: [PATCH 09/74] adding type information for related events --- .../public/embeddables/resolver/types.ts | 7 ++ .../resolver/view/process_event_dot.tsx | 114 +++++++++--------- 2 files changed, 65 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index d370bda0d18424..aca1eef3de24e8 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -130,6 +130,13 @@ export type CameraState = { } ); +export const waitingForRelatedEventData = Symbol('The app has requested related event data for this entity ID, but has not yet receieved it'); +export type RelatedEventDataEntry = object; +export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { + stats: object; +} +export type RelatedEventData = Record; + /** * State for `data` reducer which handles receiving Resolver data from the backend. */ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 1dbf0ce94b536a..422297d7824750 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -105,70 +105,72 @@ const OptionList = React.memo( } ); -const NodeSubMenu = styled(React.memo( - ({ - menuTitle, - menuAction, - optionsWithActions, - className, - }: { menuTitle: string; className?: string } & ( - | { - menuAction?: undefined; - optionsWithActions: ResolverSubmenuOptionList; +const NodeSubMenu = styled( + React.memo( + ({ + menuTitle, + menuAction, + optionsWithActions, + className, + }: { menuTitle: string; className?: string } & ( + | { + menuAction?: undefined; + optionsWithActions: ResolverSubmenuOptionList; + } + | { menuAction: () => unknown; optionsWithActions?: undefined } + )) => { + const [menuIsOpen, setMenuOpen] = useState(false); + const handleMenuOpenClick = useCallback( + (clickEvent: React.MouseEvent) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + setMenuOpen(!menuIsOpen); + }, + [menuIsOpen] + ); + const handleMenuActionClick = useCallback( + (clickEvent: React.MouseEvent) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + if (typeof menuAction === 'function') menuAction(); + }, + [menuAction] + ); + if (!optionsWithActions) { + /** + * When called with a `menuAction` + * Render without dropdown and call the supplied action when host button is clicked + */ + return ( +
+ + {menuTitle} + +
+ ); } - | { menuAction: () => unknown; optionsWithActions?: undefined } - )) => { - const [menuIsOpen, setMenuOpen] = useState(false); - const handleMenuOpenClick = useCallback( - (clickEvent: React.MouseEvent) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - setMenuOpen(!menuIsOpen); - }, - [menuIsOpen] - ); - const handleMenuActionClick = useCallback( - (clickEvent: React.MouseEvent) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - if (typeof menuAction === 'function') menuAction(); - }, - [menuAction] - ); - if (!optionsWithActions) { /** - * When called with a `menuAction` - * Render without dropdown and call the supplied action when host button is clicked + * When called with a set of `optionsWithActions`: + * Render with a panel of options that appear when the menu host button is clicked */ return ( -
- +
+ {menuTitle} + {menuIsOpen && }
); } - /** - * When called with a set of `optionsWithActions`: - * Render with a panel of options that appear when the menu host button is clicked - */ - return ( -
- - {menuTitle} - - {menuIsOpen && } -
- ); - } -))` + ) +)` margin: 0; padding: 0; border: none; @@ -178,7 +180,7 @@ const NodeSubMenu = styled(React.memo( border-bottom-left-radius: 0; border-bottom-right-radius: 0; } -` +`; /** * An artefact that represents a process node. From 36fe414bc2a1cd175f7969af4a4f16e965c637cb Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 27 Apr 2020 18:12:14 -0400 Subject: [PATCH 10/74] refine type information for related events --- .../embeddables/resolver/store/actions.ts | 14 ++++- .../embeddables/resolver/store/data/action.ts | 13 ++++- .../public/embeddables/resolver/types.ts | 51 +++++++++++++++++-- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index a26f43e1f8cc08..f21f12ee3ba47c 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -7,6 +7,7 @@ import { CameraAction } from './camera'; import { DataAction } from './data'; import { ResolverEvent } from '../../../../common/types'; + /** * When the user wants to bring a process node front-and-center on the map. */ @@ -44,6 +45,16 @@ interface AppRequestedResolverData { readonly type: 'appRequestedResolverData'; } + +/** + * The action dispatched when the app requests related event data for one or more + * subjects (whose ids should be included as an array @ `payload`) + */ +interface AppRequestedRelatedEventData { + readonly type: 'appRequestedRelatedEventData'; + readonly payload: Array; +} + /** * When the user switches the "active descendant" of the Resolver. * The "active descendant" (from the point of view of the parent element) @@ -84,4 +95,5 @@ export type ResolverAction = | UserChangedSelectedEvent | AppRequestedResolverData | UserFocusedOnResolverNode - | UserSelectedResolverNode; + | UserSelectedResolverNode + | AppRequestedRelatedEventData; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index 373afa89921dcd..bf6fc61b5f411a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -17,4 +17,15 @@ interface ServerReturnedResolverData { }; } -export type DataAction = ServerReturnedResolverData; +/** + * Will occur when a request for related event data is fulfilled. + */ +interface ServerReturnedRelatedEventData { + readonly type: 'serverReturnedRelatedEventData'; + readonly payload: { + //TODO: add type information when /related API is finalized + readonly data: object + }; +} + +export type DataAction = ServerReturnedResolverData | ServerReturnedRelatedEventData; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index aca1eef3de24e8..dcc6d49794f4d8 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -130,12 +130,53 @@ export type CameraState = { } ); -export const waitingForRelatedEventData = Symbol('The app has requested related event data for this entity ID, but has not yet receieved it'); -export type RelatedEventDataEntry = object; +/** + * This is the current list of known related event types. It has been transcribed from the + * v0 Endgame app. + */ +export type RelatedEventType = + | "Network" + | "File" + | "DNS" + | "Registry" + | "Powershell" + | "WMI" + | "API" + | "CLR" + | "Image Load" + | "User" +/** + * This symbol indicates that the app is waiting for related event data for the subject + * of any particular request. + */ +export const waitingForRelatedEventData = Symbol( + 'The app has requested related event data for this entity ID, but has not yet receieved it' +); +/** + * This represents all the raw data (sans statistics, metadata, etc.) + * about a particular subject's related events + */ +export type RelatedEventDataEntry = { + related_events: { + related_event_id: string, + related_event_type: RelatedEventType, + }[] +}; +/** + * This represents the raw related events data enhanced with statistics + * (e.g. counts of items grouped by their related event types) + */ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { - stats: object; -} -export type RelatedEventData = Record; + stats: Partial> +}; +/** + * This represents a Record that will return either a `RelatedEventDataEntryWithStats` + * or a `waitingForRelatedEventData` symbol when called with a unique event id. + */ +export type RelatedEventData = Record< + string, + RelatedEventDataEntryWithStats | typeof waitingForRelatedEventData +>; /** * State for `data` reducer which handles receiving Resolver data from the backend. From 2429b4ba7565ca354f9d3262b5dd4fec1555df57 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 28 Apr 2020 17:04:57 -0400 Subject: [PATCH 11/74] Node option submenus take waiting prop --- .../embeddables/resolver/store/actions.ts | 4 +- .../embeddables/resolver/store/data/action.ts | 4 +- .../public/embeddables/resolver/types.ts | 40 +++++++++---------- .../resolver/view/process_event_dot.tsx | 15 +++++-- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index f21f12ee3ba47c..5d4598348bf471 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -7,7 +7,6 @@ import { CameraAction } from './camera'; import { DataAction } from './data'; import { ResolverEvent } from '../../../../common/types'; - /** * When the user wants to bring a process node front-and-center on the map. */ @@ -45,14 +44,13 @@ interface AppRequestedResolverData { readonly type: 'appRequestedResolverData'; } - /** * The action dispatched when the app requests related event data for one or more * subjects (whose ids should be included as an array @ `payload`) */ interface AppRequestedRelatedEventData { readonly type: 'appRequestedRelatedEventData'; - readonly payload: Array; + readonly payload: string[]; } /** diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index bf6fc61b5f411a..53ca011d410bc2 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -23,8 +23,8 @@ interface ServerReturnedResolverData { interface ServerReturnedRelatedEventData { readonly type: 'serverReturnedRelatedEventData'; readonly payload: { - //TODO: add type information when /related API is finalized - readonly data: object + // TODO: add type information when /related API is finalized + readonly data: object; }; } diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index dcc6d49794f4d8..f453ecc63939f1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -131,20 +131,20 @@ export type CameraState = { ); /** - * This is the current list of known related event types. It has been transcribed from the + * This is the current list of known related event types. It has been transcribed from the * v0 Endgame app. */ export type RelatedEventType = - | "Network" - | "File" - | "DNS" - | "Registry" - | "Powershell" - | "WMI" - | "API" - | "CLR" - | "Image Load" - | "User" + | 'Network' + | 'File' + | 'DNS' + | 'Registry' + | 'Powershell' + | 'WMI' + | 'API' + | 'CLR' + | 'Image Load' + | 'User'; /** * This symbol indicates that the app is waiting for related event data for the subject * of any particular request. @@ -153,25 +153,25 @@ export const waitingForRelatedEventData = Symbol( 'The app has requested related event data for this entity ID, but has not yet receieved it' ); /** - * This represents all the raw data (sans statistics, metadata, etc.) + * This represents all the raw data (sans statistics, metadata, etc.) * about a particular subject's related events */ -export type RelatedEventDataEntry = { - related_events: { - related_event_id: string, - related_event_type: RelatedEventType, - }[] -}; +export interface RelatedEventDataEntry { + related_events: Array<{ + related_event_id: string; + related_event_type: RelatedEventType; + }>; +} /** * This represents the raw related events data enhanced with statistics * (e.g. counts of items grouped by their related event types) */ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { - stats: Partial> + stats: Partial>; }; /** * This represents a Record that will return either a `RelatedEventDataEntryWithStats` - * or a `waitingForRelatedEventData` symbol when called with a unique event id. + * or a `waitingForRelatedEventData` symbol when called with a unique event id. */ export type RelatedEventData = Record< string, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 422297d7824750..4de9cecbbbe994 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -17,7 +17,13 @@ import { } from '@elastic/eui'; import { useSelector } from 'react-redux'; import { applyMatrix3 } from '../lib/vector2'; -import { Vector2, Matrix3, AdjacentProcessMap, ResolverProcessType } from '../types'; +import { + Vector2, + Matrix3, + AdjacentProcessMap, + ResolverProcessType, + waitingForRelatedEventData, +} from '../types'; import { SymbolIds, NamedColors } from './defs'; import { ResolverEvent } from '../../../../common/types'; import { useResolverDispatch } from './use_resolver_dispatch'; @@ -115,7 +121,7 @@ const NodeSubMenu = styled( }: { menuTitle: string; className?: string } & ( | { menuAction?: undefined; - optionsWithActions: ResolverSubmenuOptionList; + optionsWithActions: ResolverSubmenuOptionList | typeof waitingForRelatedEventData; } | { menuAction: () => unknown; optionsWithActions?: undefined } )) => { @@ -165,7 +171,10 @@ const NodeSubMenu = styled( > {menuTitle}
- {menuIsOpen && } + {menuIsOpen && + (optionsWithActions === waitingForRelatedEventData ? null : ( + + ))}
); } From 8c454d4265b566b8204508536a05dd93313e1080 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 28 Apr 2020 17:51:19 -0400 Subject: [PATCH 12/74] merge upstream --- .../public/embeddables/resolver/store/data/action.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index d4a8bc1a68a269..15c47554b6fbab 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -26,4 +26,7 @@ interface ServerReturnedRelatedEventData { }; } -export type DataAction = ServerReturnedResolverData | ServerFailedToReturnResolverData | ServerReturnedRelatedEventData; +export type DataAction = + | ServerReturnedResolverData + | ServerFailedToReturnResolverData + | ServerReturnedRelatedEventData; From 747cd6e3a788b5f9006b5416f185a9a7ce8f604d Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 29 Apr 2020 10:54:35 -0400 Subject: [PATCH 13/74] stub selector for related events --- .../resolver/store/data/selectors.ts | 21 +++++++++++++++++++ .../public/embeddables/resolver/types.ts | 10 ++++----- .../resolver/view/process_event_dot.tsx | 21 +++++++++++++++---- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 59ee4b3b875055..31d773cd16cb0e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -14,6 +14,8 @@ import { ProcessWithWidthMetadata, Matrix3, AdjacentProcessMap, + RelatedEventData, + RelatedEventType, } from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { Vector2 } from '../../types'; @@ -405,6 +407,25 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in return indexedProcessTreeFactory(graphableProcesses); }); +export const relatedEvents = createSelector(graphableProcesses, function getRelatedEvents( + /* eslint-disable no-shadow */ + graphableProcesses + /* eslint-enable no-shadow */ +) { + const tempType: RelatedEventType = 'DNS'; + const eventsRelatedByProcess: RelatedEventData = new WeakMap(); + /* eslint-disable no-shadow */ + return graphableProcesses.reduce((relatedEvents, graphableProcess) => { + /* eslint-enable no-shadow */ + const tempEntry = { + related_events: [{ related_event: graphableProcess, related_event_type: tempType }], + stats: { DNS: 23 }, + }; + relatedEvents.set(graphableProcess, tempEntry); + return relatedEvents; + }, eventsRelatedByProcess); +}); + export const processAdjacencies = createSelector( indexedProcessTree, graphableProcesses, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 1ec611c1de273a..793f31803eaf12 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -158,7 +158,7 @@ export const waitingForRelatedEventData = Symbol( */ export interface RelatedEventDataEntry { related_events: Array<{ - related_event_id: string; + related_event: ResolverEvent; related_event_type: RelatedEventType; }>; } @@ -170,11 +170,11 @@ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { stats: Partial>; }; /** - * This represents a Record that will return either a `RelatedEventDataEntryWithStats` - * or a `waitingForRelatedEventData` symbol when called with a unique event id. + * This represents a Map that will return either a `RelatedEventDataEntryWithStats` + * or a `waitingForRelatedEventData` symbol when referenced unique event. */ -export type RelatedEventData = Record< - string, +export type RelatedEventData = WeakMap< + ResolverEvent, RelatedEventDataEntryWithStats | typeof waitingForRelatedEventData >; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 4de9cecbbbe994..b03bbb4f6e16b9 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -67,6 +67,9 @@ const nodeAssets = { }; const subMenuAssets = { + initialMenuStatus: Symbol( + 'The state of a Resolver submenu before it has been opened or requested data.' + ), relatedAlerts: { title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { defaultMessage: 'Related Alerts', @@ -121,7 +124,10 @@ const NodeSubMenu = styled( }: { menuTitle: string; className?: string } & ( | { menuAction?: undefined; - optionsWithActions: ResolverSubmenuOptionList | typeof waitingForRelatedEventData; + optionsWithActions: + | ResolverSubmenuOptionList + | typeof subMenuAssets.initialMenuStatus + | typeof waitingForRelatedEventData; } | { menuAction: () => unknown; optionsWithActions?: undefined } )) => { @@ -130,9 +136,14 @@ const NodeSubMenu = styled( (clickEvent: React.MouseEvent) => { clickEvent.preventDefault(); clickEvent.stopPropagation(); - setMenuOpen(!menuIsOpen); + if (optionsWithActions === waitingForRelatedEventData) { + // dispatch action + setMenuOpen(true); + } else { + setMenuOpen(!menuIsOpen); + } }, - [menuIsOpen] + [menuIsOpen, optionsWithActions] ); const handleMenuActionClick = useCallback( (clickEvent: React.MouseEvent) => { @@ -172,7 +183,9 @@ const NodeSubMenu = styled( {menuTitle} {menuIsOpen && - (optionsWithActions === waitingForRelatedEventData ? null : ( + (typeof optionsWithActions === 'symbol' ? ( + 'loading' + ) : ( ))} From 72665e00602e14d16d8e9efa3c4b672cc18f50ab Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 29 Apr 2020 11:23:56 -0400 Subject: [PATCH 14/74] provision component with selected relatedEvents --- .../embeddables/resolver/store/data/selectors.ts | 2 +- .../public/embeddables/resolver/store/selectors.ts | 2 ++ .../endpoint/public/embeddables/resolver/types.ts | 10 ++++++---- .../public/embeddables/resolver/view/index.tsx | 3 ++- .../embeddables/resolver/view/process_event_dot.tsx | 8 ++++++++ 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 31d773cd16cb0e..8d42fe77838f60 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -416,7 +416,7 @@ export const relatedEvents = createSelector(graphableProcesses, function getRela const eventsRelatedByProcess: RelatedEventData = new WeakMap(); /* eslint-disable no-shadow */ return graphableProcesses.reduce((relatedEvents, graphableProcess) => { - /* eslint-enable no-shadow */ + /* eslint-enable no-shadow */ const tempEntry = { related_events: [{ related_event: graphableProcess, related_event_type: tempType }], stats: { DNS: 23 }, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts index 7d09d90881da94..6168752983bb42 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts @@ -60,6 +60,8 @@ export const processAdjacencies = composeSelectors( dataSelectors.processAdjacencies ); +export const relatedEvents = composeSelectors(dataStateSelector, dataSelectors.relatedEvents); + /** * Returns the id of the "current" tree node (fake-focused) */ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 793f31803eaf12..ab06beed75b128 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -169,14 +169,16 @@ export interface RelatedEventDataEntry { export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { stats: Partial>; }; + +export type RelatedEventEntryWithStatsOrWaiting = + | RelatedEventDataEntryWithStats + | typeof waitingForRelatedEventData; + /** * This represents a Map that will return either a `RelatedEventDataEntryWithStats` * or a `waitingForRelatedEventData` symbol when referenced unique event. */ -export type RelatedEventData = WeakMap< - ResolverEvent, - RelatedEventDataEntryWithStats | typeof waitingForRelatedEventData ->; +export type RelatedEventData = WeakMap; /** * State for `data` reducer which handles receiving Resolver data from the backend. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx index 2e7ca65c92dc1e..1746a6c7e826d0 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx @@ -57,7 +57,7 @@ export const Resolver = styled( const dispatch: (action: ResolverAction) => unknown = useDispatch(); const { processToAdjacencyMap } = useSelector(selectors.processAdjacencies); - + const relatedEvents = useSelector(selectors.relatedEvents); const { projectionMatrix, ref, onMouseDown } = useCamera(); const isLoading = useSelector(selectors.isLoading); const hasError = useSelector(selectors.hasError); @@ -116,6 +116,7 @@ export const Resolver = styled( projectionMatrix={projectionMatrix} event={processEvent} adjacentNodeMap={adjacentNodeMap} + relatedEvents={relatedEvents.get(processEvent)!} /> ); })} diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index b03bbb4f6e16b9..e8103ede63f2ba 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -23,6 +23,8 @@ import { AdjacentProcessMap, ResolverProcessType, waitingForRelatedEventData, + RelatedEventDataEntryWithStats, + RelatedEventEntryWithStatsOrWaiting, } from '../types'; import { SymbolIds, NamedColors } from './defs'; import { ResolverEvent } from '../../../../common/types'; @@ -215,6 +217,7 @@ export const ProcessEventDot = styled( event, projectionMatrix, adjacentNodeMap, + relatedEvents, }: { /** * A `className` string provided by `styled` @@ -236,6 +239,11 @@ export const ProcessEventDot = styled( * map of what nodes are "adjacent" to this one in "up, down, previous, next" directions */ adjacentNodeMap: AdjacentProcessMap; + /** + * A collection of events related to the current node and statistics (e.g. counts indexed by event type) + * to provide the user some visibility regarding the contents thereof. + */ + relatedEvents: RelatedEventEntryWithStatsOrWaiting; }) => { /** * Convert the position, which is in 'world' coordinates, to screen coordinates. From a98cd7a5e027824bb19bebc789e0df5ecb8c3ed5 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 29 Apr 2020 14:58:33 -0400 Subject: [PATCH 15/74] shape reducder --- .../embeddables/resolver/store/data/action.ts | 6 ++-- .../resolver/store/data/reducer.ts | 30 ++++++++++++++++++- .../public/embeddables/resolver/types.ts | 9 ++++++ .../resolver/view/process_event_dot.tsx | 1 - 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index 15c47554b6fbab..33f63ec3278622 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -5,6 +5,7 @@ */ import { ResolverEvent } from '../../../../../common/types'; +import { RelatedEventDataEntry } from '../../types'; interface ServerReturnedResolverData { readonly type: 'serverReturnedResolverData'; @@ -20,10 +21,7 @@ interface ServerFailedToReturnResolverData { */ interface ServerReturnedRelatedEventData { readonly type: 'serverReturnedRelatedEventData'; - readonly payload: { - // TODO: add type information when /related API is finalized - readonly data: object; - }; + readonly payload: Map; } export type DataAction = diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index fc307002819a92..7f959278d36f3a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -5,13 +5,19 @@ */ import { Reducer } from 'redux'; -import { DataState, ResolverAction } from '../../types'; +import { + DataState, + ResolverAction, + resultIsEnrichedWithRelatedEventInfo, + RelatedEventDataEntryWithStats, +} from '../../types'; function initialState(): DataState { return { results: [], isLoading: false, hasError: false, + [resultIsEnrichedWithRelatedEventInfo]: new WeakMap(), }; } @@ -23,6 +29,28 @@ export const dataReducer: Reducer = (state = initialS isLoading: false, hasError: false, }; + } else if (action.type === 'serverReturnedRelatedEventData') { + /** + * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 + * When this data is inlined with results, there won't be a need for this. + */ + const statsMap = state[resultIsEnrichedWithRelatedEventInfo]; + if (statsMap && typeof statsMap?.set === 'function') { + for (const updatedEvent of action.payload.keys()) { + const newStatsEntry = action.payload.get(updatedEvent); + + if (newStatsEntry) { + // do stats + const statsForEntry = { DNS: 32, File: 12 }; + const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign( + newStatsEntry, + { stats: statsForEntry } + ); + statsMap.set(updatedEvent, newRelatedEventStats); + } + } + } + return Object.assign(state, { [resultIsEnrichedWithRelatedEventInfo]: statsMap }); } else if (action.type === 'appRequestedResolverData') { return { ...state, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index ab06beed75b128..0f1fd07b475f71 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -145,6 +145,14 @@ export type RelatedEventType = | 'CLR' | 'Image Load' | 'User'; +/** + * This symbol is used to tag results with Related event info + * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 + * When this data is inlined with results, there won't be a need for this. + */ +export const resultIsEnrichedWithRelatedEventInfo = Symbol( + 'The result (e.g. a ResolverEvent) is enriched with information and stats about related events' +); /** * This symbol indicates that the app is waiting for related event data for the subject * of any particular request. @@ -187,6 +195,7 @@ export interface DataState { readonly results: readonly ResolverEvent[]; isLoading: boolean; hasError: boolean; + [resultIsEnrichedWithRelatedEventInfo]?: RelatedEventData; } export type Vector2 = readonly [number, number]; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index e8103ede63f2ba..77fc4d80cf5f72 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -23,7 +23,6 @@ import { AdjacentProcessMap, ResolverProcessType, waitingForRelatedEventData, - RelatedEventDataEntryWithStats, RelatedEventEntryWithStatsOrWaiting, } from '../types'; import { SymbolIds, NamedColors } from './defs'; From 6e75074577417bc15323a0259db9f3ea9d2f7013 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 29 Apr 2020 15:45:14 -0400 Subject: [PATCH 16/74] adjust reducer for consistency --- .../endpoint/public/embeddables/resolver/store/data/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 7f959278d36f3a..85a22eda4214d1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -50,7 +50,7 @@ export const dataReducer: Reducer = (state = initialS } } } - return Object.assign(state, { [resultIsEnrichedWithRelatedEventInfo]: statsMap }); + return { ...state, [resultIsEnrichedWithRelatedEventInfo]: statsMap }; } else if (action.type === 'appRequestedResolverData') { return { ...state, From 5529b7ecc93a157457953fa84a37c483194bb170 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 30 Apr 2020 11:32:45 -0400 Subject: [PATCH 17/74] adding request logic to middleware --- .../resolver/store/data/reducer.ts | 8 ++--- .../embeddables/resolver/store/middleware.ts | 31 ++++++++++++++++++- .../public/embeddables/resolver/types.ts | 4 +-- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 85a22eda4214d1..c50cdeb336035b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -8,7 +8,7 @@ import { Reducer } from 'redux'; import { DataState, ResolverAction, - resultIsEnrichedWithRelatedEventInfo, + resultsEnrichedWithRelatedEventInfo, RelatedEventDataEntryWithStats, } from '../../types'; @@ -17,7 +17,7 @@ function initialState(): DataState { results: [], isLoading: false, hasError: false, - [resultIsEnrichedWithRelatedEventInfo]: new WeakMap(), + [resultsEnrichedWithRelatedEventInfo]: new WeakMap(), }; } @@ -34,7 +34,7 @@ export const dataReducer: Reducer = (state = initialS * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ - const statsMap = state[resultIsEnrichedWithRelatedEventInfo]; + const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap && typeof statsMap?.set === 'function') { for (const updatedEvent of action.payload.keys()) { const newStatsEntry = action.payload.get(updatedEvent); @@ -50,7 +50,7 @@ export const dataReducer: Reducer = (state = initialS } } } - return { ...state, [resultIsEnrichedWithRelatedEventInfo]: statsMap }; + return { ...state, [resultsEnrichedWithRelatedEventInfo]: statsMap }; } else if (action.type === 'appRequestedResolverData') { return { ...state, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index c7177c6387e7a9..f0f765a010b836 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -7,7 +7,7 @@ import { Dispatch, MiddlewareAPI } from 'redux'; import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public'; import { EndpointPluginServices } from '../../../plugin'; -import { ResolverState, ResolverAction } from '../types'; +import { ResolverState, ResolverAction, RelatedEventDataEntry } from '../types'; import { ResolverEvent, ResolverNode } from '../../../../common/types'; import * as event from '../../../../common/models/event'; @@ -78,5 +78,34 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { } } } + /** + * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 + * When this data is inlined with results, there won't be a need for this. + */ + if (action.type === 'appRequestedRelatedEventData') { + const response: Map = new Map(); + //An array, but assume it has a length of 1 + const idsToFetchRelatedEventsFor = action.payload; + if(typeof context !== 'undefined') { + const httpGetter = context.services.http.get; + async function* getEachRelatedEventsResult(idsToFetch: string[]) { + for (const id of idsToFetch){ + yield await Promise.all([ + httpGetter(`/api/endpoint/resolver/${id}/events`, { + query: {events: 100}, + }), + ]); + } + } + for await (const result of getEachRelatedEventsResult(idsToFetchRelatedEventsFor)){ + //pack up the results into response + + } + } + // return api.dispatch({ + // type: 'serverReturnedRelatedEventData', + // payload: response, + // }); + } }; }; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 0f1fd07b475f71..77133406674866 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -150,7 +150,7 @@ export type RelatedEventType = * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ -export const resultIsEnrichedWithRelatedEventInfo = Symbol( +export const resultsEnrichedWithRelatedEventInfo = Symbol( 'The result (e.g. a ResolverEvent) is enriched with information and stats about related events' ); /** @@ -195,7 +195,7 @@ export interface DataState { readonly results: readonly ResolverEvent[]; isLoading: boolean; hasError: boolean; - [resultIsEnrichedWithRelatedEventInfo]?: RelatedEventData; + [resultsEnrichedWithRelatedEventInfo]?: RelatedEventData; } export type Vector2 = readonly [number, number]; From 29e0018a0a35efcd75acc59a65822dd74d2c4893 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 30 Apr 2020 14:14:55 -0400 Subject: [PATCH 18/74] investigating problems with /events and CLI --- .../embeddables/resolver/store/middleware.ts | 5 ++- .../resolver/view/process_event_dot.tsx | 32 +++++++++++++------ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index f0f765a010b836..61079048a26075 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -83,6 +83,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * When this data is inlined with results, there won't be a need for this. */ if (action.type === 'appRequestedRelatedEventData') { + console.log('middleware handling related request'); const response: Map = new Map(); //An array, but assume it has a length of 1 const idsToFetchRelatedEventsFor = action.payload; @@ -99,7 +100,9 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { } for await (const result of getEachRelatedEventsResult(idsToFetchRelatedEventsFor)){ //pack up the results into response - + for (const relatedEvent of result){ + console.log('related Event: %o', relatedEvent) + } } } // return api.dispatch({ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 77fc4d80cf5f72..48e409a9489d4b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -122,15 +122,14 @@ const NodeSubMenu = styled( menuAction, optionsWithActions, className, - }: { menuTitle: string; className?: string } & ( + }: { menuTitle: string; className?: string; menuAction: () => unknown; } & ( | { - menuAction?: undefined; optionsWithActions: | ResolverSubmenuOptionList | typeof subMenuAssets.initialMenuStatus | typeof waitingForRelatedEventData; } - | { menuAction: () => unknown; optionsWithActions?: undefined } + | { optionsWithActions?: undefined } )) => { const [menuIsOpen, setMenuOpen] = useState(false); const handleMenuOpenClick = useCallback( @@ -161,7 +160,7 @@ const NodeSubMenu = styled( */ return (
- + {menuTitle}
@@ -174,7 +173,7 @@ const NodeSubMenu = styled( return (
{ + console.log('in dispatch') + dispatch({ + type: 'appRequestedRelatedEventData', + payload: [selfId] + }); + }, + [dispatch, selfId] + ); + /* eslint-disable jsx-a11y/click-events-have-key-events */ /** * Key event handling (e.g. 'Enter'/'Space') is provisioned by the `EuiKeyboardAccessible` component @@ -473,16 +483,18 @@ export const ProcessEventDot = styled( {} }, - { optionTitle: '10 File', action: () => {} }, - ]} + // optionsWithActions={[ + // { optionTitle: '10 DNS', action: () => {} }, + // { optionTitle: '10 File', action: () => {} }, + // ]} + optionsWithActions={subMenuAssets.initialMenuStatus} + menuAction={handleRelatedEventRequest} /> {}} + menuAction={()=>{}} /> From c7c22bf7c0dc56ed1fbe21025412e4219e71993f Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 5 May 2020 21:46:30 -0400 Subject: [PATCH 19/74] pulling through selector --- .../plugins/endpoint/common/models/event.ts | 42 ++++++++++++++++ .../embeddables/resolver/store/actions.ts | 2 +- .../resolver/store/data/reducer.ts | 6 ++- .../resolver/store/data/selectors.ts | 26 ++++++---- .../embeddables/resolver/store/middleware.ts | 49 +++++++++++++------ .../public/embeddables/resolver/types.ts | 11 +++-- .../resolver/view/process_event_dot.tsx | 4 +- 7 files changed, 107 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/endpoint/common/models/event.ts b/x-pack/plugins/endpoint/common/models/event.ts index 47f39d2d117976..7568450f68a97c 100644 --- a/x-pack/plugins/endpoint/common/models/event.ts +++ b/x-pack/plugins/endpoint/common/models/event.ts @@ -5,6 +5,7 @@ */ import { LegacyEndpointEvent, ResolverEvent } from '../types'; +import { EventCategory } from '../../public/embeddables/resolver/types'; export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEvent { return (event as LegacyEndpointEvent).endgame !== undefined; @@ -46,3 +47,44 @@ export function parentEntityId(event: ResolverEvent): string | undefined { } return event.process.parent?.entity_id; } + +export function eventCategoryDisplayName(event: ResolverEvent): EventCategory { + //This was transcribed from the Endgame app: + const eventTypeToNameMap = new Map([ + ['process', 'Process'], + ['alert', 'Alert'], + ['security', 'Security'], + ['file', 'File'], + ['network', 'Network'], + ['registry', 'Registry'], + ['dns', 'DNS'], + ['clr', 'CLR'], + ['image_load', 'Image Load'], + ['powershell', 'Powershell'], + //these did *not* have corresponding entries in the Endgame map + ['wmi','WMI'], + ['api','API'], + ['user','User'], + ]) + + //Returning "Security" as a catch-all because it seems pretty general + let eventCategoryToReturn: EventCategory = 'Security' + if (isLegacyEvent(event)) { + const legacyFullType = event.endgame.event_type_full; + if(legacyFullType){ + const mappedLegacyCategory = eventTypeToNameMap.get( legacyFullType ) + if (mappedLegacyCategory) { + eventCategoryToReturn = mappedLegacyCategory; + } + } + } + else { + const eventCategories = event.event.category; + const eventCategory = typeof eventCategories === 'string' ? eventCategories : (eventCategories[0] || ''); + const mappedCategoryValue = eventTypeToNameMap.get(eventCategory); + if(mappedCategoryValue){ + eventCategoryToReturn = mappedCategoryValue; + } + } + return eventCategoryToReturn +} \ No newline at end of file diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index 5d4598348bf471..76bac7c1fbc081 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -50,7 +50,7 @@ interface AppRequestedResolverData { */ interface AppRequestedRelatedEventData { readonly type: 'appRequestedRelatedEventData'; - readonly payload: string[]; + readonly payload: [ResolverEvent, string]; } /** diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index c50cdeb336035b..3206117cd66f23 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -10,6 +10,7 @@ import { ResolverAction, resultsEnrichedWithRelatedEventInfo, RelatedEventDataEntryWithStats, + RelatedEventType, } from '../../types'; function initialState(): DataState { @@ -17,7 +18,7 @@ function initialState(): DataState { results: [], isLoading: false, hasError: false, - [resultsEnrichedWithRelatedEventInfo]: new WeakMap(), + [resultsEnrichedWithRelatedEventInfo]: new Map(), }; } @@ -34,6 +35,7 @@ export const dataReducer: Reducer = (state = initialS * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ + console.log('payload received by reducer: %o', action.payload); const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap && typeof statsMap?.set === 'function') { for (const updatedEvent of action.payload.keys()) { @@ -41,7 +43,7 @@ export const dataReducer: Reducer = (state = initialS if (newStatsEntry) { // do stats - const statsForEntry = { DNS: 32, File: 12 }; + const statsForEntry = newStatsEntry?.related_events.reduce((a: Partial>,v: {related_event_type: RelatedEventType})=>{ a[v.related_event_type] = (a[v.related_event_type]||0)+1; return a; },{}) const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign( newStatsEntry, { stats: statsForEntry } diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 8d42fe77838f60..88847264aec338 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -15,7 +15,8 @@ import { Matrix3, AdjacentProcessMap, RelatedEventData, - RelatedEventType, + resultsEnrichedWithRelatedEventInfo, + waitingForRelatedEventData, } from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { Vector2 } from '../../types'; @@ -407,21 +408,28 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in return indexedProcessTreeFactory(graphableProcesses); }); -export const relatedEvents = createSelector(graphableProcesses, function getRelatedEvents( +/** + * Process events that will be graphed. + */ +export const relatedEventStats = createSelector( + (data: DataState) => data, + function(data) { + return data[resultsEnrichedWithRelatedEventInfo] + } +); + +export const relatedEvents = createSelector(graphableProcesses, relatedEventStats, function getRelatedEvents( /* eslint-disable no-shadow */ - graphableProcesses + graphableProcesses, + relatedEventStats, /* eslint-enable no-shadow */ ) { - const tempType: RelatedEventType = 'DNS'; const eventsRelatedByProcess: RelatedEventData = new WeakMap(); /* eslint-disable no-shadow */ return graphableProcesses.reduce((relatedEvents, graphableProcess) => { /* eslint-enable no-shadow */ - const tempEntry = { - related_events: [{ related_event: graphableProcess, related_event_type: tempType }], - stats: { DNS: 23 }, - }; - relatedEvents.set(graphableProcess, tempEntry); + const relatedEventDataEntry = relatedEventStats?.get(graphableProcess) || waitingForRelatedEventData; + relatedEvents.set(graphableProcess, relatedEventDataEntry); return relatedEvents; }, eventsRelatedByProcess); }); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 61079048a26075..39a163d762b8e7 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -7,7 +7,7 @@ import { Dispatch, MiddlewareAPI } from 'redux'; import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public'; import { EndpointPluginServices } from '../../../plugin'; -import { ResolverState, ResolverAction, RelatedEventDataEntry } from '../types'; +import { ResolverState, ResolverAction, RelatedEventDataEntry, RelatedEventType } from '../types'; import { ResolverEvent, ResolverNode } from '../../../../common/types'; import * as event from '../../../../common/models/event'; @@ -84,31 +84,48 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { */ if (action.type === 'appRequestedRelatedEventData') { console.log('middleware handling related request'); - const response: Map = new Map(); + + //An array, but assume it has a length of 1 - const idsToFetchRelatedEventsFor = action.payload; + const id = event.entityId(action.payload[0]); if(typeof context !== 'undefined') { const httpGetter = context.services.http.get; - async function* getEachRelatedEventsResult(idsToFetch: string[]) { - for (const id of idsToFetch){ - yield await Promise.all([ + async function* getEachRelatedEventsResult(eventsToFetch: ResolverEvent[]) { + for (const evt of eventsToFetch){ + //Tried with generic event.entityId() : no joy on results + yield [evt, await Promise.all([ httpGetter(`/api/endpoint/resolver/${id}/events`, { query: {events: 100}, }), - ]); + ]) + ] } } - for await (const result of getEachRelatedEventsResult(idsToFetchRelatedEventsFor)){ - //pack up the results into response - for (const relatedEvent of result){ - console.log('related Event: %o', relatedEvent) + for await (const results of getEachRelatedEventsResult([action.payload[0]])){ + const response: Map = new Map(); + const baseEvent = results[0] as unknown as ResolverEvent; + const fetchedResults = (results[1] as unknown as {events: ResolverEvent[]}[]) + //pack up the results into response + for (const relatedEventResult of fetchedResults) { + console.log('related Event result: %o', relatedEventResult) + //help figure out how to type the Async Generator above + const relatedEventsFromResult = relatedEventResult.events; + const relatedEventEntry = relatedEventsFromResult.map(related_event => { + return { + related_event, + related_event_type: event.eventCategoryDisplayName(related_event) as RelatedEventType + } + }) + response.set(baseEvent, {related_events: relatedEventEntry}); + console.log('setResponse') + } + console.log('response: %o', response); + api.dispatch({ + type: 'serverReturnedRelatedEventData', + payload: response, + }); } - } } - // return api.dispatch({ - // type: 'serverReturnedRelatedEventData', - // payload: response, - // }); } }; }; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 77133406674866..97dfe6dbd2c796 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -145,14 +145,19 @@ export type RelatedEventType = | 'CLR' | 'Image Load' | 'User'; + + export type EventCategory = RelatedEventType | + ( | 'Alert' + | 'Process' + | 'Security' + ) + /** * This symbol is used to tag results with Related event info * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ -export const resultsEnrichedWithRelatedEventInfo = Symbol( - 'The result (e.g. a ResolverEvent) is enriched with information and stats about related events' -); +export const resultsEnrichedWithRelatedEventInfo = `resultsEnrichedWithRelatedEventInfo` /** * This symbol indicates that the app is waiting for related event data for the subject * of any particular request. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 48e409a9489d4b..27bd4b952e7533 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -347,10 +347,10 @@ export const ProcessEventDot = styled( console.log('in dispatch') dispatch({ type: 'appRequestedRelatedEventData', - payload: [selfId] + payload: [event, selfId] }); }, - [dispatch, selfId] + [dispatch, event, selfId] ); /* eslint-disable jsx-a11y/click-events-have-key-events */ From 56311138620dae02280c31e4bc2476e8f3ea8dd0 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 6 May 2020 00:47:56 -0400 Subject: [PATCH 20/74] test rendering in component --- .../resolver/store/data/reducer.ts | 22 ++++++++-- .../resolver/store/data/selectors.ts | 8 ++-- .../public/embeddables/resolver/types.ts | 2 +- .../embeddables/resolver/view/index.tsx | 2 +- .../resolver/view/process_event_dot.tsx | 42 +++++++++++++------ 5 files changed, 55 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 3206117cd66f23..7a0b1451a53857 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -11,6 +11,8 @@ import { resultsEnrichedWithRelatedEventInfo, RelatedEventDataEntryWithStats, RelatedEventType, + RelatedEventData, + waitingForRelatedEventData, } from '../../types'; function initialState(): DataState { @@ -30,14 +32,27 @@ export const dataReducer: Reducer = (state = initialS isLoading: false, hasError: false, }; - } else if (action.type === 'serverReturnedRelatedEventData') { + } + else if (action.type === 'appRequestedRelatedEventData') { + const evt = action.payload[0]; + const statsMap = state[resultsEnrichedWithRelatedEventInfo]; + if(statsMap){ + const currentStatsMap = new Map(statsMap); + currentStatsMap.set(evt, waitingForRelatedEventData); + return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; + } + return state; + } + else if (action.type === 'serverReturnedRelatedEventData') { /** * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ console.log('payload received by reducer: %o', action.payload); const statsMap = state[resultsEnrichedWithRelatedEventInfo]; + if (statsMap && typeof statsMap?.set === 'function') { + const currentStatsMap: RelatedEventData = new Map(statsMap); for (const updatedEvent of action.payload.keys()) { const newStatsEntry = action.payload.get(updatedEvent); @@ -48,11 +63,12 @@ export const dataReducer: Reducer = (state = initialS newStatsEntry, { stats: statsForEntry } ); - statsMap.set(updatedEvent, newRelatedEventStats); + currentStatsMap.set(updatedEvent, newRelatedEventStats); } } + return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; } - return { ...state, [resultsEnrichedWithRelatedEventInfo]: statsMap }; + return state; } else if (action.type === 'appRequestedResolverData') { return { ...state, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 88847264aec338..f93fe1875d588d 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -424,12 +424,14 @@ export const relatedEvents = createSelector(graphableProcesses, relatedEventStat relatedEventStats, /* eslint-enable no-shadow */ ) { - const eventsRelatedByProcess: RelatedEventData = new WeakMap(); + const eventsRelatedByProcess: RelatedEventData = new Map(); /* eslint-disable no-shadow */ return graphableProcesses.reduce((relatedEvents, graphableProcess) => { /* eslint-enable no-shadow */ - const relatedEventDataEntry = relatedEventStats?.get(graphableProcess) || waitingForRelatedEventData; - relatedEvents.set(graphableProcess, relatedEventDataEntry); + const relatedEventDataEntry = relatedEventStats?.get(graphableProcess) + if(relatedEventDataEntry){ + relatedEvents.set(graphableProcess, relatedEventDataEntry); + } return relatedEvents; }, eventsRelatedByProcess); }); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 97dfe6dbd2c796..59015c992afb2f 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -191,7 +191,7 @@ export type RelatedEventEntryWithStatsOrWaiting = * This represents a Map that will return either a `RelatedEventDataEntryWithStats` * or a `waitingForRelatedEventData` symbol when referenced unique event. */ -export type RelatedEventData = WeakMap; +export type RelatedEventData = Map; /** * State for `data` reducer which handles receiving Resolver data from the backend. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx index 1746a6c7e826d0..5275ba3ec5b4c9 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/index.tsx @@ -116,7 +116,7 @@ export const Resolver = styled( projectionMatrix={projectionMatrix} event={processEvent} adjacentNodeMap={adjacentNodeMap} - relatedEvents={relatedEvents.get(processEvent)!} + relatedEvents={relatedEvents.get(processEvent)} /> ); })} diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 27bd4b952e7533..a6378961421a18 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -136,12 +136,7 @@ const NodeSubMenu = styled( (clickEvent: React.MouseEvent) => { clickEvent.preventDefault(); clickEvent.stopPropagation(); - if (optionsWithActions === waitingForRelatedEventData) { - // dispatch action - setMenuOpen(true); - } else { - setMenuOpen(!menuIsOpen); - } + setMenuOpen(!menuIsOpen); }, [menuIsOpen, optionsWithActions] ); @@ -153,6 +148,10 @@ const NodeSubMenu = styled( }, [menuAction] ); + const isMenuDataAvailable = useMemo(()=>{ + return typeof optionsWithActions === 'object'; + }, []) + if (!optionsWithActions) { /** * When called with a `menuAction` @@ -173,7 +172,7 @@ const NodeSubMenu = styled( return (
{ /** * Convert the position, which is in 'world' coordinates, to screen coordinates. @@ -255,6 +254,27 @@ export const ProcessEventDot = styled( const activeDescendantId = useSelector(selectors.uiActiveDescendantId); const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId); + const relatedEventOptions = useMemo(()=>{ + console.log('rememo on %o', relatedEvents); + if(!relatedEvents){ + return subMenuAssets.initialMenuStatus + } + if(relatedEvents === waitingForRelatedEventData) { + return relatedEvents + } + return Object.entries(relatedEvents.stats).map((k,v) => { + return { + optionTitle: `${v} ${k}`, + action: () => { + /** + * COMING SOON TO A THEATER NEAR YOU: + * OPENING SIDE MENUS + */ + } + } + }) + },[relatedEvents]) + const nodeViewportStyle = useMemo( () => ({ left: `${left}px`, @@ -483,11 +503,7 @@ export const ProcessEventDot = styled( {} }, - // { optionTitle: '10 File', action: () => {} }, - // ]} - optionsWithActions={subMenuAssets.initialMenuStatus} + optionsWithActions={relatedEventOptions} menuAction={handleRelatedEventRequest} /> From e8d48dbf0c7e64c9c9320d553c33a8d0ebe45f70 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 6 May 2020 00:48:49 -0400 Subject: [PATCH 21/74] cleanup unused --- .../endpoint/public/embeddables/resolver/store/data/selectors.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index f93fe1875d588d..ee2850eeaa6599 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -16,7 +16,6 @@ import { AdjacentProcessMap, RelatedEventData, resultsEnrichedWithRelatedEventInfo, - waitingForRelatedEventData, } from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { Vector2 } from '../../types'; From 01ea4a1c35c4c5a8d15c5ca2e27454e4b6864f1c Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 6 May 2020 02:04:09 -0400 Subject: [PATCH 22/74] more cleanup --- .../resolver/store/data/reducer.ts | 1 - .../embeddables/resolver/store/middleware.ts | 6 ---- .../resolver/view/process_event_dot.tsx | 31 +++++++++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 7a0b1451a53857..4b7b3fd2eea864 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -48,7 +48,6 @@ export const dataReducer: Reducer = (state = initialS * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ - console.log('payload received by reducer: %o', action.payload); const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap && typeof statsMap?.set === 'function') { diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 39a163d762b8e7..51bdd86d8208e2 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -83,9 +83,6 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * When this data is inlined with results, there won't be a need for this. */ if (action.type === 'appRequestedRelatedEventData') { - console.log('middleware handling related request'); - - //An array, but assume it has a length of 1 const id = event.entityId(action.payload[0]); if(typeof context !== 'undefined') { @@ -107,7 +104,6 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { const fetchedResults = (results[1] as unknown as {events: ResolverEvent[]}[]) //pack up the results into response for (const relatedEventResult of fetchedResults) { - console.log('related Event result: %o', relatedEventResult) //help figure out how to type the Async Generator above const relatedEventsFromResult = relatedEventResult.events; const relatedEventEntry = relatedEventsFromResult.map(related_event => { @@ -117,9 +113,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { } }) response.set(baseEvent, {related_events: relatedEventEntry}); - console.log('setResponse') } - console.log('response: %o', response); api.dispatch({ type: 'serverReturnedRelatedEventData', payload: response, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index a6378961421a18..13d4f0b66d4ad1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -92,10 +92,13 @@ type ResolverSubmenuOptionList = Array<{ const OptionList = React.memo( ({ subMenuOptions }: { subMenuOptions: ResolverSubmenuOptionList }) => { const selectableOptions = subMenuOptions.map((opt, index) => { - return { + return opt.prefix ? { + label: opt.optionTitle, + prepend: ({opt.prefix}), + } : { label: opt.optionTitle, }; - }); + }) const [options, setOptions] = useState(selectableOptions); return useMemo( () => ( @@ -145,12 +148,13 @@ const NodeSubMenu = styled( clickEvent.preventDefault(); clickEvent.stopPropagation(); if (typeof menuAction === 'function') menuAction(); + setMenuOpen(true); }, [menuAction] ); const isMenuDataAvailable = useMemo(()=>{ return typeof optionsWithActions === 'object'; - }, []) + }, [optionsWithActions]) if (!optionsWithActions) { /** @@ -175,13 +179,13 @@ const NodeSubMenu = styled( onClick={isMenuDataAvailable ? handleMenuOpenClick : handleMenuActionClick} color="ghost" size="s" - iconType="arrowUp" + iconType={(menuIsOpen ? 'arrowUp' : 'arrowDown')} iconSide="right" tabIndex={-1} > {menuTitle} - {menuIsOpen && + {menuIsOpen && isMenuDataAvailable && (typeof optionsWithActions === 'symbol' ? ( 'loading' ) : ( @@ -201,6 +205,9 @@ const NodeSubMenu = styled( border-bottom-left-radius: 0; border-bottom-right-radius: 0; } + &.is-open .euiSelectableListItem__prepend { + color: white; + } `; /** @@ -255,7 +262,6 @@ export const ProcessEventDot = styled( const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId); const relatedEventOptions = useMemo(()=>{ - console.log('rememo on %o', relatedEvents); if(!relatedEvents){ return subMenuAssets.initialMenuStatus } @@ -264,7 +270,8 @@ export const ProcessEventDot = styled( } return Object.entries(relatedEvents.stats).map((k,v) => { return { - optionTitle: `${v} ${k}`, + prefix: k[1], + optionTitle: `${k[0]}`, action: () => { /** * COMING SOON TO A THEATER NEAR YOU: @@ -349,6 +356,7 @@ export const ProcessEventDot = styled( const handleClick = useCallback( (clickEvent: React.MouseEvent) => { + if (animationTarget.current !== null) { (animationTarget.current as any).beginElement(); } @@ -364,7 +372,6 @@ export const ProcessEventDot = styled( const handleRelatedEventRequest = useCallback( () => { - console.log('in dispatch') dispatch({ type: 'appRequestedRelatedEventData', payload: [event, selfId] @@ -500,7 +507,7 @@ export const ProcessEventDot = styled(
{magFactorX >= 2 && ( - + Date: Wed, 6 May 2020 09:58:19 -0400 Subject: [PATCH 23/74] cleanup action --- .../public/embeddables/resolver/store/data/reducer.ts | 2 +- .../public/embeddables/resolver/store/middleware.ts | 8 ++++---- .../embeddables/resolver/view/process_event_dot.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 4b7b3fd2eea864..dcd14c06e96a4e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -34,7 +34,7 @@ export const dataReducer: Reducer = (state = initialS }; } else if (action.type === 'appRequestedRelatedEventData') { - const evt = action.payload[0]; + const evt = action.payload; const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if(statsMap){ const currentStatsMap = new Map(statsMap); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 51bdd86d8208e2..dfe7ad7b4da76d 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -84,13 +84,13 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { */ if (action.type === 'appRequestedRelatedEventData') { //An array, but assume it has a length of 1 - const id = event.entityId(action.payload[0]); + const id = event.entityId(action.payload); if(typeof context !== 'undefined') { const httpGetter = context.services.http.get; async function* getEachRelatedEventsResult(eventsToFetch: ResolverEvent[]) { - for (const evt of eventsToFetch){ + for (const eventToRqueryForRelateds of eventsToFetch){ //Tried with generic event.entityId() : no joy on results - yield [evt, await Promise.all([ + yield [eventToRqueryForRelateds, await Promise.all([ httpGetter(`/api/endpoint/resolver/${id}/events`, { query: {events: 100}, }), @@ -98,7 +98,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { ] } } - for await (const results of getEachRelatedEventsResult([action.payload[0]])){ + for await (const results of getEachRelatedEventsResult([action.payload])){ const response: Map = new Map(); const baseEvent = results[0] as unknown as ResolverEvent; const fetchedResults = (results[1] as unknown as {events: ResolverEvent[]}[]) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 13d4f0b66d4ad1..b94ce3363c8b89 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -374,7 +374,7 @@ export const ProcessEventDot = styled( () => { dispatch({ type: 'appRequestedRelatedEventData', - payload: [event, selfId] + payload: event }); }, [dispatch, event, selfId] From c7edee78859a25dafc10389aa10e0f63410e9867 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 6 May 2020 10:15:19 -0400 Subject: [PATCH 24/74] fix submenu options typings --- .../embeddables/resolver/view/process_event_dot.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index b94ce3363c8b89..9b51c522d26105 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useMemo, useState, ReactNode } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; import { @@ -83,20 +83,25 @@ const subMenuAssets = { }, }; -type ResolverSubmenuOptionList = Array<{ +type ResolverSubmenuOption = { optionTitle: string; action: () => unknown; prefix?: number | JSX.Element; -}>; +} +type ResolverSubmenuOptionList = Array; const OptionList = React.memo( ({ subMenuOptions }: { subMenuOptions: ResolverSubmenuOptionList }) => { - const selectableOptions = subMenuOptions.map((opt, index) => { + const selectableOptions = subMenuOptions.map((opt: ResolverSubmenuOption): { + label: string; + prepend?: ReactNode; + } => { return opt.prefix ? { label: opt.optionTitle, prepend: ({opt.prefix}), } : { label: opt.optionTitle, + prepend: (), }; }) const [options, setOptions] = useState(selectableOptions); From 2d1b3f2f8cb7ff266bed3ddecfe119d2400ba3c0 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 6 May 2020 10:32:15 -0400 Subject: [PATCH 25/74] move utility function off and seal it --- .../embeddables/resolver/store/middleware.ts | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index dfe7ad7b4da76d..2657a4583aa5ad 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -10,6 +10,7 @@ import { EndpointPluginServices } from '../../../plugin'; import { ResolverState, ResolverAction, RelatedEventDataEntry, RelatedEventType } from '../types'; import { ResolverEvent, ResolverNode } from '../../../../common/types'; import * as event from '../../../../common/models/event'; +import { HttpHandler } from 'kibana/public'; type MiddlewareFactory = ( context?: KibanaReactContextValue @@ -30,6 +31,18 @@ function flattenEvents(children: ResolverNode[], events: ResolverEvent[] = []): }, events); } +async function* getEachRelatedEventsResult(eventsToFetch: ResolverEvent[], httpGetter: HttpHandler) { + for (const eventToQueryForRelateds of eventsToFetch){ + const id = event.entityId(eventToQueryForRelateds); + yield [eventToQueryForRelateds, await Promise.all([ + httpGetter(`/api/endpoint/resolver/${id}/events`, { + query: {events: 100}, + }), + ]) + ] + } +} + export const resolverMiddlewareFactory: MiddlewareFactory = context => { return api => next => async (action: ResolverAction) => { next(action); @@ -83,22 +96,8 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * When this data is inlined with results, there won't be a need for this. */ if (action.type === 'appRequestedRelatedEventData') { - //An array, but assume it has a length of 1 - const id = event.entityId(action.payload); - if(typeof context !== 'undefined') { - const httpGetter = context.services.http.get; - async function* getEachRelatedEventsResult(eventsToFetch: ResolverEvent[]) { - for (const eventToRqueryForRelateds of eventsToFetch){ - //Tried with generic event.entityId() : no joy on results - yield [eventToRqueryForRelateds, await Promise.all([ - httpGetter(`/api/endpoint/resolver/${id}/events`, { - query: {events: 100}, - }), - ]) - ] - } - } - for await (const results of getEachRelatedEventsResult([action.payload])){ + if(typeof context !== 'undefined') { + for await (const results of getEachRelatedEventsResult([action.payload], context.services.http.get)){ const response: Map = new Map(); const baseEvent = results[0] as unknown as ResolverEvent; const fetchedResults = (results[1] as unknown as {events: ResolverEvent[]}[]) From 4ad185d27b8530f2d45c0de78d683af9663bc582 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 15:18:34 -0400 Subject: [PATCH 26/74] fix middleware types --- .../embeddables/resolver/store/actions.ts | 2 +- .../embeddables/resolver/store/data/action.ts | 10 ++- .../resolver/store/data/reducer.ts | 21 ++++-- .../embeddables/resolver/store/middleware.ts | 71 +++++++++++-------- .../public/embeddables/resolver/types.ts | 14 ++-- 5 files changed, 72 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index 76bac7c1fbc081..498fd008ead62a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -50,7 +50,7 @@ interface AppRequestedResolverData { */ interface AppRequestedRelatedEventData { readonly type: 'appRequestedRelatedEventData'; - readonly payload: [ResolverEvent, string]; + readonly payload: ResolverEvent; } /** diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index 33f63ec3278622..b50dfb28cafd82 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -24,7 +24,15 @@ interface ServerReturnedRelatedEventData { readonly payload: Map; } +/** + * Will occur when a request for related event data is unsuccessful. + */ +interface ServerFailedToReturnRelatedEventData { + readonly type: 'serverFailedToReturnRelatedEventData'; +} + export type DataAction = | ServerReturnedResolverData | ServerFailedToReturnResolverData - | ServerReturnedRelatedEventData; + | ServerReturnedRelatedEventData + | ServerFailedToReturnRelatedEventData; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index dcd14c06e96a4e..07500cbf8c19d6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -32,24 +32,22 @@ export const dataReducer: Reducer = (state = initialS isLoading: false, hasError: false, }; - } - else if (action.type === 'appRequestedRelatedEventData') { + } else if (action.type === 'appRequestedRelatedEventData') { const evt = action.payload; const statsMap = state[resultsEnrichedWithRelatedEventInfo]; - if(statsMap){ + if (statsMap) { const currentStatsMap = new Map(statsMap); currentStatsMap.set(evt, waitingForRelatedEventData); return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; } return state; - } - else if (action.type === 'serverReturnedRelatedEventData') { + } else if (action.type === 'serverReturnedRelatedEventData') { /** * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ const statsMap = state[resultsEnrichedWithRelatedEventInfo]; - + if (statsMap && typeof statsMap?.set === 'function') { const currentStatsMap: RelatedEventData = new Map(statsMap); for (const updatedEvent of action.payload.keys()) { @@ -57,7 +55,16 @@ export const dataReducer: Reducer = (state = initialS if (newStatsEntry) { // do stats - const statsForEntry = newStatsEntry?.related_events.reduce((a: Partial>,v: {related_event_type: RelatedEventType})=>{ a[v.related_event_type] = (a[v.related_event_type]||0)+1; return a; },{}) + const statsForEntry = newStatsEntry?.relatedEvents.reduce( + ( + a: Partial>, + v: { relatedEventType: RelatedEventType } + ) => { + a[v.relatedEventType] = (a[v.relatedEventType] || 0) + 1; + return a; + }, + {} + ); const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign( newStatsEntry, { stats: statsForEntry } diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 2657a4583aa5ad..b4685a07f5e768 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -5,12 +5,12 @@ */ import { Dispatch, MiddlewareAPI } from 'redux'; +import { HttpHandler } from 'kibana/public'; import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public'; import { EndpointPluginServices } from '../../../plugin'; import { ResolverState, ResolverAction, RelatedEventDataEntry, RelatedEventType } from '../types'; import { ResolverEvent, ResolverNode } from '../../../../common/types'; import * as event from '../../../../common/models/event'; -import { HttpHandler } from 'kibana/public'; type MiddlewareFactory = ( context?: KibanaReactContextValue @@ -31,15 +31,23 @@ function flattenEvents(children: ResolverNode[], events: ResolverEvent[] = []): }, events); } -async function* getEachRelatedEventsResult(eventsToFetch: ResolverEvent[], httpGetter: HttpHandler) { - for (const eventToQueryForRelateds of eventsToFetch){ +type RelatedEventAPIResponse = Error | { events: ResolverEvent[] }; +async function* getEachRelatedEventsResult( + eventsToFetch: ResolverEvent[], + httpGetter: HttpHandler +): AsyncGenerator<[ResolverEvent, RelatedEventAPIResponse], any, any> { + for (const eventToQueryForRelateds of eventsToFetch) { const id = event.entityId(eventToQueryForRelateds); - yield [eventToQueryForRelateds, await Promise.all([ - httpGetter(`/api/endpoint/resolver/${id}/events`, { - query: {events: 100}, - }), - ]) - ] + const relatedEventError = new Error(`Error fetching related events for entity=${id}`); + let result: RelatedEventAPIResponse = relatedEventError; + try { + result = await httpGetter(`/api/endpoint/resolver/${id}/events`, { + query: { events: 100 }, + }); + } catch (e) { + result = relatedEventError; + } + yield [eventToQueryForRelateds, result]; } } @@ -96,28 +104,35 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * When this data is inlined with results, there won't be a need for this. */ if (action.type === 'appRequestedRelatedEventData') { - if(typeof context !== 'undefined') { - for await (const results of getEachRelatedEventsResult([action.payload], context.services.http.get)){ - const response: Map = new Map(); - const baseEvent = results[0] as unknown as ResolverEvent; - const fetchedResults = (results[1] as unknown as {events: ResolverEvent[]}[]) - //pack up the results into response - for (const relatedEventResult of fetchedResults) { - //help figure out how to type the Async Generator above - const relatedEventsFromResult = relatedEventResult.events; - const relatedEventEntry = relatedEventsFromResult.map(related_event => { - return { - related_event, - related_event_type: event.eventCategoryDisplayName(related_event) as RelatedEventType - } - }) - response.set(baseEvent, {related_events: relatedEventEntry}); - } + if (typeof context !== 'undefined') { + for await (const results of getEachRelatedEventsResult( + [action.payload], + context.services.http.get + )) { + const apiResults = results[1]; + if (apiResults instanceof Error) { api.dispatch({ - type: 'serverReturnedRelatedEventData', - payload: response, + type: 'serverFailedToReturnRelatedEventData', }); } + const response: Map = new Map(); + const baseEvent = results[0]; + const fetchedResults = (results[1] as { events: ResolverEvent[] }).events; + // pack up the results into response + const relatedEventEntry = fetchedResults.map(relatedEvent => { + return { + relatedEvent, + relatedEventType: event.eventCategoryDisplayName(relatedEvent) as RelatedEventType, + }; + }); + + response.set(baseEvent, { relatedEvents: relatedEventEntry }); + + api.dispatch({ + type: 'serverReturnedRelatedEventData', + payload: response, + }); + } } } }; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 59015c992afb2f..e88fd8861f6ed1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -146,18 +146,14 @@ export type RelatedEventType = | 'Image Load' | 'User'; - export type EventCategory = RelatedEventType | - ( | 'Alert' - | 'Process' - | 'Security' - ) +export type EventCategory = RelatedEventType | ('Alert' | 'Process' | 'Security'); /** * This symbol is used to tag results with Related event info * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ -export const resultsEnrichedWithRelatedEventInfo = `resultsEnrichedWithRelatedEventInfo` +export const resultsEnrichedWithRelatedEventInfo = `resultsEnrichedWithRelatedEventInfo`; /** * This symbol indicates that the app is waiting for related event data for the subject * of any particular request. @@ -170,9 +166,9 @@ export const waitingForRelatedEventData = Symbol( * about a particular subject's related events */ export interface RelatedEventDataEntry { - related_events: Array<{ - related_event: ResolverEvent; - related_event_type: RelatedEventType; + relatedEvents: Array<{ + relatedEvent: ResolverEvent; + relatedEventType: RelatedEventType; }>; } /** From de039bfbc62c1d6676d7076c231101ee961314d6 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 16:23:11 -0400 Subject: [PATCH 27/74] adjust model to return Process --- .../plugins/endpoint/common/models/event.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/endpoint/common/models/event.ts b/x-pack/plugins/endpoint/common/models/event.ts index 7568450f68a97c..7e098b9b451f16 100644 --- a/x-pack/plugins/endpoint/common/models/event.ts +++ b/x-pack/plugins/endpoint/common/models/event.ts @@ -49,7 +49,6 @@ export function parentEntityId(event: ResolverEvent): string | undefined { } export function eventCategoryDisplayName(event: ResolverEvent): EventCategory { - //This was transcribed from the Endgame app: const eventTypeToNameMap = new Map([ ['process', 'Process'], ['alert', 'Alert'], @@ -61,30 +60,29 @@ export function eventCategoryDisplayName(event: ResolverEvent): EventCategory { ['clr', 'CLR'], ['image_load', 'Image Load'], ['powershell', 'Powershell'], - //these did *not* have corresponding entries in the Endgame map - ['wmi','WMI'], - ['api','API'], - ['user','User'], - ]) + ['wmi', 'WMI'], + ['api', 'API'], + ['user', 'User'], + ]); - //Returning "Security" as a catch-all because it seems pretty general - let eventCategoryToReturn: EventCategory = 'Security' + // Returning "Process" as a catch-all here because it seems pretty general + let eventCategoryToReturn: EventCategory = 'Process'; if (isLegacyEvent(event)) { const legacyFullType = event.endgame.event_type_full; - if(legacyFullType){ - const mappedLegacyCategory = eventTypeToNameMap.get( legacyFullType ) + if (legacyFullType) { + const mappedLegacyCategory = eventTypeToNameMap.get(legacyFullType); if (mappedLegacyCategory) { eventCategoryToReturn = mappedLegacyCategory; } } - } - else { + } else { const eventCategories = event.event.category; - const eventCategory = typeof eventCategories === 'string' ? eventCategories : (eventCategories[0] || ''); + const eventCategory = + typeof eventCategories === 'string' ? eventCategories : eventCategories[0] || ''; const mappedCategoryValue = eventTypeToNameMap.get(eventCategory); - if(mappedCategoryValue){ + if (mappedCategoryValue) { eventCategoryToReturn = mappedCategoryValue; } } - return eventCategoryToReturn -} \ No newline at end of file + return eventCategoryToReturn; +} From ffdb41f10b2c8fe00d31a0617936421ab8e244c5 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 16:30:57 -0400 Subject: [PATCH 28/74] K Qualters review: change action name --- .../endpoint/public/embeddables/resolver/store/actions.ts | 6 +++--- .../public/embeddables/resolver/store/data/reducer.ts | 2 +- .../public/embeddables/resolver/store/middleware.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index 498fd008ead62a..0fa34dad8ba309 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -48,8 +48,8 @@ interface AppRequestedResolverData { * The action dispatched when the app requests related event data for one or more * subjects (whose ids should be included as an array @ `payload`) */ -interface AppRequestedRelatedEventData { - readonly type: 'appRequestedRelatedEventData'; +interface UserRequestedRelatedEventData { + readonly type: 'userRequestedRelatedEventData'; readonly payload: ResolverEvent; } @@ -94,4 +94,4 @@ export type ResolverAction = | AppRequestedResolverData | UserFocusedOnResolverNode | UserSelectedResolverNode - | AppRequestedRelatedEventData; + | UserRequestedRelatedEventData; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 07500cbf8c19d6..1e274390bea0bb 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -32,7 +32,7 @@ export const dataReducer: Reducer = (state = initialS isLoading: false, hasError: false, }; - } else if (action.type === 'appRequestedRelatedEventData') { + } else if (action.type === 'userRequestedRelatedEventData') { const evt = action.payload; const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap) { diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index b4685a07f5e768..9701f80d691b3b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -103,7 +103,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. */ - if (action.type === 'appRequestedRelatedEventData') { + if (action.type === 'userRequestedRelatedEventData') { if (typeof context !== 'undefined') { for await (const results of getEachRelatedEventsResult( [action.payload], From 3a6314b25a292f10c2f5aa1ec2e6e7674d3cfcb4 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 16:41:57 -0400 Subject: [PATCH 29/74] lint --- .../endpoint/public/embeddables/resolver/store/data/action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index b50dfb28cafd82..d6cf8fba462ada 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -17,7 +17,7 @@ interface ServerFailedToReturnResolverData { } /** - * Will occur when a request for related event data is fulfilled. + * Will occur when a request for related event data is fulfilled by the API. */ interface ServerReturnedRelatedEventData { readonly type: 'serverReturnedRelatedEventData'; From 12af60b6e294ba902d6081cdbf69c58a17f4c8a3 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:02:43 -0400 Subject: [PATCH 30/74] Add error handling --- .../embeddables/resolver/store/data/action.ts | 1 + .../resolver/store/data/reducer.ts | 21 ++++++++++++++++--- .../embeddables/resolver/store/middleware.ts | 1 + .../public/embeddables/resolver/types.ts | 3 ++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index d6cf8fba462ada..7481f9d733969b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -29,6 +29,7 @@ interface ServerReturnedRelatedEventData { */ interface ServerFailedToReturnRelatedEventData { readonly type: 'serverFailedToReturnRelatedEventData'; + readonly payload: [ResolverEvent, Error]; } export type DataAction = diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 1e274390bea0bb..45177e864fef6b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -33,15 +33,30 @@ export const dataReducer: Reducer = (state = initialS hasError: false, }; } else if (action.type === 'userRequestedRelatedEventData') { - const evt = action.payload; + const resolverEvent = action.payload; const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap) { const currentStatsMap = new Map(statsMap); - currentStatsMap.set(evt, waitingForRelatedEventData); + /** + * Set the waiting indicator for this event to indicate that related event results are pending. + * It will be replaced by the actual results from the API when they are returned. + */ + currentStatsMap.set(resolverEvent, waitingForRelatedEventData); return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; } return state; - } else if (action.type === 'serverReturnedRelatedEventData') { + } else if (action.type === 'serverFailedToReturnRelatedEventData') { + + const statsMap = state[resultsEnrichedWithRelatedEventInfo]; + if (statsMap) { + const currentStatsMap = new Map(statsMap); + const [resolverEvent, apiError] = action.payload; + currentStatsMap.set(resolverEvent, apiError); + return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; + } + return state; + } + else if (action.type === 'serverReturnedRelatedEventData') { /** * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 9701f80d691b3b..88da2f6cfc7246 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -113,6 +113,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { if (apiResults instanceof Error) { api.dispatch({ type: 'serverFailedToReturnRelatedEventData', + payload: [results[0], apiResults], }); } const response: Map = new Map(); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index e88fd8861f6ed1..4dda0860c7ecf2 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -181,7 +181,8 @@ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { export type RelatedEventEntryWithStatsOrWaiting = | RelatedEventDataEntryWithStats - | typeof waitingForRelatedEventData; + | typeof waitingForRelatedEventData + | Error; /** * This represents a Map that will return either a `RelatedEventDataEntryWithStats` From ecf85dbb23ac1ec71e262f2255f512b28ba46a3f Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:16:24 -0400 Subject: [PATCH 31/74] cleanup reducer --- .../embeddables/resolver/store/data/reducer.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 45177e864fef6b..969068e1324b04 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -39,14 +39,13 @@ export const dataReducer: Reducer = (state = initialS const currentStatsMap = new Map(statsMap); /** * Set the waiting indicator for this event to indicate that related event results are pending. - * It will be replaced by the actual results from the API when they are returned. + * It will be replaced by the actual results from the API when they are returned. */ currentStatsMap.set(resolverEvent, waitingForRelatedEventData); return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; } return state; } else if (action.type === 'serverFailedToReturnRelatedEventData') { - const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap) { const currentStatsMap = new Map(statsMap); @@ -55,8 +54,7 @@ export const dataReducer: Reducer = (state = initialS return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; } return state; - } - else if (action.type === 'serverReturnedRelatedEventData') { + } else if (action.type === 'serverReturnedRelatedEventData') { /** * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 * When this data is inlined with results, there won't be a need for this. @@ -72,11 +70,12 @@ export const dataReducer: Reducer = (state = initialS // do stats const statsForEntry = newStatsEntry?.relatedEvents.reduce( ( - a: Partial>, - v: { relatedEventType: RelatedEventType } + compiledStats: Partial>, + relatedEvent: { relatedEventType: RelatedEventType } ) => { - a[v.relatedEventType] = (a[v.relatedEventType] || 0) + 1; - return a; + compiledStats[relatedEvent.relatedEventType] = + (compiledStats[relatedEvent.relatedEventType] || 0) + 1; + return compiledStats; }, {} ); From 84ffd8be472ffaf06eef51a6cacea6f8715be6ee Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:31:31 -0400 Subject: [PATCH 32/74] document selector --- .../endpoint/public/embeddables/resolver/store/selectors.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts index 6168752983bb42..493c23621a6cf3 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/selectors.ts @@ -60,6 +60,9 @@ export const processAdjacencies = composeSelectors( dataSelectors.processAdjacencies ); +/** + * Returns a map of `ResolverEvent`s to their related `ResolverEvent`s + */ export const relatedEvents = composeSelectors(dataStateSelector, dataSelectors.relatedEvents); /** From 7fc4d0be776e0e5f228256137cfc3bd936f5e5c2 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:38:40 -0400 Subject: [PATCH 33/74] adding comments to types --- .../plugins/endpoint/public/embeddables/resolver/types.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 4dda0860c7ecf2..bff971f663c8bc 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -179,6 +179,13 @@ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { stats: Partial>; }; +/** + * The status or value of any particular event's related events w.r.t. their valence to the current view. + * One of: + * `RelatedEventDataEntryWithStats` when results have been received and processed and are ready to display + * `waitingForRelatedEventData` when related events have been requested but have not yet matriculated + * `Error` when the request for any event encounters an error during service + */ export type RelatedEventEntryWithStatsOrWaiting = | RelatedEventDataEntryWithStats | typeof waitingForRelatedEventData From 8c46844af29cd4ce5e41536d7d36278f62c232c0 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:41:38 -0400 Subject: [PATCH 34/74] linting --- x-pack/plugins/endpoint/public/embeddables/resolver/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index bff971f663c8bc..cabef065d0e179 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -184,7 +184,7 @@ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { * One of: * `RelatedEventDataEntryWithStats` when results have been received and processed and are ready to display * `waitingForRelatedEventData` when related events have been requested but have not yet matriculated - * `Error` when the request for any event encounters an error during service + * `Error` when the request for any event encounters an error during service */ export type RelatedEventEntryWithStatsOrWaiting = | RelatedEventDataEntryWithStats From f1863c5a69c774296fb89cab6567b9fb5f2b87a1 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:58:12 -0400 Subject: [PATCH 35/74] use built-in EUI loading indicator --- .../resolver/view/process_event_dot.tsx | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 9b51c522d26105..ea0393f2aa5b9c 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -71,6 +71,9 @@ const subMenuAssets = { initialMenuStatus: Symbol( 'The state of a Resolver submenu before it has been opened or requested data.' ), + menuError: Symbol( + 'The options in this submenu cannot be displayed because of an error' + ), relatedAlerts: { title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { defaultMessage: 'Related Alerts', @@ -91,7 +94,7 @@ type ResolverSubmenuOption = { type ResolverSubmenuOptionList = Array; const OptionList = React.memo( - ({ subMenuOptions }: { subMenuOptions: ResolverSubmenuOptionList }) => { + ({ subMenuOptions, isLoading }: { subMenuOptions: ResolverSubmenuOptionList; isLoading: boolean; }) => { const selectableOptions = subMenuOptions.map((opt: ResolverSubmenuOption): { label: string; prepend?: ReactNode; @@ -114,6 +117,7 @@ const OptionList = React.memo( setOptions(newOptions); }} listProps={{ showIcons: true, bordered: true }} + isLoading={isLoading} > {list => list} @@ -161,6 +165,10 @@ const NodeSubMenu = styled( return typeof optionsWithActions === 'object'; }, [optionsWithActions]) + const isMenuLoading = useMemo(()=>{ + return optionsWithActions === waitingForRelatedEventData; + }, [optionsWithActions]) + if (!optionsWithActions) { /** * When called with a `menuAction` @@ -191,11 +199,10 @@ const NodeSubMenu = styled( {menuTitle}
{menuIsOpen && isMenuDataAvailable && - (typeof optionsWithActions === 'symbol' ? ( - 'loading' - ) : ( - - ))} + ( + + ) + }
); } @@ -216,7 +223,7 @@ const NodeSubMenu = styled( `; /** - * An artefact that represents a process node. + * An artifact that represents a process node. */ export const ProcessEventDot = styled( React.memo( @@ -270,6 +277,9 @@ export const ProcessEventDot = styled( if(!relatedEvents){ return subMenuAssets.initialMenuStatus } + if(relatedEvents instanceof Error){ + return subMenuAssets.menuError; + } if(relatedEvents === waitingForRelatedEventData) { return relatedEvents } @@ -378,7 +388,7 @@ export const ProcessEventDot = styled( const handleRelatedEventRequest = useCallback( () => { dispatch({ - type: 'appRequestedRelatedEventData', + type: 'userRequestedRelatedEventData', payload: event }); }, From 0620a86413304bfc8ddf4b2a869b01726362f8f7 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 17:58:32 -0400 Subject: [PATCH 36/74] linting --- .../resolver/view/process_event_dot.tsx | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index ea0393f2aa5b9c..02e3d35e46ed21 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -71,9 +71,7 @@ const subMenuAssets = { initialMenuStatus: Symbol( 'The state of a Resolver submenu before it has been opened or requested data.' ), - menuError: Symbol( - 'The options in this submenu cannot be displayed because of an error' - ), + menuError: Symbol('The options in this submenu cannot be displayed because of an error'), relatedAlerts: { title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { defaultMessage: 'Related Alerts', @@ -86,27 +84,35 @@ const subMenuAssets = { }, }; -type ResolverSubmenuOption = { +interface ResolverSubmenuOption { optionTitle: string; action: () => unknown; prefix?: number | JSX.Element; } -type ResolverSubmenuOptionList = Array; +type ResolverSubmenuOptionList = ResolverSubmenuOption[]; const OptionList = React.memo( - ({ subMenuOptions, isLoading }: { subMenuOptions: ResolverSubmenuOptionList; isLoading: boolean; }) => { + ({ + subMenuOptions, + isLoading, + }: { + subMenuOptions: ResolverSubmenuOptionList; + isLoading: boolean; + }) => { const selectableOptions = subMenuOptions.map((opt: ResolverSubmenuOption): { label: string; prepend?: ReactNode; } => { - return opt.prefix ? { - label: opt.optionTitle, - prepend: ({opt.prefix}), - } : { - label: opt.optionTitle, - prepend: (), - }; - }) + return opt.prefix + ? { + label: opt.optionTitle, + prepend: {opt.prefix}, + } + : { + label: opt.optionTitle, + prepend: , + }; + }); const [options, setOptions] = useState(selectableOptions); return useMemo( () => ( @@ -122,7 +128,7 @@ const OptionList = React.memo( {list => list} ), - [options] + [isLoading, options] ); } ); @@ -134,7 +140,7 @@ const NodeSubMenu = styled( menuAction, optionsWithActions, className, - }: { menuTitle: string; className?: string; menuAction: () => unknown; } & ( + }: { menuTitle: string; className?: string; menuAction: () => unknown } & ( | { optionsWithActions: | ResolverSubmenuOptionList @@ -150,7 +156,7 @@ const NodeSubMenu = styled( clickEvent.stopPropagation(); setMenuOpen(!menuIsOpen); }, - [menuIsOpen, optionsWithActions] + [menuIsOpen] ); const handleMenuActionClick = useCallback( (clickEvent: React.MouseEvent) => { @@ -161,13 +167,13 @@ const NodeSubMenu = styled( }, [menuAction] ); - const isMenuDataAvailable = useMemo(()=>{ - return typeof optionsWithActions === 'object'; - }, [optionsWithActions]) + const isMenuDataAvailable = useMemo(() => { + return typeof optionsWithActions === 'object'; + }, [optionsWithActions]); - const isMenuLoading = useMemo(()=>{ - return optionsWithActions === waitingForRelatedEventData; - }, [optionsWithActions]) + const isMenuLoading = useMemo(() => { + return optionsWithActions === waitingForRelatedEventData; + }, [optionsWithActions]); if (!optionsWithActions) { /** @@ -176,7 +182,7 @@ const NodeSubMenu = styled( */ return (
- + {menuTitle}
@@ -192,17 +198,15 @@ const NodeSubMenu = styled( onClick={isMenuDataAvailable ? handleMenuOpenClick : handleMenuActionClick} color="ghost" size="s" - iconType={(menuIsOpen ? 'arrowUp' : 'arrowDown')} + iconType={menuIsOpen ? 'arrowUp' : 'arrowDown'} iconSide="right" tabIndex={-1} > {menuTitle} - {menuIsOpen && isMenuDataAvailable && - ( - - ) - } + {menuIsOpen && isMenuDataAvailable && ( + + )} ); } @@ -273,17 +277,17 @@ export const ProcessEventDot = styled( const activeDescendantId = useSelector(selectors.uiActiveDescendantId); const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId); - const relatedEventOptions = useMemo(()=>{ - if(!relatedEvents){ - return subMenuAssets.initialMenuStatus + const relatedEventOptions = useMemo(() => { + if (!relatedEvents) { + return subMenuAssets.initialMenuStatus; } - if(relatedEvents instanceof Error){ + if (relatedEvents instanceof Error) { return subMenuAssets.menuError; } - if(relatedEvents === waitingForRelatedEventData) { - return relatedEvents + if (relatedEvents === waitingForRelatedEventData) { + return relatedEvents; } - return Object.entries(relatedEvents.stats).map((k,v) => { + return Object.entries(relatedEvents.stats).map((k, v) => { return { prefix: k[1], optionTitle: `${k[0]}`, @@ -292,10 +296,10 @@ export const ProcessEventDot = styled( * COMING SOON TO A THEATER NEAR YOU: * OPENING SIDE MENUS */ - } - } - }) - },[relatedEvents]) + }, + }; + }); + }, [relatedEvents]); const nodeViewportStyle = useMemo( () => ({ @@ -371,7 +375,6 @@ export const ProcessEventDot = styled( const handleClick = useCallback( (clickEvent: React.MouseEvent) => { - if (animationTarget.current !== null) { (animationTarget.current as any).beginElement(); } @@ -385,15 +388,12 @@ export const ProcessEventDot = styled( [animationTarget, dispatch, nodeId] ); - const handleRelatedEventRequest = useCallback( - () => { - dispatch({ - type: 'userRequestedRelatedEventData', - payload: event - }); - }, - [dispatch, event, selfId] - ); + const handleRelatedEventRequest = useCallback(() => { + dispatch({ + type: 'userRequestedRelatedEventData', + payload: event, + }); + }, [dispatch, event]); /* eslint-disable jsx-a11y/click-events-have-key-events */ /** @@ -532,7 +532,7 @@ export const ProcessEventDot = styled( {}} + menuAction={() => {}} /> @@ -576,7 +576,7 @@ export const ProcessEventDot = styled( & .related-dropdown { width: 4.5em; - } + } & .euiSelectableList-bordered { border-top-right-radius: 0px; border-top-left-radius: 0px; From ea3aa8574bdfc47dfffa4413c956f848c91d95d8 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Fri, 8 May 2020 18:11:57 -0400 Subject: [PATCH 37/74] node cleanup --- .../resolver/view/process_event_dot.tsx | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 02e3d35e46ed21..0480012bb790f6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -89,7 +89,7 @@ interface ResolverSubmenuOption { action: () => unknown; prefix?: number | JSX.Element; } -type ResolverSubmenuOptionList = ResolverSubmenuOption[]; +type ResolverSubmenuOptionList = ResolverSubmenuOption[] | symbol; const OptionList = React.memo( ({ @@ -99,20 +99,23 @@ const OptionList = React.memo( subMenuOptions: ResolverSubmenuOptionList; isLoading: boolean; }) => { - const selectableOptions = subMenuOptions.map((opt: ResolverSubmenuOption): { - label: string; - prepend?: ReactNode; - } => { - return opt.prefix - ? { - label: opt.optionTitle, - prepend: {opt.prefix}, - } - : { - label: opt.optionTitle, - prepend: , - }; - }); + const selectableOptions = + typeof subMenuOptions === 'symbol' + ? [] + : subMenuOptions.map((opt: ResolverSubmenuOption): { + label: string; + prepend?: ReactNode; + } => { + return opt.prefix + ? { + label: opt.optionTitle, + prepend: {opt.prefix}, + } + : { + label: opt.optionTitle, + prepend: , + }; + }); const [options, setOptions] = useState(selectableOptions); return useMemo( () => ( From 4842833a2b035699e3b5f16e0e894dc50631c211 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 10:21:19 -0400 Subject: [PATCH 38/74] add related category select action --- .../embeddables/resolver/store/actions.ts | 11 +++- .../resolver/view/process_event_dot.tsx | 51 ++++++++++--------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index 0fa34dad8ba309..bf49787abfd31e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -86,6 +86,14 @@ interface UserSelectedResolverNode { }; } +interface UserSelectedRelatedEventCategory { + readonly type: 'userSelectedRelatedEventCategory'; + readonly payload: { + subject: ResolverEvent; + category: string; + }; +} + export type ResolverAction = | CameraAction | DataAction @@ -94,4 +102,5 @@ export type ResolverAction = | AppRequestedResolverData | UserFocusedOnResolverNode | UserSelectedResolverNode - | UserRequestedRelatedEventData; + | UserRequestedRelatedEventData + | UserSelectedRelatedEventCategory; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 0480012bb790f6..f231f0192d33b6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -280,30 +280,6 @@ export const ProcessEventDot = styled( const activeDescendantId = useSelector(selectors.uiActiveDescendantId); const selectedDescendantId = useSelector(selectors.uiSelectedDescendantId); - const relatedEventOptions = useMemo(() => { - if (!relatedEvents) { - return subMenuAssets.initialMenuStatus; - } - if (relatedEvents instanceof Error) { - return subMenuAssets.menuError; - } - if (relatedEvents === waitingForRelatedEventData) { - return relatedEvents; - } - return Object.entries(relatedEvents.stats).map((k, v) => { - return { - prefix: k[1], - optionTitle: `${k[0]}`, - action: () => { - /** - * COMING SOON TO A THEATER NEAR YOU: - * OPENING SIDE MENUS - */ - }, - }; - }); - }, [relatedEvents]); - const nodeViewportStyle = useMemo( () => ({ left: `${left}px`, @@ -364,6 +340,33 @@ export const ProcessEventDot = styled( const dispatch = useResolverDispatch(); + const relatedEventOptions = useMemo(() => { + if (!relatedEvents) { + return subMenuAssets.initialMenuStatus; + } + if (relatedEvents instanceof Error) { + return subMenuAssets.menuError; + } + if (relatedEvents === waitingForRelatedEventData) { + return relatedEvents; + } + return Object.entries(relatedEvents.stats).map(k => { + return { + prefix: k[1], + optionTitle: `${k[0]}`, + action: () => { + dispatch({ + type: 'userSelectedRelatedEventCategory', + payload: { + subject: event, + category: k[0], + }, + }); + }, + }; + }); + }, [relatedEvents, dispatch, event]); + const handleFocus = useCallback( (focusEvent: React.FocusEvent) => { dispatch({ From 03237e672508984f96b95e572c42bb059e1e7a18 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 10:34:08 -0400 Subject: [PATCH 39/74] Move submenu to import to clear up event_dot --- .../resolver/view/process_event_dot.tsx | 174 +----------------- .../embeddables/resolver/view/submenu.tsx | 174 ++++++++++++++++++ 2 files changed, 177 insertions(+), 171 deletions(-) create mode 100644 x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index f231f0192d33b6..8bfb521408723a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -4,18 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useMemo, useState, ReactNode } from 'react'; +import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; -import { - htmlIdGenerator, - EuiKeyboardAccessible, - EuiButton, - EuiFlexGroup, - EuiFlexItem, - EuiSelectable, -} from '@elastic/eui'; +import { htmlIdGenerator, EuiKeyboardAccessible, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useSelector } from 'react-redux'; +import { NodeSubMenu, subMenuAssets } from './submenu'; import { applyMatrix3 } from '../lib/vector2'; import { Vector2, @@ -67,168 +61,6 @@ const nodeAssets = { }, }; -const subMenuAssets = { - initialMenuStatus: Symbol( - 'The state of a Resolver submenu before it has been opened or requested data.' - ), - menuError: Symbol('The options in this submenu cannot be displayed because of an error'), - relatedAlerts: { - title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { - defaultMessage: 'Related Alerts', - }), - }, - relatedEvents: { - title: i18n.translate('xpack.endpoint.resolver.relatedEvents', { - defaultMessage: 'Events', - }), - }, -}; - -interface ResolverSubmenuOption { - optionTitle: string; - action: () => unknown; - prefix?: number | JSX.Element; -} -type ResolverSubmenuOptionList = ResolverSubmenuOption[] | symbol; - -const OptionList = React.memo( - ({ - subMenuOptions, - isLoading, - }: { - subMenuOptions: ResolverSubmenuOptionList; - isLoading: boolean; - }) => { - const selectableOptions = - typeof subMenuOptions === 'symbol' - ? [] - : subMenuOptions.map((opt: ResolverSubmenuOption): { - label: string; - prepend?: ReactNode; - } => { - return opt.prefix - ? { - label: opt.optionTitle, - prepend: {opt.prefix}, - } - : { - label: opt.optionTitle, - prepend: , - }; - }); - const [options, setOptions] = useState(selectableOptions); - return useMemo( - () => ( - { - setOptions(newOptions); - }} - listProps={{ showIcons: true, bordered: true }} - isLoading={isLoading} - > - {list => list} - - ), - [isLoading, options] - ); - } -); - -const NodeSubMenu = styled( - React.memo( - ({ - menuTitle, - menuAction, - optionsWithActions, - className, - }: { menuTitle: string; className?: string; menuAction: () => unknown } & ( - | { - optionsWithActions: - | ResolverSubmenuOptionList - | typeof subMenuAssets.initialMenuStatus - | typeof waitingForRelatedEventData; - } - | { optionsWithActions?: undefined } - )) => { - const [menuIsOpen, setMenuOpen] = useState(false); - const handleMenuOpenClick = useCallback( - (clickEvent: React.MouseEvent) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - setMenuOpen(!menuIsOpen); - }, - [menuIsOpen] - ); - const handleMenuActionClick = useCallback( - (clickEvent: React.MouseEvent) => { - clickEvent.preventDefault(); - clickEvent.stopPropagation(); - if (typeof menuAction === 'function') menuAction(); - setMenuOpen(true); - }, - [menuAction] - ); - const isMenuDataAvailable = useMemo(() => { - return typeof optionsWithActions === 'object'; - }, [optionsWithActions]); - - const isMenuLoading = useMemo(() => { - return optionsWithActions === waitingForRelatedEventData; - }, [optionsWithActions]); - - if (!optionsWithActions) { - /** - * When called with a `menuAction` - * Render without dropdown and call the supplied action when host button is clicked - */ - return ( -
- - {menuTitle} - -
- ); - } - /** - * When called with a set of `optionsWithActions`: - * Render with a panel of options that appear when the menu host button is clicked - */ - return ( -
- - {menuTitle} - - {menuIsOpen && isMenuDataAvailable && ( - - )} -
- ); - } - ) -)` - margin: 0; - padding: 0; - border: none; - display: flex; - flex-flow: column; - &.is-open .euiButton { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - &.is-open .euiSelectableListItem__prepend { - color: white; - } -`; - /** * An artifact that represents a process node. */ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx new file mode 100644 index 00000000000000..cd9a019f7773a3 --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -0,0 +1,174 @@ +/* + * 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'; +import React, { ReactNode, useState, useMemo, useCallback } from 'react'; +import { EuiSelectable, EuiButton } from '@elastic/eui'; +import styled from 'styled-components'; +import { waitingForRelatedEventData } from '../types'; + +export const subMenuAssets = { + initialMenuStatus: Symbol( + 'The state of a Resolver submenu before it has been opened or requested data.' + ), + menuError: Symbol('The options in this submenu cannot be displayed because of an error'), + relatedAlerts: { + title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { + defaultMessage: 'Related Alerts', + }), + }, + relatedEvents: { + title: i18n.translate('xpack.endpoint.resolver.relatedEvents', { + defaultMessage: 'Events', + }), + }, +}; + +interface ResolverSubmenuOption { + optionTitle: string; + action: () => unknown; + prefix?: number | JSX.Element; +} + +export type ResolverSubmenuOptionList = ResolverSubmenuOption[] | symbol; + +const OptionList = React.memo( + ({ + subMenuOptions, + isLoading, + }: { + subMenuOptions: ResolverSubmenuOptionList; + isLoading: boolean; + }) => { + const selectableOptions = + typeof subMenuOptions === 'symbol' + ? [] + : subMenuOptions.map((opt: ResolverSubmenuOption): { + label: string; + prepend?: ReactNode; + } => { + return opt.prefix + ? { + label: opt.optionTitle, + prepend: {opt.prefix}, + } + : { + label: opt.optionTitle, + prepend: , + }; + }); + const [options, setOptions] = useState(selectableOptions); + return useMemo( + () => ( + { + setOptions(newOptions); + }} + listProps={{ showIcons: true, bordered: true }} + isLoading={isLoading} + > + {list => list} + + ), + [isLoading, options] + ); + } +); + +export const NodeSubMenu = styled( + React.memo( + ({ + menuTitle, + menuAction, + optionsWithActions, + className, + }: { menuTitle: string; className?: string; menuAction: () => unknown } & ( + | { + optionsWithActions: + | ResolverSubmenuOptionList + | typeof subMenuAssets.initialMenuStatus + | typeof waitingForRelatedEventData; + } + | { optionsWithActions?: undefined } + )) => { + const [menuIsOpen, setMenuOpen] = useState(false); + const handleMenuOpenClick = useCallback( + (clickEvent: React.MouseEvent) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + setMenuOpen(!menuIsOpen); + }, + [menuIsOpen] + ); + const handleMenuActionClick = useCallback( + (clickEvent: React.MouseEvent) => { + clickEvent.preventDefault(); + clickEvent.stopPropagation(); + if (typeof menuAction === 'function') menuAction(); + setMenuOpen(true); + }, + [menuAction] + ); + const isMenuDataAvailable = useMemo(() => { + return typeof optionsWithActions === 'object'; + }, [optionsWithActions]); + + const isMenuLoading = useMemo(() => { + return optionsWithActions === waitingForRelatedEventData; + }, [optionsWithActions]); + + if (!optionsWithActions) { + /** + * When called with a `menuAction` + * Render without dropdown and call the supplied action when host button is clicked + */ + return ( +
+ + {menuTitle} + +
+ ); + } + /** + * When called with a set of `optionsWithActions`: + * Render with a panel of options that appear when the menu host button is clicked + */ + return ( +
+ + {menuTitle} + + {menuIsOpen && isMenuDataAvailable && ( + + )} +
+ ); + } + ) +)` + margin: 0; + padding: 0; + border: none; + display: flex; + flex-flow: column; + &.is-open .euiButton { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + &.is-open .euiSelectableListItem__prepend { + color: white; + } +`; From 1d09e381b990b5c881fe6aed3d2171ee866ef25b Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 10:59:06 -0400 Subject: [PATCH 40/74] 18n format for number --- .../resolver/view/process_event_dot.tsx | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 8bfb521408723a..aef1fb11e4eca7 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -7,7 +7,13 @@ import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; import { i18n } from '@kbn/i18n'; -import { htmlIdGenerator, EuiKeyboardAccessible, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + htmlIdGenerator, + EuiI18nNumber, + EuiKeyboardAccessible, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; import { useSelector } from 'react-redux'; import { NodeSubMenu, subMenuAssets } from './submenu'; import { applyMatrix3 } from '../lib/vector2'; @@ -172,33 +178,6 @@ export const ProcessEventDot = styled( const dispatch = useResolverDispatch(); - const relatedEventOptions = useMemo(() => { - if (!relatedEvents) { - return subMenuAssets.initialMenuStatus; - } - if (relatedEvents instanceof Error) { - return subMenuAssets.menuError; - } - if (relatedEvents === waitingForRelatedEventData) { - return relatedEvents; - } - return Object.entries(relatedEvents.stats).map(k => { - return { - prefix: k[1], - optionTitle: `${k[0]}`, - action: () => { - dispatch({ - type: 'userSelectedRelatedEventCategory', - payload: { - subject: event, - category: k[0], - }, - }); - }, - }; - }); - }, [relatedEvents, dispatch, event]); - const handleFocus = useCallback( (focusEvent: React.FocusEvent) => { dispatch({ @@ -232,6 +211,37 @@ export const ProcessEventDot = styled( payload: event, }); }, [dispatch, event]); + /** + * Enumerates the stats for related events to display with the node as options, + * generally in the form `number of related events in category` `category title` + * e.g. "10 DNS", "230 File" + */ + const relatedEventOptions = useMemo(() => { + if (!relatedEvents) { + return subMenuAssets.initialMenuStatus; + } + if (relatedEvents instanceof Error) { + return subMenuAssets.menuError; + } + if (relatedEvents === waitingForRelatedEventData) { + return relatedEvents; + } + return Object.entries(relatedEvents.stats).map(statsEntry => { + return { + prefix: , + optionTitle: `${statsEntry[0]}`, + action: () => { + dispatch({ + type: 'userSelectedRelatedEventCategory', + payload: { + subject: event, + category: statsEntry[0], + }, + }); + }, + }; + }); + }, [relatedEvents, dispatch, event]); /* eslint-disable jsx-a11y/click-events-have-key-events */ /** From 7395f6d51ea6042e65aa13f96c6548760c32e38a Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 11:11:52 -0400 Subject: [PATCH 41/74] add comments to submenu --- .../public/embeddables/resolver/view/submenu.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx index cd9a019f7773a3..0412144152c2fe 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -10,6 +10,11 @@ import { EuiSelectable, EuiButton } from '@elastic/eui'; import styled from 'styled-components'; import { waitingForRelatedEventData } from '../types'; +/** + * i18n-translated titles for submenus and identifiers for display of states: + * initialMenuStatus: submenu before it has been opened / requested data + * menuError: if the submenu requested data, but received an error + */ export const subMenuAssets = { initialMenuStatus: Symbol( 'The state of a Resolver submenu before it has been opened or requested data.' @@ -80,6 +85,11 @@ const OptionList = React.memo( } ); +/** + * A Submenu to be displayed in one of two forms: + * 1) Provided a collection of `optionsWithActions`: it will call `menuAction` then - if and when menuData becomes available - display each item with an optional prefix and call the supplied action for the options when that option is clicked. + * 2) Provided `optionsWithActions` is undefined, it will call the supplied `menuAction` when its host button is clicked. + */ export const NodeSubMenu = styled( React.memo( ({ From ea0c43619894d71076d33af2ebdd6a15f4a62a69 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 11:29:10 -0400 Subject: [PATCH 42/74] add related alerts action --- .../public/embeddables/resolver/store/actions.ts | 8 +++++++- .../embeddables/resolver/view/process_event_dot.tsx | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index bf49787abfd31e..58ef81d328b89b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -94,6 +94,11 @@ interface UserSelectedRelatedEventCategory { }; } +interface UserSelectedRelatedAlerts { + readonly type: 'userSelectedRelatedAlerts'; + readonly payload: ResolverEvent; +} + export type ResolverAction = | CameraAction | DataAction @@ -103,4 +108,5 @@ export type ResolverAction = | UserFocusedOnResolverNode | UserSelectedResolverNode | UserRequestedRelatedEventData - | UserSelectedRelatedEventCategory; + | UserSelectedRelatedEventCategory + | UserSelectedRelatedAlerts; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index aef1fb11e4eca7..b6bb6b595fae77 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -211,6 +211,13 @@ export const ProcessEventDot = styled( payload: event, }); }, [dispatch, event]); + + const handleRelatedAlertsRequest = useCallback(() => { + dispatch({ + type: 'userSelectedRelatedAlerts', + payload: event, + }); + }, [dispatch, event]); /** * Enumerates the stats for related events to display with the node as options, * generally in the form `number of related events in category` `category title` @@ -380,7 +387,7 @@ export const ProcessEventDot = styled( {}} + menuAction={handleRelatedAlertsRequest} /> From 211df540cb505d30e64290857f50437006597af5 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 12:38:20 -0400 Subject: [PATCH 43/74] lint selector --- .../resolver/store/data/selectors.ts | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index ee2850eeaa6599..9f4f6447adb917 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -413,27 +413,31 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in export const relatedEventStats = createSelector( (data: DataState) => data, function(data) { - return data[resultsEnrichedWithRelatedEventInfo] + return data[resultsEnrichedWithRelatedEventInfo]; } ); -export const relatedEvents = createSelector(graphableProcesses, relatedEventStats, function getRelatedEvents( - /* eslint-disable no-shadow */ +export const relatedEvents = createSelector( graphableProcesses, relatedEventStats, - /* eslint-enable no-shadow */ -) { - const eventsRelatedByProcess: RelatedEventData = new Map(); - /* eslint-disable no-shadow */ - return graphableProcesses.reduce((relatedEvents, graphableProcess) => { + function getRelatedEvents( + /* eslint-disable no-shadow */ + graphableProcesses, + relatedEventStats /* eslint-enable no-shadow */ - const relatedEventDataEntry = relatedEventStats?.get(graphableProcess) - if(relatedEventDataEntry){ - relatedEvents.set(graphableProcess, relatedEventDataEntry); - } - return relatedEvents; - }, eventsRelatedByProcess); -}); + ) { + const eventsRelatedByProcess: RelatedEventData = new Map(); + /* eslint-disable no-shadow */ + return graphableProcesses.reduce((relatedEvents, graphableProcess) => { + /* eslint-enable no-shadow */ + const relatedEventDataEntry = relatedEventStats?.get(graphableProcess); + if (relatedEventDataEntry) { + relatedEvents.set(graphableProcess, relatedEventDataEntry); + } + return relatedEvents; + }, eventsRelatedByProcess); + } +); export const processAdjacencies = createSelector( indexedProcessTree, From e2d87f4571f1dbf44004f0b361e120609f03d548 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 15:39:44 -0400 Subject: [PATCH 44/74] D Plumlee review: remove unnecessary bindings to type --- .../endpoint/public/embeddables/resolver/store/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 88da2f6cfc7246..1b42a41fb09276 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -35,7 +35,7 @@ type RelatedEventAPIResponse = Error | { events: ResolverEvent[] }; async function* getEachRelatedEventsResult( eventsToFetch: ResolverEvent[], httpGetter: HttpHandler -): AsyncGenerator<[ResolverEvent, RelatedEventAPIResponse], any, any> { +): AsyncGenerator<[ResolverEvent, RelatedEventAPIResponse]> { for (const eventToQueryForRelateds of eventsToFetch) { const id = event.entityId(eventToQueryForRelateds); const relatedEventError = new Error(`Error fetching related events for entity=${id}`); From fe22f984589afc8852ea4307b650ca1d994df5b9 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 15:46:53 -0400 Subject: [PATCH 45/74] D Plumlee review: remove comments --- .../endpoint/public/embeddables/resolver/store/middleware.ts | 5 +---- x-pack/plugins/endpoint/public/embeddables/resolver/types.ts | 2 -- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 1b42a41fb09276..d71a27bf19ddbf 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -99,10 +99,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { } } } - /** - * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 - * When this data is inlined with results, there won't be a need for this. - */ + if (action.type === 'userRequestedRelatedEventData') { if (typeof context !== 'undefined') { for await (const results of getEachRelatedEventsResult( diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index cabef065d0e179..228e64831033f9 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -150,8 +150,6 @@ export type EventCategory = RelatedEventType | ('Alert' | 'Process' | 'Security' /** * This symbol is used to tag results with Related event info - * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 - * When this data is inlined with results, there won't be a need for this. */ export const resultsEnrichedWithRelatedEventInfo = `resultsEnrichedWithRelatedEventInfo`; /** From c0bcc2514670ca84b67ba89a58470b7d69728d9b Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 11 May 2020 16:33:15 -0400 Subject: [PATCH 46/74] add comments to relatedEventOptions breakouts --- x-pack/plugins/endpoint/common/models/event.ts | 2 +- .../public/embeddables/resolver/view/process_event_dot.tsx | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/common/models/event.ts b/x-pack/plugins/endpoint/common/models/event.ts index 7e098b9b451f16..7b5f4b2d190ec9 100644 --- a/x-pack/plugins/endpoint/common/models/event.ts +++ b/x-pack/plugins/endpoint/common/models/event.ts @@ -49,7 +49,7 @@ export function parentEntityId(event: ResolverEvent): string | undefined { } export function eventCategoryDisplayName(event: ResolverEvent): EventCategory { - const eventTypeToNameMap = new Map([ + const eventTypeToNameMap = new Map([ ['process', 'Process'], ['alert', 'Alert'], ['security', 'Security'], diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 7e814400b9b227..f5c60c3c15152d 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -226,14 +226,19 @@ export const ProcessEventDot = styled( */ const relatedEventOptions = useMemo(() => { if (!relatedEvents) { + //If related events have not yet been requested return subMenuAssets.initialMenuStatus; } if (relatedEvents instanceof Error) { + //If there was an error when we tried to request the events return subMenuAssets.menuError; } if (relatedEvents === waitingForRelatedEventData) { + //If we're waiting for events to be returned + //Pass on the waiting symbol return relatedEvents; } + //If we have entries to show, map them into options to display in the selectable list return Object.entries(relatedEvents.stats).map(statsEntry => { return { prefix: , From e375fa7b4fd4c7241d3da1ecb0c4566c4e15be52 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 12 May 2020 00:11:07 -0400 Subject: [PATCH 47/74] R Austin review: move logic from reducer to selector --- .../resolver/store/data/reducer.ts | 35 +------------- .../resolver/store/data/selectors.ts | 46 ++++++++++++++++++- .../public/embeddables/resolver/types.ts | 12 ++++- .../resolver/view/process_event_dot.tsx | 10 ++-- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 969068e1324b04..5bf6983918aa02 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -9,9 +9,6 @@ import { DataState, ResolverAction, resultsEnrichedWithRelatedEventInfo, - RelatedEventDataEntryWithStats, - RelatedEventType, - RelatedEventData, waitingForRelatedEventData, } from '../../types'; @@ -55,38 +52,10 @@ export const dataReducer: Reducer = (state = initialS } return state; } else if (action.type === 'serverReturnedRelatedEventData') { - /** - * REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379 - * When this data is inlined with results, there won't be a need for this. - */ const statsMap = state[resultsEnrichedWithRelatedEventInfo]; - if (statsMap && typeof statsMap?.set === 'function') { - const currentStatsMap: RelatedEventData = new Map(statsMap); - for (const updatedEvent of action.payload.keys()) { - const newStatsEntry = action.payload.get(updatedEvent); - - if (newStatsEntry) { - // do stats - const statsForEntry = newStatsEntry?.relatedEvents.reduce( - ( - compiledStats: Partial>, - relatedEvent: { relatedEventType: RelatedEventType } - ) => { - compiledStats[relatedEvent.relatedEventType] = - (compiledStats[relatedEvent.relatedEventType] || 0) + 1; - return compiledStats; - }, - {} - ); - const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign( - newStatsEntry, - { stats: statsForEntry } - ); - currentStatsMap.set(updatedEvent, newRelatedEventStats); - } - } - return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; + const relatedDataEntries = new Map([...statsMap, ...action.payload]); + return { ...state, [resultsEnrichedWithRelatedEventInfo]: relatedDataEntries }; } return state; } else if (action.type === 'appRequestedResolverData') { diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 9f4f6447adb917..9d08bd7c415649 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -16,6 +16,8 @@ import { AdjacentProcessMap, RelatedEventData, resultsEnrichedWithRelatedEventInfo, + RelatedEventType, + RelatedEventDataEntryWithStats, } from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { Vector2 } from '../../types'; @@ -410,13 +412,55 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in /** * Process events that will be graphed. */ -export const relatedEventStats = createSelector( +export const relatedEventResults = createSelector( (data: DataState) => data, function(data) { return data[resultsEnrichedWithRelatedEventInfo]; } ); +export const relatedEventStats = createSelector(relatedEventResults, function getRelatedEvents( + /* eslint-disable no-shadow */ + relatedEventResults + /* eslint-enable no-shadow */ +) { + /* eslint-disable no-shadow */ + const relatedEventStats: RelatedEventData = new Map(); + /* eslint-enable no-shadow */ + if (!relatedEventResults) { + return relatedEventStats; + } + + for (const updatedEvent of relatedEventResults.keys()) { + + const newStatsEntry = relatedEventResults.get(updatedEvent); + if (typeof newStatsEntry === 'object') { + // compile stats + if (newStatsEntry instanceof Error) { + relatedEventStats.set(updatedEvent, newStatsEntry); + continue; + } + const statsForEntry = newStatsEntry?.relatedEvents.reduce( + ( + compiledStats: Partial>, + relatedEvent: { relatedEventType: RelatedEventType } + ) => { + compiledStats[relatedEvent.relatedEventType] = + (compiledStats[relatedEvent.relatedEventType] || 0) + 1; + return compiledStats; + }, + {} + ); + + const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign(newStatsEntry, { + stats: statsForEntry, + }); + relatedEventStats.set(updatedEvent, newRelatedEventStats); + } + } + return relatedEventStats; +}); + export const relatedEvents = createSelector( graphableProcesses, relatedEventStats, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 228e64831033f9..9eeb0dc2f5a8cb 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -169,6 +169,16 @@ export interface RelatedEventDataEntry { relatedEventType: RelatedEventType; }>; } + +/** + * Represents the status of the request for related event data, which will be either the data, + * a value indicating that it's still waiting for the data or an Error indicating the data can't be retrieved as expected + */ +export type RelatedEventDataResults = + | RelatedEventDataEntry + | typeof waitingForRelatedEventData + | Error; + /** * This represents the raw related events data enhanced with statistics * (e.g. counts of items grouped by their related event types) @@ -202,7 +212,7 @@ export interface DataState { readonly results: readonly ResolverEvent[]; isLoading: boolean; hasError: boolean; - [resultsEnrichedWithRelatedEventInfo]?: RelatedEventData; + [resultsEnrichedWithRelatedEventInfo]?: Map; } export type Vector2 = readonly [number, number]; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index f5c60c3c15152d..34e072cbf10fc3 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -226,19 +226,19 @@ export const ProcessEventDot = styled( */ const relatedEventOptions = useMemo(() => { if (!relatedEvents) { - //If related events have not yet been requested + // If related events have not yet been requested return subMenuAssets.initialMenuStatus; } if (relatedEvents instanceof Error) { - //If there was an error when we tried to request the events + // If there was an error when we tried to request the events return subMenuAssets.menuError; } if (relatedEvents === waitingForRelatedEventData) { - //If we're waiting for events to be returned - //Pass on the waiting symbol + // If we're waiting for events to be returned + // Pass on the waiting symbol return relatedEvents; } - //If we have entries to show, map them into options to display in the selectable list + // If we have entries to show, map them into options to display in the selectable list return Object.entries(relatedEvents.stats).map(statsEntry => { return { prefix: , From eaea02e419ea221aef719f67d27b2faccd88683d Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 12 May 2020 00:12:25 -0400 Subject: [PATCH 48/74] linting --- .../public/embeddables/resolver/store/data/selectors.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 9d08bd7c415649..d3be5d6dc17d6d 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -424,15 +424,14 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge relatedEventResults /* eslint-enable no-shadow */ ) { - /* eslint-disable no-shadow */ + /* eslint-disable no-shadow */ const relatedEventStats: RelatedEventData = new Map(); - /* eslint-enable no-shadow */ + /* eslint-enable no-shadow */ if (!relatedEventResults) { return relatedEventStats; } - + for (const updatedEvent of relatedEventResults.keys()) { - const newStatsEntry = relatedEventResults.get(updatedEvent); if (typeof newStatsEntry === 'object') { // compile stats @@ -451,7 +450,7 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge }, {} ); - + const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign(newStatsEntry, { stats: statsForEntry, }); From 7bb0c308d0631cdcb0501c24f692cd4abbaba8f5 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 12 May 2020 00:41:52 -0400 Subject: [PATCH 49/74] D Plumlee / R Austin review: i18n for event categories --- .../plugins/endpoint/common/models/event.ts | 92 ++++++++++++++++--- 1 file changed, 79 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/endpoint/common/models/event.ts b/x-pack/plugins/endpoint/common/models/event.ts index 7b5f4b2d190ec9..3464f44e0b10e1 100644 --- a/x-pack/plugins/endpoint/common/models/event.ts +++ b/x-pack/plugins/endpoint/common/models/event.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { LegacyEndpointEvent, ResolverEvent } from '../types'; import { EventCategory } from '../../public/embeddables/resolver/types'; @@ -50,19 +51,84 @@ export function parentEntityId(event: ResolverEvent): string | undefined { export function eventCategoryDisplayName(event: ResolverEvent): EventCategory { const eventTypeToNameMap = new Map([ - ['process', 'Process'], - ['alert', 'Alert'], - ['security', 'Security'], - ['file', 'File'], - ['network', 'Network'], - ['registry', 'Registry'], - ['dns', 'DNS'], - ['clr', 'CLR'], - ['image_load', 'Image Load'], - ['powershell', 'Powershell'], - ['wmi', 'WMI'], - ['api', 'API'], - ['user', 'User'], + [ + 'process', + i18n.translate('xpack.endpoint.resolver.Process', { + defaultMessage: 'Process', + }) as EventCategory, + ], + [ + 'alert', + i18n.translate('xpack.endpoint.resolver.Alert', { + defaultMessage: 'Alert', + }) as EventCategory, + ], + [ + 'security', + i18n.translate('xpack.endpoint.resolver.Security', { + defaultMessage: 'Security', + }) as EventCategory, + ], + [ + 'file', + i18n.translate('xpack.endpoint.resolver.File', { + defaultMessage: 'File', + }) as EventCategory, + ], + [ + 'network', + i18n.translate('xpack.endpoint.resolver.Network', { + defaultMessage: 'Network', + }) as EventCategory, + ], + [ + 'registry', + i18n.translate('xpack.endpoint.resolver.Registry', { + defaultMessage: 'Registry', + }) as EventCategory, + ], + [ + 'dns', + i18n.translate('xpack.endpoint.resolver.DNS', { + defaultMessage: 'DNS', + }) as EventCategory, + ], + [ + 'clr', + i18n.translate('xpack.endpoint.resolver.CLR', { + defaultMessage: 'CLR', + }) as EventCategory, + ], + [ + 'image_load', + i18n.translate('xpack.endpoint.resolver.ImageLoad', { + defaultMessage: 'Image Load', + }) as EventCategory, + ], + [ + 'powershell', + i18n.translate('xpack.endpoint.resolver.Powershell', { + defaultMessage: 'Powershell', + }) as EventCategory, + ], + [ + 'wmi', + i18n.translate('xpack.endpoint.resolver.WMI', { + defaultMessage: 'WMI', + }) as EventCategory, + ], + [ + 'api', + i18n.translate('xpack.endpoint.resolver.API', { + defaultMessage: 'API', + }) as EventCategory, + ], + [ + 'user', + i18n.translate('xpack.endpoint.resolver.User', { + defaultMessage: 'User', + }) as EventCategory, + ], ]); // Returning "Process" as a catch-all here because it seems pretty general From ae14c1e0c91f951398ff54c40a3dc3520f335852 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 12 May 2020 00:56:01 -0400 Subject: [PATCH 50/74] lint resolver node --- .../public/embeddables/resolver/view/process_event_dot.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 34e072cbf10fc3..dfc77bbe0bb9a9 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -179,7 +179,6 @@ export const ProcessEventDot = styled( ]); const labelId = useMemo(() => resolverNodeIdGenerator(), [resolverNodeIdGenerator]); const descriptionId = useMemo(() => resolverNodeIdGenerator(), [resolverNodeIdGenerator]); - const isActiveDescendant = nodeId === activeDescendantId; const isSelectedDescendant = nodeId === selectedDescendantId; From 5a4819c8fecf4eb23ffff1948f2db0ce67cd5854 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Tue, 12 May 2020 12:03:26 -0400 Subject: [PATCH 51/74] R Austin Review: adjust middleware, rewrites for clarity, fixing types --- .../embeddables/resolver/store/data/action.ts | 2 +- .../resolver/store/data/reducer.ts | 4 +- .../resolver/store/data/selectors.ts | 6 +-- .../embeddables/resolver/store/middleware.ts | 39 ++++++++++++------- .../public/embeddables/resolver/types.ts | 2 +- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index 7481f9d733969b..1d58785be616ab 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -29,7 +29,7 @@ interface ServerReturnedRelatedEventData { */ interface ServerFailedToReturnRelatedEventData { readonly type: 'serverFailedToReturnRelatedEventData'; - readonly payload: [ResolverEvent, Error]; + readonly payload: [ResolverEvent]; } export type DataAction = diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index 5bf6983918aa02..ddcb4d13fe993a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -46,8 +46,8 @@ export const dataReducer: Reducer = (state = initialS const statsMap = state[resultsEnrichedWithRelatedEventInfo]; if (statsMap) { const currentStatsMap = new Map(statsMap); - const [resolverEvent, apiError] = action.payload; - currentStatsMap.set(resolverEvent, apiError); + const [resolverEvent] = action.payload; + currentStatsMap.set(resolverEvent, new Error('error requesting related events')); return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; } return state; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index d3be5d6dc17d6d..8773310599dc7c 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -16,8 +16,8 @@ import { AdjacentProcessMap, RelatedEventData, resultsEnrichedWithRelatedEventInfo, - RelatedEventType, RelatedEventDataEntryWithStats, + EventCategory, } from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { Vector2 } from '../../types'; @@ -441,8 +441,8 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge } const statsForEntry = newStatsEntry?.relatedEvents.reduce( ( - compiledStats: Partial>, - relatedEvent: { relatedEventType: RelatedEventType } + compiledStats: Partial>, + relatedEvent: { relatedEventType: EventCategory } ) => { compiledStats[relatedEvent.relatedEventType] = (compiledStats[relatedEvent.relatedEventType] || 0) + 1; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index d71a27bf19ddbf..045470fda03088 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -8,7 +8,7 @@ import { Dispatch, MiddlewareAPI } from 'redux'; import { HttpHandler } from 'kibana/public'; import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public'; import { EndpointPluginServices } from '../../../plugin'; -import { ResolverState, ResolverAction, RelatedEventDataEntry, RelatedEventType } from '../types'; +import { ResolverState, ResolverAction, RelatedEventDataEntry } from '../types'; import { ResolverEvent, ResolverNode } from '../../../../common/types'; import * as event from '../../../../common/models/event'; @@ -32,20 +32,27 @@ function flattenEvents(children: ResolverNode[], events: ResolverEvent[] = []): } type RelatedEventAPIResponse = Error | { events: ResolverEvent[] }; +/** + * As the design goal of this stopgap was to prevent saturating the server with /events + * requests, this generator intentionally processes events in serial rather than in parallel. + * @param eventsToFetch + * events to run against the /id/events API + * @param httpGetter + * the HttpHandler to use + */ async function* getEachRelatedEventsResult( eventsToFetch: ResolverEvent[], httpGetter: HttpHandler ): AsyncGenerator<[ResolverEvent, RelatedEventAPIResponse]> { for (const eventToQueryForRelateds of eventsToFetch) { const id = event.entityId(eventToQueryForRelateds); - const relatedEventError = new Error(`Error fetching related events for entity=${id}`); - let result: RelatedEventAPIResponse = relatedEventError; + let result: RelatedEventAPIResponse; try { result = await httpGetter(`/api/endpoint/resolver/${id}/events`, { query: { events: 100 }, }); } catch (e) { - result = relatedEventError; + result = new Error(`Error fetching related events for entity=${id}`); } yield [eventToQueryForRelateds, result]; } @@ -102,35 +109,41 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { if (action.type === 'userRequestedRelatedEventData') { if (typeof context !== 'undefined') { + const response: Map = new Map(); for await (const results of getEachRelatedEventsResult( [action.payload], context.services.http.get )) { + /** + * results here will take the shape of + * [event requested , response of event against the /related api] + */ const apiResults = results[1]; if (apiResults instanceof Error) { api.dispatch({ type: 'serverFailedToReturnRelatedEventData', - payload: [results[0], apiResults], + payload: [results[0]], }); + continue; } - const response: Map = new Map(); + const baseEvent = results[0]; - const fetchedResults = (results[1] as { events: ResolverEvent[] }).events; + const fetchedResults = apiResults.events; // pack up the results into response const relatedEventEntry = fetchedResults.map(relatedEvent => { return { relatedEvent, - relatedEventType: event.eventCategoryDisplayName(relatedEvent) as RelatedEventType, + relatedEventType: event.eventCategoryDisplayName(relatedEvent), }; }); response.set(baseEvent, { relatedEvents: relatedEventEntry }); - - api.dispatch({ - type: 'serverReturnedRelatedEventData', - payload: response, - }); } + + api.dispatch({ + type: 'serverReturnedRelatedEventData', + payload: response, + }); } } }; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 9eeb0dc2f5a8cb..7670ccf80197fe 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -166,7 +166,7 @@ export const waitingForRelatedEventData = Symbol( export interface RelatedEventDataEntry { relatedEvents: Array<{ relatedEvent: ResolverEvent; - relatedEventType: RelatedEventType; + relatedEventType: EventCategory; }>; } From 7b8aa3698f211b031ac16d832611f53ba206a54d Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 03:13:12 -0400 Subject: [PATCH 52/74] R Austin review: Move category display names to view --- .../plugins/endpoint/common/models/event.ts | 99 ++----------------- .../resolver/store/data/selectors.ts | 5 +- .../embeddables/resolver/store/middleware.ts | 2 +- .../public/embeddables/resolver/types.ts | 2 +- .../resolver/view/process_event_dot.tsx | 85 +++++++++++++++- 5 files changed, 93 insertions(+), 100 deletions(-) diff --git a/x-pack/plugins/endpoint/common/models/event.ts b/x-pack/plugins/endpoint/common/models/event.ts index 3464f44e0b10e1..68d398473f4099 100644 --- a/x-pack/plugins/endpoint/common/models/event.ts +++ b/x-pack/plugins/endpoint/common/models/event.ts @@ -3,10 +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 { i18n } from '@kbn/i18n'; import { LegacyEndpointEvent, ResolverEvent } from '../types'; -import { EventCategory } from '../../public/embeddables/resolver/types'; export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEvent { return (event as LegacyEndpointEvent).endgame !== undefined; @@ -49,105 +46,21 @@ export function parentEntityId(event: ResolverEvent): string | undefined { return event.process.parent?.entity_id; } -export function eventCategoryDisplayName(event: ResolverEvent): EventCategory { - const eventTypeToNameMap = new Map([ - [ - 'process', - i18n.translate('xpack.endpoint.resolver.Process', { - defaultMessage: 'Process', - }) as EventCategory, - ], - [ - 'alert', - i18n.translate('xpack.endpoint.resolver.Alert', { - defaultMessage: 'Alert', - }) as EventCategory, - ], - [ - 'security', - i18n.translate('xpack.endpoint.resolver.Security', { - defaultMessage: 'Security', - }) as EventCategory, - ], - [ - 'file', - i18n.translate('xpack.endpoint.resolver.File', { - defaultMessage: 'File', - }) as EventCategory, - ], - [ - 'network', - i18n.translate('xpack.endpoint.resolver.Network', { - defaultMessage: 'Network', - }) as EventCategory, - ], - [ - 'registry', - i18n.translate('xpack.endpoint.resolver.Registry', { - defaultMessage: 'Registry', - }) as EventCategory, - ], - [ - 'dns', - i18n.translate('xpack.endpoint.resolver.DNS', { - defaultMessage: 'DNS', - }) as EventCategory, - ], - [ - 'clr', - i18n.translate('xpack.endpoint.resolver.CLR', { - defaultMessage: 'CLR', - }) as EventCategory, - ], - [ - 'image_load', - i18n.translate('xpack.endpoint.resolver.ImageLoad', { - defaultMessage: 'Image Load', - }) as EventCategory, - ], - [ - 'powershell', - i18n.translate('xpack.endpoint.resolver.Powershell', { - defaultMessage: 'Powershell', - }) as EventCategory, - ], - [ - 'wmi', - i18n.translate('xpack.endpoint.resolver.WMI', { - defaultMessage: 'WMI', - }) as EventCategory, - ], - [ - 'api', - i18n.translate('xpack.endpoint.resolver.API', { - defaultMessage: 'API', - }) as EventCategory, - ], - [ - 'user', - i18n.translate('xpack.endpoint.resolver.User', { - defaultMessage: 'User', - }) as EventCategory, - ], - ]); - +export function eventType(event: ResolverEvent): string { // Returning "Process" as a catch-all here because it seems pretty general - let eventCategoryToReturn: EventCategory = 'Process'; + let eventCategoryToReturn: string = 'Process'; if (isLegacyEvent(event)) { const legacyFullType = event.endgame.event_type_full; if (legacyFullType) { - const mappedLegacyCategory = eventTypeToNameMap.get(legacyFullType); - if (mappedLegacyCategory) { - eventCategoryToReturn = mappedLegacyCategory; - } + eventCategoryToReturn = legacyFullType; } } else { const eventCategories = event.event.category; const eventCategory = typeof eventCategories === 'string' ? eventCategories : eventCategories[0] || ''; - const mappedCategoryValue = eventTypeToNameMap.get(eventCategory); - if (mappedCategoryValue) { - eventCategoryToReturn = mappedCategoryValue; + + if (eventCategory) { + eventCategoryToReturn = eventCategory; } } return eventCategoryToReturn; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 8773310599dc7c..cb35612dab953c 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -440,10 +440,7 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge continue; } const statsForEntry = newStatsEntry?.relatedEvents.reduce( - ( - compiledStats: Partial>, - relatedEvent: { relatedEventType: EventCategory } - ) => { + (compiledStats: Record, relatedEvent: { relatedEventType: string }) => { compiledStats[relatedEvent.relatedEventType] = (compiledStats[relatedEvent.relatedEventType] || 0) + 1; return compiledStats; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index 045470fda03088..b2c669316104c1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -133,7 +133,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { const relatedEventEntry = fetchedResults.map(relatedEvent => { return { relatedEvent, - relatedEventType: event.eventCategoryDisplayName(relatedEvent), + relatedEventType: event.eventType(relatedEvent), }; }); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 7670ccf80197fe..289a368c904aef 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -166,7 +166,7 @@ export const waitingForRelatedEventData = Symbol( export interface RelatedEventDataEntry { relatedEvents: Array<{ relatedEvent: ResolverEvent; - relatedEventType: EventCategory; + relatedEventType: string; }>; } diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index dfc77bbe0bb9a9..c14302b5b944b9 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -24,6 +24,7 @@ import { ResolverProcessType, waitingForRelatedEventData, RelatedEventEntryWithStatsOrWaiting, + EventCategory, } from '../types'; import { SymbolIds, NamedColors } from './defs'; import { ResolverEvent } from '../../../../common/types'; @@ -67,6 +68,87 @@ const nodeAssets = { }, }; +const eventTypeToNameMap = new Map([ + [ + 'process', + i18n.translate('xpack.endpoint.resolver.Process', { + defaultMessage: 'Process', + }) as EventCategory, + ], + [ + 'alert', + i18n.translate('xpack.endpoint.resolver.Alert', { + defaultMessage: 'Alert', + }) as EventCategory, + ], + [ + 'security', + i18n.translate('xpack.endpoint.resolver.Security', { + defaultMessage: 'Security', + }) as EventCategory, + ], + [ + 'file', + i18n.translate('xpack.endpoint.resolver.File', { + defaultMessage: 'File', + }) as EventCategory, + ], + [ + 'network', + i18n.translate('xpack.endpoint.resolver.Network', { + defaultMessage: 'Network', + }) as EventCategory, + ], + [ + 'registry', + i18n.translate('xpack.endpoint.resolver.Registry', { + defaultMessage: 'Registry', + }) as EventCategory, + ], + [ + 'dns', + i18n.translate('xpack.endpoint.resolver.DNS', { + defaultMessage: 'DNS', + }) as EventCategory, + ], + [ + 'clr', + i18n.translate('xpack.endpoint.resolver.CLR', { + defaultMessage: 'CLR', + }) as EventCategory, + ], + [ + 'image_load', + i18n.translate('xpack.endpoint.resolver.ImageLoad', { + defaultMessage: 'Image Load', + }) as EventCategory, + ], + [ + 'powershell', + i18n.translate('xpack.endpoint.resolver.Powershell', { + defaultMessage: 'Powershell', + }) as EventCategory, + ], + [ + 'wmi', + i18n.translate('xpack.endpoint.resolver.WMI', { + defaultMessage: 'WMI', + }) as EventCategory, + ], + [ + 'api', + i18n.translate('xpack.endpoint.resolver.API', { + defaultMessage: 'API', + }) as EventCategory, + ], + [ + 'user', + i18n.translate('xpack.endpoint.resolver.User', { + defaultMessage: 'User', + }) as EventCategory, + ], +]); + /** * An artifact that represents a process node. */ @@ -239,9 +321,10 @@ export const ProcessEventDot = styled( } // If we have entries to show, map them into options to display in the selectable list return Object.entries(relatedEvents.stats).map(statsEntry => { + const displayName = eventTypeToNameMap.get(statsEntry[0]) || 'Process'; return { prefix: , - optionTitle: `${statsEntry[0]}`, + optionTitle: `${displayName}`, action: () => { dispatch({ type: 'userSelectedRelatedEventCategory', From e019815996af086f9fece6b2b1bdc3048162f3be Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 03:37:07 -0400 Subject: [PATCH 53/74] R Austin review: remove all symbols --- .../resolver/store/data/reducer.ts | 23 ++++++++----------- .../resolver/store/data/selectors.ts | 4 +--- .../public/embeddables/resolver/types.ts | 22 ++++-------------- .../resolver/view/process_event_dot.tsx | 3 +-- .../embeddables/resolver/view/submenu.tsx | 22 ++++++++++-------- 5 files changed, 28 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index ddcb4d13fe993a..e9e8d0ea246351 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -5,19 +5,14 @@ */ import { Reducer } from 'redux'; -import { - DataState, - ResolverAction, - resultsEnrichedWithRelatedEventInfo, - waitingForRelatedEventData, -} from '../../types'; +import { DataState, ResolverAction } from '../../types'; function initialState(): DataState { return { results: [], isLoading: false, hasError: false, - [resultsEnrichedWithRelatedEventInfo]: new Map(), + resultsEnrichedWithRelatedEventInfo: new Map(), }; } @@ -31,31 +26,31 @@ export const dataReducer: Reducer = (state = initialS }; } else if (action.type === 'userRequestedRelatedEventData') { const resolverEvent = action.payload; - const statsMap = state[resultsEnrichedWithRelatedEventInfo]; + const statsMap = state.resultsEnrichedWithRelatedEventInfo; if (statsMap) { const currentStatsMap = new Map(statsMap); /** * Set the waiting indicator for this event to indicate that related event results are pending. * It will be replaced by the actual results from the API when they are returned. */ - currentStatsMap.set(resolverEvent, waitingForRelatedEventData); - return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; + currentStatsMap.set(resolverEvent, 'waitingForRelatedEventData'); + return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; } return state; } else if (action.type === 'serverFailedToReturnRelatedEventData') { - const statsMap = state[resultsEnrichedWithRelatedEventInfo]; + const statsMap = state.resultsEnrichedWithRelatedEventInfo; if (statsMap) { const currentStatsMap = new Map(statsMap); const [resolverEvent] = action.payload; currentStatsMap.set(resolverEvent, new Error('error requesting related events')); - return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap }; + return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; } return state; } else if (action.type === 'serverReturnedRelatedEventData') { - const statsMap = state[resultsEnrichedWithRelatedEventInfo]; + const statsMap = state.resultsEnrichedWithRelatedEventInfo; if (statsMap && typeof statsMap?.set === 'function') { const relatedDataEntries = new Map([...statsMap, ...action.payload]); - return { ...state, [resultsEnrichedWithRelatedEventInfo]: relatedDataEntries }; + return { ...state, resultsEnrichedWithRelatedEventInfo: relatedDataEntries }; } return state; } else if (action.type === 'appRequestedResolverData') { diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index cb35612dab953c..56db17342e245e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -15,9 +15,7 @@ import { Matrix3, AdjacentProcessMap, RelatedEventData, - resultsEnrichedWithRelatedEventInfo, RelatedEventDataEntryWithStats, - EventCategory, } from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { Vector2 } from '../../types'; @@ -415,7 +413,7 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in export const relatedEventResults = createSelector( (data: DataState) => data, function(data) { - return data[resultsEnrichedWithRelatedEventInfo]; + return data.resultsEnrichedWithRelatedEventInfo; } ); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 289a368c904aef..b6baa27f80f8a2 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -148,17 +148,6 @@ export type RelatedEventType = export type EventCategory = RelatedEventType | ('Alert' | 'Process' | 'Security'); -/** - * This symbol is used to tag results with Related event info - */ -export const resultsEnrichedWithRelatedEventInfo = `resultsEnrichedWithRelatedEventInfo`; -/** - * This symbol indicates that the app is waiting for related event data for the subject - * of any particular request. - */ -export const waitingForRelatedEventData = Symbol( - 'The app has requested related event data for this entity ID, but has not yet receieved it' -); /** * This represents all the raw data (sans statistics, metadata, etc.) * about a particular subject's related events @@ -174,10 +163,7 @@ export interface RelatedEventDataEntry { * Represents the status of the request for related event data, which will be either the data, * a value indicating that it's still waiting for the data or an Error indicating the data can't be retrieved as expected */ -export type RelatedEventDataResults = - | RelatedEventDataEntry - | typeof waitingForRelatedEventData - | Error; +export type RelatedEventDataResults = RelatedEventDataEntry | 'waitingForRelatedEventData' | Error; /** * This represents the raw related events data enhanced with statistics @@ -196,12 +182,12 @@ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { */ export type RelatedEventEntryWithStatsOrWaiting = | RelatedEventDataEntryWithStats - | typeof waitingForRelatedEventData + | `waitingForRelatedEventData` | Error; /** * This represents a Map that will return either a `RelatedEventDataEntryWithStats` - * or a `waitingForRelatedEventData` symbol when referenced unique event. + * or a `waitingForRelatedEventData` symbol when referenced with a unique event. */ export type RelatedEventData = Map; @@ -212,7 +198,7 @@ export interface DataState { readonly results: readonly ResolverEvent[]; isLoading: boolean; hasError: boolean; - [resultsEnrichedWithRelatedEventInfo]?: Map; + resultsEnrichedWithRelatedEventInfo?: Map; } export type Vector2 = readonly [number, number]; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index c14302b5b944b9..f863509e57427b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -22,7 +22,6 @@ import { Matrix3, AdjacentProcessMap, ResolverProcessType, - waitingForRelatedEventData, RelatedEventEntryWithStatsOrWaiting, EventCategory, } from '../types'; @@ -314,7 +313,7 @@ export const ProcessEventDot = styled( // If there was an error when we tried to request the events return subMenuAssets.menuError; } - if (relatedEvents === waitingForRelatedEventData) { + if (relatedEvents === 'waitingForRelatedEventData') { // If we're waiting for events to be returned // Pass on the waiting symbol return relatedEvents; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx index 0412144152c2fe..b04a884bb500b5 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode, useState, useMemo, useCallback } from 'react'; import { EuiSelectable, EuiButton } from '@elastic/eui'; import styled from 'styled-components'; -import { waitingForRelatedEventData } from '../types'; /** * i18n-translated titles for submenus and identifiers for display of states: @@ -16,10 +15,12 @@ import { waitingForRelatedEventData } from '../types'; * menuError: if the submenu requested data, but received an error */ export const subMenuAssets = { - initialMenuStatus: Symbol( - 'The state of a Resolver submenu before it has been opened or requested data.' - ), - menuError: Symbol('The options in this submenu cannot be displayed because of an error'), + initialMenuStatus: i18n.translate('xpack.endpoint.resolver.relatedNotRetrieved', { + defaultMessage: 'Related Events have not yet been retrieved.', + }), + menuError: i18n.translate('xpack.endpoint.resolver.relatedRetrievalError', { + defaultMessage: 'There was an error retrieving related events.', + }), relatedAlerts: { title: i18n.translate('xpack.endpoint.resolver.relatedAlerts', { defaultMessage: 'Related Alerts', @@ -38,7 +39,10 @@ interface ResolverSubmenuOption { prefix?: number | JSX.Element; } -export type ResolverSubmenuOptionList = ResolverSubmenuOption[] | symbol; +export type ResolverSubmenuOptionList = + | ResolverSubmenuOption[] + | 'waitingForRelatedEventData' + | typeof subMenuAssets.initialMenuStatus; const OptionList = React.memo( ({ @@ -49,7 +53,7 @@ const OptionList = React.memo( isLoading: boolean; }) => { const selectableOptions = - typeof subMenuOptions === 'symbol' + typeof subMenuOptions !== 'object' ? [] : subMenuOptions.map((opt: ResolverSubmenuOption): { label: string; @@ -102,7 +106,7 @@ export const NodeSubMenu = styled( optionsWithActions: | ResolverSubmenuOptionList | typeof subMenuAssets.initialMenuStatus - | typeof waitingForRelatedEventData; + | 'waitingForRelatedEventData'; } | { optionsWithActions?: undefined } )) => { @@ -129,7 +133,7 @@ export const NodeSubMenu = styled( }, [optionsWithActions]); const isMenuLoading = useMemo(() => { - return optionsWithActions === waitingForRelatedEventData; + return optionsWithActions === 'waitingForRelatedEventData'; }, [optionsWithActions]); if (!optionsWithActions) { From 2d7523acba05ae997ab45677eaea8a49ce28305d Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 11:55:55 -0400 Subject: [PATCH 54/74] R Austin review: refactor memos for clarity --- .../resolver/store/data/selectors.ts | 4 ++ .../public/embeddables/resolver/types.ts | 20 +----- .../resolver/view/process_event_dot.tsx | 67 +++++++++++-------- 3 files changed, 45 insertions(+), 46 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 56db17342e245e..5b7ba2d0bc913b 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -437,6 +437,10 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge relatedEventStats.set(updatedEvent, newStatsEntry); continue; } + /** + * Folowing reduction, this will be a record like + * {DNS: 10, File: 2} etc. + */ const statsForEntry = newStatsEntry?.relatedEvents.reduce( (compiledStats: Record, relatedEvent: { relatedEventType: string }) => { compiledStats[relatedEvent.relatedEventType] = diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index b6baa27f80f8a2..a87003c2330b9e 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -130,24 +130,6 @@ export type CameraState = { } ); -/** - * This is the current list of known related event types. It has been transcribed from the - * v0 Endgame app. - */ -export type RelatedEventType = - | 'Network' - | 'File' - | 'DNS' - | 'Registry' - | 'Powershell' - | 'WMI' - | 'API' - | 'CLR' - | 'Image Load' - | 'User'; - -export type EventCategory = RelatedEventType | ('Alert' | 'Process' | 'Security'); - /** * This represents all the raw data (sans statistics, metadata, etc.) * about a particular subject's related events @@ -170,7 +152,7 @@ export type RelatedEventDataResults = RelatedEventDataEntry | 'waitingForRelated * (e.g. counts of items grouped by their related event types) */ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { - stats: Partial>; + stats: Record; }; /** diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index f863509e57427b..1bcff8dc41de74 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -23,7 +23,6 @@ import { AdjacentProcessMap, ResolverProcessType, RelatedEventEntryWithStatsOrWaiting, - EventCategory, } from '../types'; import { SymbolIds, NamedColors } from './defs'; import { ResolverEvent } from '../../../../common/types'; @@ -67,84 +66,84 @@ const nodeAssets = { }, }; -const eventTypeToNameMap = new Map([ +const eventTypeToNameMap = new Map([ [ 'process', i18n.translate('xpack.endpoint.resolver.Process', { defaultMessage: 'Process', - }) as EventCategory, + }), ], [ 'alert', i18n.translate('xpack.endpoint.resolver.Alert', { defaultMessage: 'Alert', - }) as EventCategory, + }), ], [ 'security', i18n.translate('xpack.endpoint.resolver.Security', { defaultMessage: 'Security', - }) as EventCategory, + }), ], [ 'file', i18n.translate('xpack.endpoint.resolver.File', { defaultMessage: 'File', - }) as EventCategory, + }), ], [ 'network', i18n.translate('xpack.endpoint.resolver.Network', { defaultMessage: 'Network', - }) as EventCategory, + }), ], [ 'registry', i18n.translate('xpack.endpoint.resolver.Registry', { defaultMessage: 'Registry', - }) as EventCategory, + }), ], [ 'dns', i18n.translate('xpack.endpoint.resolver.DNS', { defaultMessage: 'DNS', - }) as EventCategory, + }), ], [ 'clr', i18n.translate('xpack.endpoint.resolver.CLR', { defaultMessage: 'CLR', - }) as EventCategory, + }), ], [ 'image_load', i18n.translate('xpack.endpoint.resolver.ImageLoad', { defaultMessage: 'Image Load', - }) as EventCategory, + }), ], [ 'powershell', i18n.translate('xpack.endpoint.resolver.Powershell', { defaultMessage: 'Powershell', - }) as EventCategory, + }), ], [ 'wmi', i18n.translate('xpack.endpoint.resolver.WMI', { defaultMessage: 'WMI', - }) as EventCategory, + }), ], [ 'api', i18n.translate('xpack.endpoint.resolver.API', { defaultMessage: 'API', - }) as EventCategory, + }), ], [ 'user', i18n.translate('xpack.endpoint.resolver.User', { defaultMessage: 'User', - }) as EventCategory, + }), ], ]); @@ -305,21 +304,17 @@ export const ProcessEventDot = styled( * e.g. "10 DNS", "230 File" */ const relatedEventOptions = useMemo(() => { - if (!relatedEvents) { - // If related events have not yet been requested - return subMenuAssets.initialMenuStatus; - } if (relatedEvents instanceof Error) { - // If there was an error when we tried to request the events - return subMenuAssets.menuError; + // Return an empty set of options if there was an error requesting them + return []; } - if (relatedEvents === 'waitingForRelatedEventData') { - // If we're waiting for events to be returned - // Pass on the waiting symbol - return relatedEvents; + const relatedStats = typeof relatedEvents === 'object' && relatedEvents.stats; + if (!relatedStats) { + // Return an empty set of options if there are no stats to report + return []; } // If we have entries to show, map them into options to display in the selectable list - return Object.entries(relatedEvents.stats).map(statsEntry => { + return Object.entries(relatedStats).map(statsEntry => { const displayName = eventTypeToNameMap.get(statsEntry[0]) || 'Process'; return { prefix: , @@ -332,11 +327,29 @@ export const ProcessEventDot = styled( category: statsEntry[0], }, }); + return false; }, }; }); }, [relatedEvents, dispatch, event]); + const relatedEventStatusOrOptions = useMemo(() => { + if (!relatedEvents) { + // If related events have not yet been requested + return subMenuAssets.initialMenuStatus; + } + if (relatedEvents instanceof Error) { + // If there was an error when we tried to request the events + return subMenuAssets.menuError; + } + if (relatedEvents === 'waitingForRelatedEventData') { + // If we're waiting for events to be returned + // Pass on the waiting symbol + return relatedEvents; + } + return relatedEventOptions; + }, [relatedEvents, relatedEventOptions]); + /* eslint-disable jsx-a11y/click-events-have-key-events */ /** * Key event handling (e.g. 'Enter'/'Space') is provisioned by the `EuiKeyboardAccessible` component @@ -469,7 +482,7 @@ export const ProcessEventDot = styled( From 17dfe3cfac2d7ed49b8e28871196c174dab3f743 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 12:04:07 -0400 Subject: [PATCH 55/74] add comment --- .../public/embeddables/resolver/view/process_event_dot.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 1bcff8dc41de74..d7acc5086ae39a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -66,6 +66,9 @@ const nodeAssets = { }, }; +/** + * A Map of undfriendly/ugly event types to beautiful translated display strings. + */ const eventTypeToNameMap = new Map([ [ 'process', From 2413c93209354b1c1f9f6a011692e99ed1d7da4c Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 15:51:40 -0400 Subject: [PATCH 56/74] K Qualters review: destructure arguments --- .../endpoint/public/embeddables/resolver/store/middleware.ts | 3 +-- .../public/embeddables/resolver/view/process_event_dot.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index b2c669316104c1..c7d8deda822730 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -118,7 +118,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * results here will take the shape of * [event requested , response of event against the /related api] */ - const apiResults = results[1]; + const [baseEvent, apiResults] = results; if (apiResults instanceof Error) { api.dispatch({ type: 'serverFailedToReturnRelatedEventData', @@ -127,7 +127,6 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { continue; } - const baseEvent = results[0]; const fetchedResults = apiResults.events; // pack up the results into response const relatedEventEntry = fetchedResults.map(relatedEvent => { diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index d7acc5086ae39a..fbdfbb1b2f5163 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -67,7 +67,7 @@ const nodeAssets = { }; /** - * A Map of undfriendly/ugly event types to beautiful translated display strings. + * A Map of undfriendly/ugly event types to beautiful translated display strings. */ const eventTypeToNameMap = new Map([ [ From d08f4144a7d572b49dc40804cc964b6b41a22310 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 17:04:34 -0400 Subject: [PATCH 57/74] K Qualters review: add more ECS event types to Map --- .../resolver/view/process_event_dot.tsx | 132 +++++++----------- 1 file changed, 48 insertions(+), 84 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index fbdfbb1b2f5163..88182d5ae0aa11 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -66,89 +66,53 @@ const nodeAssets = { }, }; -/** - * A Map of undfriendly/ugly event types to beautiful translated display strings. - */ -const eventTypeToNameMap = new Map([ - [ - 'process', - i18n.translate('xpack.endpoint.resolver.Process', { - defaultMessage: 'Process', - }), - ], - [ - 'alert', - i18n.translate('xpack.endpoint.resolver.Alert', { - defaultMessage: 'Alert', - }), - ], - [ - 'security', - i18n.translate('xpack.endpoint.resolver.Security', { - defaultMessage: 'Security', - }), - ], - [ - 'file', - i18n.translate('xpack.endpoint.resolver.File', { - defaultMessage: 'File', - }), - ], - [ - 'network', - i18n.translate('xpack.endpoint.resolver.Network', { - defaultMessage: 'Network', - }), - ], - [ - 'registry', - i18n.translate('xpack.endpoint.resolver.Registry', { - defaultMessage: 'Registry', - }), - ], - [ - 'dns', - i18n.translate('xpack.endpoint.resolver.DNS', { - defaultMessage: 'DNS', - }), - ], - [ - 'clr', - i18n.translate('xpack.endpoint.resolver.CLR', { - defaultMessage: 'CLR', - }), - ], - [ - 'image_load', - i18n.translate('xpack.endpoint.resolver.ImageLoad', { - defaultMessage: 'Image Load', - }), - ], - [ - 'powershell', - i18n.translate('xpack.endpoint.resolver.Powershell', { - defaultMessage: 'Powershell', - }), - ], - [ - 'wmi', - i18n.translate('xpack.endpoint.resolver.WMI', { - defaultMessage: 'WMI', - }), - ], - [ - 'api', - i18n.translate('xpack.endpoint.resolver.API', { - defaultMessage: 'API', - }), - ], - [ - 'user', - i18n.translate('xpack.endpoint.resolver.User', { - defaultMessage: 'User', - }), - ], -]); +const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName( + translator: Pick, + schemaName: string +) { + return translator.translate(`xpack.endpoint.resolver.${schemaName}`, { + defaultMessage: lookupDisplayName(schemaName), + }); + function lookupDisplayName(referenceName: string): string { + const displayNameRecord: Record = { + application: 'Application', + apm: 'APM', + audit: 'Audit', + authentication: 'Authentication', + certificate: 'Certificate', + cloud: 'Cloud', + database: 'Database', + driver: 'Driver', + email: 'Email', + file: 'File', + host: 'Host', + iam: 'IAM', + iam_group: 'IAM Group', + intrusion_detection: 'Intrusion Detection', + malware: 'Malware', + network_flow: 'Network Flow', + network: 'Network', + package: 'Package', + process: 'Process', + registry: 'Registry', + session: 'Session', + service: 'Service', + socket: 'Socket', + vulnerability: 'Vulnerability', + web: 'Web', + alert: 'Alert', + security: 'Security', + dns: 'DNS', + clr: 'CLR', + image_load: 'Image Load', + powershell: 'Powershell', + wmi: 'WMI', + api: 'API', + user: 'User', + }; + return displayNameRecord[referenceName] || 'User'; + } +}.bind(null, i18n); /** * An artifact that represents a process node. @@ -318,7 +282,7 @@ export const ProcessEventDot = styled( } // If we have entries to show, map them into options to display in the selectable list return Object.entries(relatedStats).map(statsEntry => { - const displayName = eventTypeToNameMap.get(statsEntry[0]) || 'Process'; + const displayName = getDisplayName(statsEntry[0]); return { prefix: , optionTitle: `${displayName}`, From 2f90697bab0b98feaaab59d0807f9c66b0640143 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Wed, 13 May 2020 17:09:40 -0400 Subject: [PATCH 58/74] add comments for clarification --- .../public/embeddables/resolver/view/process_event_dot.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 88182d5ae0aa11..2853c1af669676 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -66,7 +66,11 @@ const nodeAssets = { }, }; +/** + * Take a gross `schemaName` and return a beautiful translated one. + */ const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName( + // note: i18n is bound below translator: Pick, schemaName: string ) { @@ -115,7 +119,7 @@ const getDisplayName: (schemaName: string) => string = function nameInSchemaToDi }.bind(null, i18n); /** - * An artifact that represents a process node. + * An artifact that represents a process node and the things associated with it in the Resolver */ export const ProcessEventDot = styled( React.memo( From 66fc65387b566f9879a5e42e2b3d1089456c94dc Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 14 May 2020 11:04:54 -0400 Subject: [PATCH 59/74] D Plumlee review: Display unknown for unmatched categories --- .../public/embeddables/resolver/view/process_event_dot.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 2853c1af669676..1d8d89f83025d3 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -114,7 +114,7 @@ const getDisplayName: (schemaName: string) => string = function nameInSchemaToDi api: 'API', user: 'User', }; - return displayNameRecord[referenceName] || 'User'; + return displayNameRecord[referenceName] || 'Unknown'; } }.bind(null, i18n); From 235ebd56d635d38b62cdcc3d29f28288ce1d9d77 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 14 May 2020 11:22:54 -0400 Subject: [PATCH 60/74] R Austin review: Call i18n with static values --- .../resolver/view/process_event_dot.tsx | 154 +++++++++++++----- 1 file changed, 113 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 1d8d89f83025d3..09b944ec02d80a 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -70,53 +70,125 @@ const nodeAssets = { * Take a gross `schemaName` and return a beautiful translated one. */ const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName( - // note: i18n is bound below - translator: Pick, schemaName: string ) { - return translator.translate(`xpack.endpoint.resolver.${schemaName}`, { - defaultMessage: lookupDisplayName(schemaName), - }); + return lookupDisplayName(schemaName); function lookupDisplayName(referenceName: string): string { const displayNameRecord: Record = { - application: 'Application', - apm: 'APM', - audit: 'Audit', - authentication: 'Authentication', - certificate: 'Certificate', - cloud: 'Cloud', - database: 'Database', - driver: 'Driver', - email: 'Email', - file: 'File', - host: 'Host', - iam: 'IAM', - iam_group: 'IAM Group', - intrusion_detection: 'Intrusion Detection', - malware: 'Malware', - network_flow: 'Network Flow', - network: 'Network', - package: 'Package', - process: 'Process', - registry: 'Registry', - session: 'Session', - service: 'Service', - socket: 'Socket', - vulnerability: 'Vulnerability', - web: 'Web', - alert: 'Alert', - security: 'Security', - dns: 'DNS', - clr: 'CLR', - image_load: 'Image Load', - powershell: 'Powershell', - wmi: 'WMI', - api: 'API', - user: 'User', + application: i18n.translate('xpack.endpoint.resolver.applicationEventTypeDisplayName', { + defaultMessage: 'Application', + }), + apm: i18n.translate('xpack.endpoint.resolver.apmEventTypeDisplayName', { + defaultMessage: 'APM', + }), + audit: i18n.translate('xpack.endpoint.resolver.auditEventTypeDisplayName', { + defaultMessage: 'Audit', + }), + authentication: i18n.translate('xpack.endpoint.resolver.authenticationEventTypeDisplayName', { + defaultMessage: 'Authentication', + }), + certificate: i18n.translate('xpack.endpoint.resolver.certificateEventTypeDisplayName', { + defaultMessage: 'Certificate', + }), + cloud: i18n.translate('xpack.endpoint.resolver.cloudEventTypeDisplayName', { + defaultMessage: 'Cloud', + }), + database: i18n.translate('xpack.endpoint.resolver.databaseEventTypeDisplayName', { + defaultMessage: 'Database', + }), + driver: i18n.translate('xpack.endpoint.resolver.driverEventTypeDisplayName', { + defaultMessage: 'Driver', + }), + email: i18n.translate('xpack.endpoint.resolver.emailEventTypeDisplayName', { + defaultMessage: 'Email', + }), + file: i18n.translate('xpack.endpoint.resolver.fileEventTypeDisplayName', { + defaultMessage: 'File', + }), + host: i18n.translate('xpack.endpoint.resolver.hostEventTypeDisplayName', { + defaultMessage: 'Host', + }), + iam: i18n.translate('xpack.endpoint.resolver.iamEventTypeDisplayName', { + defaultMessage: 'IAM', + }), + iam_group: i18n.translate('xpack.endpoint.resolver.iam_groupEventTypeDisplayName', { + defaultMessage: 'IAM Group', + }), + intrusion_detection: i18n.translate( + 'xpack.endpoint.resolver.intrusion_detectionEventTypeDisplayName', + { + defaultMessage: 'Intrusion Detection', + } + ), + malware: i18n.translate('xpack.endpoint.resolver.malwareEventTypeDisplayName', { + defaultMessage: 'Malware', + }), + network_flow: i18n.translate('xpack.endpoint.resolver.network_flowEventTypeDisplayName', { + defaultMessage: 'Network Flow', + }), + network: i18n.translate('xpack.endpoint.resolver.networkEventTypeDisplayName', { + defaultMessage: 'Network', + }), + package: i18n.translate('xpack.endpoint.resolver.packageEventTypeDisplayName', { + defaultMessage: 'Package', + }), + process: i18n.translate('xpack.endpoint.resolver.processEventTypeDisplayName', { + defaultMessage: 'Process', + }), + registry: i18n.translate('xpack.endpoint.resolver.registryEventTypeDisplayName', { + defaultMessage: 'Registry', + }), + session: i18n.translate('xpack.endpoint.resolver.sessionEventTypeDisplayName', { + defaultMessage: 'Session', + }), + service: i18n.translate('xpack.endpoint.resolver.serviceEventTypeDisplayName', { + defaultMessage: 'Service', + }), + socket: i18n.translate('xpack.endpoint.resolver.socketEventTypeDisplayName', { + defaultMessage: 'Socket', + }), + vulnerability: i18n.translate('xpack.endpoint.resolver.vulnerabilityEventTypeDisplayName', { + defaultMessage: 'Vulnerability', + }), + web: i18n.translate('xpack.endpoint.resolver.webEventTypeDisplayName', { + defaultMessage: 'Web', + }), + alert: i18n.translate('xpack.endpoint.resolver.alertEventTypeDisplayName', { + defaultMessage: 'Alert', + }), + security: i18n.translate('xpack.endpoint.resolver.securityEventTypeDisplayName', { + defaultMessage: 'Security', + }), + dns: i18n.translate('xpack.endpoint.resolver.dnsEventTypeDisplayName', { + defaultMessage: 'DNS', + }), + clr: i18n.translate('xpack.endpoint.resolver.clrEventTypeDisplayName', { + defaultMessage: 'CLR', + }), + image_load: i18n.translate('xpack.endpoint.resolver.image_loadEventTypeDisplayName', { + defaultMessage: 'Image Load', + }), + powershell: i18n.translate('xpack.endpoint.resolver.powershellEventTypeDisplayName', { + defaultMessage: 'Powershell', + }), + wmi: i18n.translate('xpack.endpoint.resolver.wmiEventTypeDisplayName', { + defaultMessage: 'WMI', + }), + api: i18n.translate('xpack.endpoint.resolver.apiEventTypeDisplayName', { + defaultMessage: 'API', + }), + user: i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayName', { + defaultMessage: 'User', + }), }; - return displayNameRecord[referenceName] || 'Unknown'; + return ( + displayNameRecord[referenceName] || + i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayUnknown', { + defaultMessage: 'Unknown', + }) + ); } -}.bind(null, i18n); +}; /** * An artifact that represents a process node and the things associated with it in the Resolver From 080e112ff485fd9599c858bc317c8fba49440797 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 14 May 2020 13:13:47 -0400 Subject: [PATCH 61/74] D Plumlee review: remove reselect call from selector where it's not needed --- .../resolver/store/data/selectors.ts | 9 +- .../resolver/view/process_event_dot.tsx | 230 +++++++++--------- 2 files changed, 117 insertions(+), 122 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index 5b7ba2d0bc913b..cc1d1a36acda14 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -410,12 +410,9 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in /** * Process events that will be graphed. */ -export const relatedEventResults = createSelector( - (data: DataState) => data, - function(data) { - return data.resultsEnrichedWithRelatedEventInfo; - } -); +export const relatedEventResults = function(data: DataState) { + return data.resultsEnrichedWithRelatedEventInfo; +}; export const relatedEventStats = createSelector(relatedEventResults, function getRelatedEvents( /* eslint-disable no-shadow */ diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 09b944ec02d80a..f22f055a686544 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -72,122 +72,120 @@ const nodeAssets = { const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName( schemaName: string ) { - return lookupDisplayName(schemaName); - function lookupDisplayName(referenceName: string): string { - const displayNameRecord: Record = { - application: i18n.translate('xpack.endpoint.resolver.applicationEventTypeDisplayName', { - defaultMessage: 'Application', - }), - apm: i18n.translate('xpack.endpoint.resolver.apmEventTypeDisplayName', { - defaultMessage: 'APM', - }), - audit: i18n.translate('xpack.endpoint.resolver.auditEventTypeDisplayName', { - defaultMessage: 'Audit', - }), - authentication: i18n.translate('xpack.endpoint.resolver.authenticationEventTypeDisplayName', { - defaultMessage: 'Authentication', - }), - certificate: i18n.translate('xpack.endpoint.resolver.certificateEventTypeDisplayName', { - defaultMessage: 'Certificate', - }), - cloud: i18n.translate('xpack.endpoint.resolver.cloudEventTypeDisplayName', { - defaultMessage: 'Cloud', - }), - database: i18n.translate('xpack.endpoint.resolver.databaseEventTypeDisplayName', { - defaultMessage: 'Database', - }), - driver: i18n.translate('xpack.endpoint.resolver.driverEventTypeDisplayName', { - defaultMessage: 'Driver', - }), - email: i18n.translate('xpack.endpoint.resolver.emailEventTypeDisplayName', { - defaultMessage: 'Email', - }), - file: i18n.translate('xpack.endpoint.resolver.fileEventTypeDisplayName', { - defaultMessage: 'File', - }), - host: i18n.translate('xpack.endpoint.resolver.hostEventTypeDisplayName', { - defaultMessage: 'Host', - }), - iam: i18n.translate('xpack.endpoint.resolver.iamEventTypeDisplayName', { - defaultMessage: 'IAM', - }), - iam_group: i18n.translate('xpack.endpoint.resolver.iam_groupEventTypeDisplayName', { - defaultMessage: 'IAM Group', - }), - intrusion_detection: i18n.translate( - 'xpack.endpoint.resolver.intrusion_detectionEventTypeDisplayName', - { - defaultMessage: 'Intrusion Detection', - } - ), - malware: i18n.translate('xpack.endpoint.resolver.malwareEventTypeDisplayName', { - defaultMessage: 'Malware', - }), - network_flow: i18n.translate('xpack.endpoint.resolver.network_flowEventTypeDisplayName', { - defaultMessage: 'Network Flow', - }), - network: i18n.translate('xpack.endpoint.resolver.networkEventTypeDisplayName', { - defaultMessage: 'Network', - }), - package: i18n.translate('xpack.endpoint.resolver.packageEventTypeDisplayName', { - defaultMessage: 'Package', - }), - process: i18n.translate('xpack.endpoint.resolver.processEventTypeDisplayName', { - defaultMessage: 'Process', - }), - registry: i18n.translate('xpack.endpoint.resolver.registryEventTypeDisplayName', { - defaultMessage: 'Registry', - }), - session: i18n.translate('xpack.endpoint.resolver.sessionEventTypeDisplayName', { - defaultMessage: 'Session', - }), - service: i18n.translate('xpack.endpoint.resolver.serviceEventTypeDisplayName', { - defaultMessage: 'Service', - }), - socket: i18n.translate('xpack.endpoint.resolver.socketEventTypeDisplayName', { - defaultMessage: 'Socket', - }), - vulnerability: i18n.translate('xpack.endpoint.resolver.vulnerabilityEventTypeDisplayName', { - defaultMessage: 'Vulnerability', - }), - web: i18n.translate('xpack.endpoint.resolver.webEventTypeDisplayName', { - defaultMessage: 'Web', - }), - alert: i18n.translate('xpack.endpoint.resolver.alertEventTypeDisplayName', { - defaultMessage: 'Alert', - }), - security: i18n.translate('xpack.endpoint.resolver.securityEventTypeDisplayName', { - defaultMessage: 'Security', - }), - dns: i18n.translate('xpack.endpoint.resolver.dnsEventTypeDisplayName', { - defaultMessage: 'DNS', - }), - clr: i18n.translate('xpack.endpoint.resolver.clrEventTypeDisplayName', { - defaultMessage: 'CLR', - }), - image_load: i18n.translate('xpack.endpoint.resolver.image_loadEventTypeDisplayName', { - defaultMessage: 'Image Load', - }), - powershell: i18n.translate('xpack.endpoint.resolver.powershellEventTypeDisplayName', { - defaultMessage: 'Powershell', - }), - wmi: i18n.translate('xpack.endpoint.resolver.wmiEventTypeDisplayName', { - defaultMessage: 'WMI', - }), - api: i18n.translate('xpack.endpoint.resolver.apiEventTypeDisplayName', { - defaultMessage: 'API', - }), - user: i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayName', { - defaultMessage: 'User', - }), - }; - return ( - displayNameRecord[referenceName] || - i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayUnknown', { - defaultMessage: 'Unknown', - }) - ); - } + + const displayNameRecord: Record = { + application: i18n.translate('xpack.endpoint.resolver.applicationEventTypeDisplayName', { + defaultMessage: 'Application', + }), + apm: i18n.translate('xpack.endpoint.resolver.apmEventTypeDisplayName', { + defaultMessage: 'APM', + }), + audit: i18n.translate('xpack.endpoint.resolver.auditEventTypeDisplayName', { + defaultMessage: 'Audit', + }), + authentication: i18n.translate('xpack.endpoint.resolver.authenticationEventTypeDisplayName', { + defaultMessage: 'Authentication', + }), + certificate: i18n.translate('xpack.endpoint.resolver.certificateEventTypeDisplayName', { + defaultMessage: 'Certificate', + }), + cloud: i18n.translate('xpack.endpoint.resolver.cloudEventTypeDisplayName', { + defaultMessage: 'Cloud', + }), + database: i18n.translate('xpack.endpoint.resolver.databaseEventTypeDisplayName', { + defaultMessage: 'Database', + }), + driver: i18n.translate('xpack.endpoint.resolver.driverEventTypeDisplayName', { + defaultMessage: 'Driver', + }), + email: i18n.translate('xpack.endpoint.resolver.emailEventTypeDisplayName', { + defaultMessage: 'Email', + }), + file: i18n.translate('xpack.endpoint.resolver.fileEventTypeDisplayName', { + defaultMessage: 'File', + }), + host: i18n.translate('xpack.endpoint.resolver.hostEventTypeDisplayName', { + defaultMessage: 'Host', + }), + iam: i18n.translate('xpack.endpoint.resolver.iamEventTypeDisplayName', { + defaultMessage: 'IAM', + }), + iam_group: i18n.translate('xpack.endpoint.resolver.iam_groupEventTypeDisplayName', { + defaultMessage: 'IAM Group', + }), + intrusion_detection: i18n.translate( + 'xpack.endpoint.resolver.intrusion_detectionEventTypeDisplayName', + { + defaultMessage: 'Intrusion Detection', + } + ), + malware: i18n.translate('xpack.endpoint.resolver.malwareEventTypeDisplayName', { + defaultMessage: 'Malware', + }), + network_flow: i18n.translate('xpack.endpoint.resolver.network_flowEventTypeDisplayName', { + defaultMessage: 'Network Flow', + }), + network: i18n.translate('xpack.endpoint.resolver.networkEventTypeDisplayName', { + defaultMessage: 'Network', + }), + package: i18n.translate('xpack.endpoint.resolver.packageEventTypeDisplayName', { + defaultMessage: 'Package', + }), + process: i18n.translate('xpack.endpoint.resolver.processEventTypeDisplayName', { + defaultMessage: 'Process', + }), + registry: i18n.translate('xpack.endpoint.resolver.registryEventTypeDisplayName', { + defaultMessage: 'Registry', + }), + session: i18n.translate('xpack.endpoint.resolver.sessionEventTypeDisplayName', { + defaultMessage: 'Session', + }), + service: i18n.translate('xpack.endpoint.resolver.serviceEventTypeDisplayName', { + defaultMessage: 'Service', + }), + socket: i18n.translate('xpack.endpoint.resolver.socketEventTypeDisplayName', { + defaultMessage: 'Socket', + }), + vulnerability: i18n.translate('xpack.endpoint.resolver.vulnerabilityEventTypeDisplayName', { + defaultMessage: 'Vulnerability', + }), + web: i18n.translate('xpack.endpoint.resolver.webEventTypeDisplayName', { + defaultMessage: 'Web', + }), + alert: i18n.translate('xpack.endpoint.resolver.alertEventTypeDisplayName', { + defaultMessage: 'Alert', + }), + security: i18n.translate('xpack.endpoint.resolver.securityEventTypeDisplayName', { + defaultMessage: 'Security', + }), + dns: i18n.translate('xpack.endpoint.resolver.dnsEventTypeDisplayName', { + defaultMessage: 'DNS', + }), + clr: i18n.translate('xpack.endpoint.resolver.clrEventTypeDisplayName', { + defaultMessage: 'CLR', + }), + image_load: i18n.translate('xpack.endpoint.resolver.image_loadEventTypeDisplayName', { + defaultMessage: 'Image Load', + }), + powershell: i18n.translate('xpack.endpoint.resolver.powershellEventTypeDisplayName', { + defaultMessage: 'Powershell', + }), + wmi: i18n.translate('xpack.endpoint.resolver.wmiEventTypeDisplayName', { + defaultMessage: 'WMI', + }), + api: i18n.translate('xpack.endpoint.resolver.apiEventTypeDisplayName', { + defaultMessage: 'API', + }), + user: i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayName', { + defaultMessage: 'User', + }), + }; + return ( + displayNameRecord[schemaName] || + i18n.translate('xpack.endpoint.resolver.userEventTypeDisplayUnknown', { + defaultMessage: 'Unknown', + }) + ); }; /** From 7e03a259f58f9e7f3ab38342417ded934259f1d2 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Thu, 14 May 2020 14:45:28 -0400 Subject: [PATCH 62/74] R Austin / J Brown review: Add test coverage for selectors --- .../resolver/store/data/selectors.test.ts | 52 +++++++++++++++++++ .../resolver/view/process_event_dot.tsx | 1 - 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts new file mode 100644 index 00000000000000..368641c05c3988 --- /dev/null +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts @@ -0,0 +1,52 @@ +/* + * 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 { Store, createStore } from 'redux'; +import { DataAction } from './action'; +import { dataReducer } from './reducer'; +import { DataState, RelatedEventDataEntry, RelatedEventDataEntryWithStats } from '../../types'; +import { ResolverEvent } from '../../../../../common/types'; +import { relatedEventStats, relatedEvents } from './selectors'; + +describe('resolver data selectors', () => { + const store: Store = createStore(dataReducer, undefined); + describe('when related event data is reduced into state with no results', () => { + const relatedEventInfoBeforeAction = new Map(relatedEvents(store.getState()) || []); + beforeEach(() => { + const payload: Map = new Map(); + const action: DataAction = { type: 'serverReturnedRelatedEventData', payload }; + store.dispatch(action); + }); + it('should have the same related info as before the action', () => { + const relatedInfoAfterAction = relatedEvents(store.getState()); + expect(relatedInfoAfterAction).toEqual(relatedEventInfoBeforeAction); + }); + }); + describe('when related event data is reduced into state with 2 dns results', () => { + const mockBaseEvent = {} as ResolverEvent; + beforeEach(() => { + function dnsRelatedEventEntry() { + const fakeEvent = {} as ResolverEvent; + return { relatedEvent: fakeEvent, relatedEventType: 'dns' }; + } + const payload: Map = new Map([ + [ + mockBaseEvent, + { + relatedEvents: [dnsRelatedEventEntry(), dnsRelatedEventEntry()], + }, + ], + ]); + const action: DataAction = { type: 'serverReturnedRelatedEventData', payload }; + store.dispatch(action); + }); + it('should compile stats reflecting a count of 2 for dns', () => { + const actualStats = relatedEventStats(store.getState()); + const statsForFakeEvent = actualStats.get(mockBaseEvent)! as RelatedEventDataEntryWithStats; + expect(statsForFakeEvent.stats).toEqual({ dns: 2 }); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index f22f055a686544..0be0dd8e24faa6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -72,7 +72,6 @@ const nodeAssets = { const getDisplayName: (schemaName: string) => string = function nameInSchemaToDisplayName( schemaName: string ) { - const displayNameRecord: Record = { application: i18n.translate('xpack.endpoint.resolver.applicationEventTypeDisplayName', { defaultMessage: 'Application', From f601477aaf7b9d3190997562aba05f04a854ee48 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 11:52:39 -0400 Subject: [PATCH 63/74] R Austin review: undo Memos, add comments --- .../public/embeddables/resolver/view/submenu.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx index b04a884bb500b5..28d75b17b682cb 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -113,6 +113,7 @@ export const NodeSubMenu = styled( const [menuIsOpen, setMenuOpen] = useState(false); const handleMenuOpenClick = useCallback( (clickEvent: React.MouseEvent) => { + // stopping propagation/default to prevent other node animations from triggering clickEvent.preventDefault(); clickEvent.stopPropagation(); setMenuOpen(!menuIsOpen); @@ -121,6 +122,7 @@ export const NodeSubMenu = styled( ); const handleMenuActionClick = useCallback( (clickEvent: React.MouseEvent) => { + // stopping propagation/default to prevent other node animations from triggering clickEvent.preventDefault(); clickEvent.stopPropagation(); if (typeof menuAction === 'function') menuAction(); @@ -128,13 +130,8 @@ export const NodeSubMenu = styled( }, [menuAction] ); - const isMenuDataAvailable = useMemo(() => { - return typeof optionsWithActions === 'object'; - }, [optionsWithActions]); - const isMenuLoading = useMemo(() => { - return optionsWithActions === 'waitingForRelatedEventData'; - }, [optionsWithActions]); + const isMenuLoading = optionsWithActions === 'waitingForRelatedEventData'; if (!optionsWithActions) { /** @@ -156,7 +153,9 @@ export const NodeSubMenu = styled( return (
{menuTitle} - {menuIsOpen && isMenuDataAvailable && ( + {menuIsOpen && typeof optionsWithActions === 'object' && ( )}
From 1985a31e17cdbdacd7a161c8f152d704f79e311c Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 11:59:50 -0400 Subject: [PATCH 64/74] R Austin review: adjust type on submenu for readability --- .../public/embeddables/resolver/view/submenu.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx index 28d75b17b682cb..e295dd647f8dd0 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -101,15 +101,9 @@ export const NodeSubMenu = styled( menuAction, optionsWithActions, className, - }: { menuTitle: string; className?: string; menuAction: () => unknown } & ( - | { - optionsWithActions: - | ResolverSubmenuOptionList - | typeof subMenuAssets.initialMenuStatus - | 'waitingForRelatedEventData'; - } - | { optionsWithActions?: undefined } - )) => { + }: { menuTitle: string; className?: string; menuAction: () => unknown } & { + optionsWithActions?: ResolverSubmenuOptionList | string | undefined; + }) => { const [menuIsOpen, setMenuOpen] = useState(false); const handleMenuOpenClick = useCallback( (clickEvent: React.MouseEvent) => { From 91f2bd61331f950098762915dbf9c408b9ab0128 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 12:10:48 -0400 Subject: [PATCH 65/74] R Austin review: move selectable options to state hook --- .../endpoint/public/embeddables/resolver/view/submenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx index e295dd647f8dd0..5e7a1377acd918 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -52,7 +52,7 @@ const OptionList = React.memo( subMenuOptions: ResolverSubmenuOptionList; isLoading: boolean; }) => { - const selectableOptions = + const [options, setOptions] = useState(() => typeof subMenuOptions !== 'object' ? [] : subMenuOptions.map((opt: ResolverSubmenuOption): { @@ -62,14 +62,14 @@ const OptionList = React.memo( return opt.prefix ? { label: opt.optionTitle, - prepend: {opt.prefix}, + prepend: {opt.prefix} , } : { label: opt.optionTitle, prepend: , }; - }); - const [options, setOptions] = useState(selectableOptions); + }) + ); return useMemo( () => ( Date: Mon, 18 May 2020 12:24:24 -0400 Subject: [PATCH 66/74] R Austin review: simplify type, remove memo, remove inert return --- .../public/embeddables/resolver/view/process_event_dot.tsx | 5 ++--- .../endpoint/public/embeddables/resolver/view/submenu.tsx | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index 0be0dd8e24faa6..c2e7070fc77ca8 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -367,13 +367,12 @@ export const ProcessEventDot = styled( category: statsEntry[0], }, }); - return false; }, }; }); }, [relatedEvents, dispatch, event]); - const relatedEventStatusOrOptions = useMemo(() => { + const relatedEventStatusOrOptions = (() => { if (!relatedEvents) { // If related events have not yet been requested return subMenuAssets.initialMenuStatus; @@ -388,7 +387,7 @@ export const ProcessEventDot = styled( return relatedEvents; } return relatedEventOptions; - }, [relatedEvents, relatedEventOptions]); + })(); /* eslint-disable jsx-a11y/click-events-have-key-events */ /** diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx index 5e7a1377acd918..9f6427d801ce43 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/submenu.tsx @@ -39,10 +39,7 @@ interface ResolverSubmenuOption { prefix?: number | JSX.Element; } -export type ResolverSubmenuOptionList = - | ResolverSubmenuOption[] - | 'waitingForRelatedEventData' - | typeof subMenuAssets.initialMenuStatus; +export type ResolverSubmenuOptionList = ResolverSubmenuOption[] | string; const OptionList = React.memo( ({ From 5bb04113567fcb0d55e373ed9da311f3a9d35ea7 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 12:58:40 -0400 Subject: [PATCH 67/74] R Austin review: add comments, move testing instantiation --- .../resolver/store/data/selectors.test.ts | 13 ++++++++++--- .../embeddables/resolver/store/data/selectors.ts | 10 ++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts index 368641c05c3988..561b0da12bcb10 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.test.ts @@ -7,15 +7,21 @@ import { Store, createStore } from 'redux'; import { DataAction } from './action'; import { dataReducer } from './reducer'; -import { DataState, RelatedEventDataEntry, RelatedEventDataEntryWithStats } from '../../types'; +import { + DataState, + RelatedEventDataEntry, + RelatedEventDataEntryWithStats, + RelatedEventData, +} from '../../types'; import { ResolverEvent } from '../../../../../common/types'; import { relatedEventStats, relatedEvents } from './selectors'; describe('resolver data selectors', () => { const store: Store = createStore(dataReducer, undefined); describe('when related event data is reduced into state with no results', () => { - const relatedEventInfoBeforeAction = new Map(relatedEvents(store.getState()) || []); + let relatedEventInfoBeforeAction: RelatedEventData; beforeEach(() => { + relatedEventInfoBeforeAction = new Map(relatedEvents(store.getState()) || []); const payload: Map = new Map(); const action: DataAction = { type: 'serverReturnedRelatedEventData', payload }; store.dispatch(action); @@ -26,8 +32,9 @@ describe('resolver data selectors', () => { }); }); describe('when related event data is reduced into state with 2 dns results', () => { - const mockBaseEvent = {} as ResolverEvent; + let mockBaseEvent: ResolverEvent; beforeEach(() => { + mockBaseEvent = {} as ResolverEvent; function dnsRelatedEventEntry() { const fakeEvent = {} as ResolverEvent; return { relatedEvent: fakeEvent, relatedEventType: 'dns' }; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index cc1d1a36acda14..dcf7e3e1334ea6 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -414,6 +414,10 @@ export const relatedEventResults = function(data: DataState) { return data.resultsEnrichedWithRelatedEventInfo; }; +/** + * This selector compiles the related event data attached in `relatedEventResults` + * into a `RelatedEventData` map of ResolverEvents to statistics about their related events + */ export const relatedEventStats = createSelector(relatedEventResults, function getRelatedEvents( /* eslint-disable no-shadow */ relatedEventResults @@ -431,10 +435,13 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge if (typeof newStatsEntry === 'object') { // compile stats if (newStatsEntry instanceof Error) { + // If the entry is an error, return it as is relatedEventStats.set(updatedEvent, newStatsEntry); continue; } /** + * Otherwise, it should be a valid stats entry. + * Do the work to compile the stats. * Folowing reduction, this will be a record like * {DNS: 10, File: 2} etc. */ @@ -456,6 +463,9 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge return relatedEventStats; }); +/** + * This selects `RelatedEventData` maps specifically for graphable processes + */ export const relatedEvents = createSelector( graphableProcesses, relatedEventStats, From 7e85044b0d9aa5eefa768572b1ada5f63a7c17ce Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 13:02:30 -0400 Subject: [PATCH 68/74] R Austin review: remove unnecessary type check --- .../endpoint/public/embeddables/resolver/store/data/reducer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index e9e8d0ea246351..ddd423830560c1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -48,7 +48,7 @@ export const dataReducer: Reducer = (state = initialS return state; } else if (action.type === 'serverReturnedRelatedEventData') { const statsMap = state.resultsEnrichedWithRelatedEventInfo; - if (statsMap && typeof statsMap?.set === 'function') { + if (statsMap) { const relatedDataEntries = new Map([...statsMap, ...action.payload]); return { ...state, resultsEnrichedWithRelatedEventInfo: relatedDataEntries }; } From c06da13a8debf6ad98054450d9b5b58a2f302c16 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 13:11:18 -0400 Subject: [PATCH 69/74] R Austin review: direct return --- x-pack/plugins/endpoint/common/models/event.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/common/models/event.ts b/x-pack/plugins/endpoint/common/models/event.ts index 68d398473f4099..192daba4a717d9 100644 --- a/x-pack/plugins/endpoint/common/models/event.ts +++ b/x-pack/plugins/endpoint/common/models/event.ts @@ -52,7 +52,7 @@ export function eventType(event: ResolverEvent): string { if (isLegacyEvent(event)) { const legacyFullType = event.endgame.event_type_full; if (legacyFullType) { - eventCategoryToReturn = legacyFullType; + return legacyFullType; } } else { const eventCategories = event.event.category; From e742ccee19faa4ec2fdac4004093e390fcddc106 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 13:23:11 -0400 Subject: [PATCH 70/74] R Austin review: Add comments to actions --- .../public/embeddables/resolver/store/actions.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts index 58ef81d328b89b..462f6e251d5d09 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts @@ -86,14 +86,23 @@ interface UserSelectedResolverNode { }; } +/** + * This action should dispatch to indicate that the user chose to + * focus on examining the related events of a particular ResolverEvent. + * Optionally, this can be bound by a category of related events (e.g. 'file' or 'dns') + */ interface UserSelectedRelatedEventCategory { readonly type: 'userSelectedRelatedEventCategory'; readonly payload: { subject: ResolverEvent; - category: string; + category?: string; }; } +/** + * This action should dispatch to indicate that the user chose to focus + * on examining alerts related to a particular ResolverEvent + */ interface UserSelectedRelatedAlerts { readonly type: 'userSelectedRelatedAlerts'; readonly payload: ResolverEvent; From fe978f64d5d511a04cf00035649309c9e58e891d Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 13:30:28 -0400 Subject: [PATCH 71/74] R Austin review: change payload for failed action --- .../endpoint/public/embeddables/resolver/store/data/action.ts | 2 +- .../endpoint/public/embeddables/resolver/store/data/reducer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts index 1d58785be616ab..8c84d8f82b874f 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts @@ -29,7 +29,7 @@ interface ServerReturnedRelatedEventData { */ interface ServerFailedToReturnRelatedEventData { readonly type: 'serverFailedToReturnRelatedEventData'; - readonly payload: [ResolverEvent]; + readonly payload: ResolverEvent; } export type DataAction = diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index ddd423830560c1..f2e4e930e09c0f 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -41,7 +41,7 @@ export const dataReducer: Reducer = (state = initialS const statsMap = state.resultsEnrichedWithRelatedEventInfo; if (statsMap) { const currentStatsMap = new Map(statsMap); - const [resolverEvent] = action.payload; + const resolverEvent = action.payload; currentStatsMap.set(resolverEvent, new Error('error requesting related events')); return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; } From 7750c97df9b20963dd304d32a77ba602adb28890 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 13:40:06 -0400 Subject: [PATCH 72/74] R Austin review: More specific related type? --- .../endpoint/public/embeddables/resolver/store/middleware.ts | 2 +- x-pack/plugins/endpoint/public/embeddables/resolver/types.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index c7d8deda822730..a42b43ef5c40b1 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -122,7 +122,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { if (apiResults instanceof Error) { api.dispatch({ type: 'serverFailedToReturnRelatedEventData', - payload: [results[0]], + payload: results[0], }); continue; } diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index a87003c2330b9e..071c35ea064cfd 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -9,6 +9,7 @@ import { Store } from 'redux'; import { ResolverAction } from './store/actions'; export { ResolverAction } from './store/actions'; import { ResolverEvent } from '../../../common/types'; +import { eventType } from '../../../common/models/event'; /** * Redux state for the Resolver feature. Properties on this interface are populated via multiple reducers using redux's `combineReducers`. @@ -137,7 +138,7 @@ export type CameraState = { export interface RelatedEventDataEntry { relatedEvents: Array<{ relatedEvent: ResolverEvent; - relatedEventType: string; + relatedEventType: ReturnType; }>; } From 9e07c11f440bb0f0b306ebb11355a8495ddf7f0a Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 15:15:50 -0400 Subject: [PATCH 73/74] R Austin review: Remove Error construction --- .../public/embeddables/resolver/store/data/reducer.ts | 2 +- .../embeddables/resolver/store/data/selectors.ts | 11 +++++------ .../public/embeddables/resolver/store/middleware.ts | 6 +++--- .../endpoint/public/embeddables/resolver/types.ts | 7 +++++-- .../embeddables/resolver/view/process_event_dot.tsx | 4 ++-- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index f2e4e930e09c0f..c06dc3291e4108 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -42,7 +42,7 @@ export const dataReducer: Reducer = (state = initialS if (statsMap) { const currentStatsMap = new Map(statsMap); const resolverEvent = action.payload; - currentStatsMap.set(resolverEvent, new Error('error requesting related events')); + currentStatsMap.set(resolverEvent, 'error'); return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; } return state; diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts index dcf7e3e1334ea6..413f4db1cc99e0 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts @@ -432,13 +432,12 @@ export const relatedEventStats = createSelector(relatedEventResults, function ge for (const updatedEvent of relatedEventResults.keys()) { const newStatsEntry = relatedEventResults.get(updatedEvent); + if (newStatsEntry === 'error') { + // If the entry is an error, return it as is + relatedEventStats.set(updatedEvent, newStatsEntry); + continue; + } if (typeof newStatsEntry === 'object') { - // compile stats - if (newStatsEntry instanceof Error) { - // If the entry is an error, return it as is - relatedEventStats.set(updatedEvent, newStatsEntry); - continue; - } /** * Otherwise, it should be a valid stats entry. * Do the work to compile the stats. diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts index a42b43ef5c40b1..06758022b05c53 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/middleware.ts @@ -31,7 +31,7 @@ function flattenEvents(children: ResolverNode[], events: ResolverEvent[] = []): }, events); } -type RelatedEventAPIResponse = Error | { events: ResolverEvent[] }; +type RelatedEventAPIResponse = 'error' | { events: ResolverEvent[] }; /** * As the design goal of this stopgap was to prevent saturating the server with /events * requests, this generator intentionally processes events in serial rather than in parallel. @@ -52,7 +52,7 @@ async function* getEachRelatedEventsResult( query: { events: 100 }, }); } catch (e) { - result = new Error(`Error fetching related events for entity=${id}`); + result = 'error'; } yield [eventToQueryForRelateds, result]; } @@ -119,7 +119,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => { * [event requested , response of event against the /related api] */ const [baseEvent, apiResults] = results; - if (apiResults instanceof Error) { + if (apiResults === 'error') { api.dispatch({ type: 'serverFailedToReturnRelatedEventData', payload: results[0], diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index 071c35ea064cfd..ec5ac188d7e6ee 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -146,7 +146,10 @@ export interface RelatedEventDataEntry { * Represents the status of the request for related event data, which will be either the data, * a value indicating that it's still waiting for the data or an Error indicating the data can't be retrieved as expected */ -export type RelatedEventDataResults = RelatedEventDataEntry | 'waitingForRelatedEventData' | Error; +export type RelatedEventDataResults = + | RelatedEventDataEntry + | 'waitingForRelatedEventData' + | 'error'; /** * This represents the raw related events data enhanced with statistics @@ -166,7 +169,7 @@ export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { export type RelatedEventEntryWithStatsOrWaiting = | RelatedEventDataEntryWithStats | `waitingForRelatedEventData` - | Error; + | 'error'; /** * This represents a Map that will return either a `RelatedEventDataEntryWithStats` diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx index c2e7070fc77ca8..32928d511a1f90 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/view/process_event_dot.tsx @@ -344,7 +344,7 @@ export const ProcessEventDot = styled( * e.g. "10 DNS", "230 File" */ const relatedEventOptions = useMemo(() => { - if (relatedEvents instanceof Error) { + if (relatedEvents === 'error') { // Return an empty set of options if there was an error requesting them return []; } @@ -377,7 +377,7 @@ export const ProcessEventDot = styled( // If related events have not yet been requested return subMenuAssets.initialMenuStatus; } - if (relatedEvents instanceof Error) { + if (relatedEvents === 'error') { // If there was an error when we tried to request the events return subMenuAssets.menuError; } From 002d631284c6ef152b751b4df0f3a6122a049524 Mon Sep 17 00:00:00 2001 From: Brent Kimmel Date: Mon, 18 May 2020 15:28:01 -0400 Subject: [PATCH 74/74] R Austin review: mark relatedEventInfo non-optional --- .../resolver/store/data/reducer.ts | 41 ++++++++----------- .../public/embeddables/resolver/types.ts | 2 +- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts index c06dc3291e4108..9dd6bcdf385ae8 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts @@ -26,33 +26,24 @@ export const dataReducer: Reducer = (state = initialS }; } else if (action.type === 'userRequestedRelatedEventData') { const resolverEvent = action.payload; - const statsMap = state.resultsEnrichedWithRelatedEventInfo; - if (statsMap) { - const currentStatsMap = new Map(statsMap); - /** - * Set the waiting indicator for this event to indicate that related event results are pending. - * It will be replaced by the actual results from the API when they are returned. - */ - currentStatsMap.set(resolverEvent, 'waitingForRelatedEventData'); - return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; - } - return state; + const currentStatsMap = new Map(state.resultsEnrichedWithRelatedEventInfo); + /** + * Set the waiting indicator for this event to indicate that related event results are pending. + * It will be replaced by the actual results from the API when they are returned. + */ + currentStatsMap.set(resolverEvent, 'waitingForRelatedEventData'); + return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; } else if (action.type === 'serverFailedToReturnRelatedEventData') { - const statsMap = state.resultsEnrichedWithRelatedEventInfo; - if (statsMap) { - const currentStatsMap = new Map(statsMap); - const resolverEvent = action.payload; - currentStatsMap.set(resolverEvent, 'error'); - return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; - } - return state; + const currentStatsMap = new Map(state.resultsEnrichedWithRelatedEventInfo); + const resolverEvent = action.payload; + currentStatsMap.set(resolverEvent, 'error'); + return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; } else if (action.type === 'serverReturnedRelatedEventData') { - const statsMap = state.resultsEnrichedWithRelatedEventInfo; - if (statsMap) { - const relatedDataEntries = new Map([...statsMap, ...action.payload]); - return { ...state, resultsEnrichedWithRelatedEventInfo: relatedDataEntries }; - } - return state; + const relatedDataEntries = new Map([ + ...state.resultsEnrichedWithRelatedEventInfo, + ...action.payload, + ]); + return { ...state, resultsEnrichedWithRelatedEventInfo: relatedDataEntries }; } else if (action.type === 'appRequestedResolverData') { return { ...state, diff --git a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts index ec5ac188d7e6ee..32fefba8f0f207 100644 --- a/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts +++ b/x-pack/plugins/endpoint/public/embeddables/resolver/types.ts @@ -184,7 +184,7 @@ export interface DataState { readonly results: readonly ResolverEvent[]; isLoading: boolean; hasError: boolean; - resultsEnrichedWithRelatedEventInfo?: Map; + resultsEnrichedWithRelatedEventInfo: Map; } export type Vector2 = readonly [number, number];