diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts index 9e1c396723a27..0826391a10688 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts @@ -13,6 +13,7 @@ import { mockTreeWithNoAncestorsAnd2Children, mockTreeWith2AncestorsAndNoChildren, mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents, + mockTreeWithAllProcessesTerminated, } from '../mocks/resolver_tree'; import { uniquePidForProcess } from '../../models/process_event'; import { EndpointEvent } from '../../../../common/endpoint/types'; @@ -299,6 +300,34 @@ describe('data state', () => { expect(selectors.ariaFlowtoCandidate(state())(secondAncestorID)).toBe(null); }); }); + describe('with a tree with all processes terminated', () => { + const originID = 'c'; + const firstAncestorID = 'b'; + const secondAncestorID = 'a'; + beforeEach(() => { + actions.push({ + type: 'serverReturnedResolverData', + payload: { + result: mockTreeWithAllProcessesTerminated({ + originID, + firstAncestorID, + secondAncestorID, + }), + // this value doesn't matter + databaseDocumentID: '', + }, + }); + }); + it('should have origin as terminated', () => { + expect(selectors.isProcessTerminated(state())(originID)).toBe(true); + }); + it('should have first ancestor as termianted', () => { + expect(selectors.isProcessTerminated(state())(firstAncestorID)).toBe(true); + }); + it('should have second ancestor as terminated', () => { + expect(selectors.isProcessTerminated(state())(secondAncestorID)).toBe(true); + }); + }); describe('with a tree with 2 children and no ancestors', () => { const originID = 'c'; const firstChildID = 'd'; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 1d65b406306a3..ea0cb8663d11d 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -105,6 +105,19 @@ export const terminatedProcesses = createSelector(resolverTreeResponse, function ); }); +/** + * A function that given an entity id returns a boolean indicating if the id is in the set of terminated processes. + */ +export const isProcessTerminated = createSelector(terminatedProcesses, function ( + /* eslint-disable no-shadow */ + terminatedProcesses + /* eslint-enable no-shadow */ +) { + return (entityId: string) => { + return terminatedProcesses.has(entityId); + }; +}); + /** * Process events that will be graphed. */ diff --git a/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts b/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts index b58ea73e1fdc7..8f2e0ad3a6d85 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts @@ -14,16 +14,18 @@ export function mockEndpointEvent({ name, parentEntityId, timestamp, + lifecycleType, }: { entityID: string; name: string; parentEntityId: string | undefined; timestamp: number; + lifecycleType?: string; }): EndpointEvent { return { '@timestamp': timestamp, event: { - type: 'start', + type: lifecycleType ? lifecycleType : 'start', category: 'process', }, process: { diff --git a/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts b/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts index 2860eec5a6ab6..ae43955f4c47c 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts @@ -46,6 +46,69 @@ export function mockTreeWith2AncestorsAndNoChildren({ } as unknown) as ResolverTree; } +export function mockTreeWithAllProcessesTerminated({ + originID, + firstAncestorID, + secondAncestorID, +}: { + secondAncestorID: string; + firstAncestorID: string; + originID: string; +}): ResolverTree { + const secondAncestor: ResolverEvent = mockEndpointEvent({ + entityID: secondAncestorID, + name: 'a', + parentEntityId: 'none', + timestamp: 0, + }); + const firstAncestor: ResolverEvent = mockEndpointEvent({ + entityID: firstAncestorID, + name: 'b', + parentEntityId: secondAncestorID, + timestamp: 1, + }); + const originEvent: ResolverEvent = mockEndpointEvent({ + entityID: originID, + name: 'c', + parentEntityId: firstAncestorID, + timestamp: 2, + }); + const secondAncestorTermination: ResolverEvent = mockEndpointEvent({ + entityID: secondAncestorID, + name: 'a', + parentEntityId: 'none', + timestamp: 0, + lifecycleType: 'end', + }); + const firstAncestorTermination: ResolverEvent = mockEndpointEvent({ + entityID: firstAncestorID, + name: 'b', + parentEntityId: secondAncestorID, + timestamp: 1, + lifecycleType: 'end', + }); + const originEventTermination: ResolverEvent = mockEndpointEvent({ + entityID: originID, + name: 'c', + parentEntityId: firstAncestorID, + timestamp: 2, + lifecycleType: 'end', + }); + return ({ + entityID: originID, + children: { + childNodes: [], + }, + ancestry: { + ancestors: [ + { lifecycle: [secondAncestor, secondAncestorTermination] }, + { lifecycle: [firstAncestor, firstAncestorTermination] }, + ], + }, + lifecycle: [originEvent, originEventTermination], + } as unknown) as ResolverTree; +} + export function mockTreeWithNoAncestorsAnd2Children({ originID, firstChildID, diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 66d7e04d118ed..87ef8d5d095ef 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -53,6 +53,14 @@ export const userIsPanning = composeSelectors(cameraStateSelector, cameraSelecto */ export const isAnimating = composeSelectors(cameraStateSelector, cameraSelectors.isAnimating); +/** + * Whether or not a given entity id is in the set of termination events. + */ +export const isProcessTerminated = composeSelectors( + dataStateSelector, + dataSelectors.isProcessTerminated +); + /** * Given a nodeID (aka entity_id) get the indexed process event. * Legacy functions take process events instead of nodeID, use this to get diff --git a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx index cb0acdc29ceb1..83d3930065da6 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panel.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panel.tsx @@ -162,19 +162,10 @@ const PanelContent = memo(function PanelContent() { return 'processListWithCounts'; }, [uiSelectedEvent, crumbEvent, crumbId, graphableProcessEntityIds]); - const terminatedProcesses = useSelector(selectors.terminatedProcesses); - const processEntityId = uiSelectedEvent ? event.entityId(uiSelectedEvent) : undefined; - const isProcessTerminated = processEntityId ? terminatedProcesses.has(processEntityId) : false; - const panelInstance = useMemo(() => { if (panelToShow === 'processDetails') { return ( - + ); } @@ -213,13 +204,7 @@ const PanelContent = memo(function PanelContent() { ); } // The default 'Event List' / 'List of all processes' view - return ( - - ); + return ; }, [ uiSelectedEvent, crumbEvent, @@ -227,7 +212,6 @@ const PanelContent = memo(function PanelContent() { pushToQueryParams, relatedStatsForIdFromParams, panelToShow, - isProcessTerminated, ]); return <>{panelInstance}; diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx index 5d90cd11d31af..29c7676d2167d 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { memo, useMemo } from 'react'; +import { useSelector } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { htmlIdGenerator, @@ -15,6 +16,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from 'react-intl'; +import * as selectors from '../../store/selectors'; import * as event from '../../../../common/endpoint/models/event'; import { CrumbInfo, formatDate, StyledBreadcrumbs } from './panel_content_utilities'; import { @@ -41,16 +43,14 @@ const StyledDescriptionList = styled(EuiDescriptionList)` */ export const ProcessDetails = memo(function ProcessDetails({ processEvent, - isProcessTerminated, - isProcessOrigin, pushToQueryParams, }: { processEvent: ResolverEvent; - isProcessTerminated: boolean; - isProcessOrigin: boolean; pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown; }) { const processName = event.eventName(processEvent); + const entityId = event.entityId(processEvent); + const isProcessTerminated = useSelector(selectors.isProcessTerminated)(entityId); const processInfoEntry = useMemo(() => { const eventTime = event.eventTimestamp(processEvent); const dateTime = eventTime ? formatDate(eventTime) : ''; @@ -151,8 +151,8 @@ export const ProcessDetails = memo(function ProcessDetails({ if (!processEvent) { return { descriptionText: '' }; } - return cubeAssetsForNode(isProcessTerminated, isProcessOrigin); - }, [processEvent, cubeAssetsForNode, isProcessTerminated, isProcessOrigin]); + return cubeAssetsForNode(isProcessTerminated, false); + }, [processEvent, cubeAssetsForNode, isProcessTerminated]); const titleId = useMemo(() => htmlIdGenerator('resolverTable')(), []); return ( @@ -161,10 +161,7 @@ export const ProcessDetails = memo(function ProcessDetails({

- + {processName}

diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx index 6f9bfad8c08c2..efb96cde431e5 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx @@ -50,12 +50,8 @@ const StyledLimitWarning = styled(LimitWarning)` */ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ pushToQueryParams, - isProcessTerminated, - isProcessOrigin, }: { pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown; - isProcessTerminated: boolean; - isProcessOrigin: boolean; }) { interface ProcessTableView { name: string; @@ -65,6 +61,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ const dispatch = useResolverDispatch(); const { timestamp } = useContext(SideEffectContext); + const isProcessTerminated = useSelector(selectors.isProcessTerminated); const handleBringIntoViewClick = useCallback( (processTableViewItem) => { dispatch({ @@ -92,6 +89,8 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ sortable: true, truncateText: true, render(name: string, item: ProcessTableView) { + const entityId = event.entityId(item.event); + const isTerminated = isProcessTerminated(entityId); return name === '' ? ( {i18n.translate( @@ -108,10 +107,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ pushToQueryParams({ crumbId: event.entityId(item.event), crumbEvent: '' }); }} > - + {name} ); @@ -143,7 +139,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({ }, }, ], - [pushToQueryParams, handleBringIntoViewClick, isProcessOrigin, isProcessTerminated] + [pushToQueryParams, handleBringIntoViewClick, isProcessTerminated] ); const { processNodePositions } = useSelector(selectors.layout); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx index 98eea51a011b6..b073324b27f9b 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx @@ -13,13 +13,11 @@ import { useResolverTheme } from '../assets'; */ export const CubeForProcess = memo(function CubeForProcess({ isProcessTerminated, - isProcessOrigin, }: { isProcessTerminated: boolean; - isProcessOrigin: boolean; }) { const { cubeAssetsForNode } = useResolverTheme(); - const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, isProcessOrigin); + const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, false); return ( <>