Skip to content

Commit 604321c

Browse files
[7.9] [Security Solution] Show proper icon for termination status of all processes (#73235) (#73386)
* Show proper icon for termination status of all processes * Add basic test for isProcessTerminated selector
1 parent a3c79fb commit 604321c

File tree

9 files changed

+131
-41
lines changed

9 files changed

+131
-41
lines changed

x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
mockTreeWithNoAncestorsAnd2Children,
1414
mockTreeWith2AncestorsAndNoChildren,
1515
mockTreeWith1AncestorAnd2ChildrenAndAllNodesHave2GraphableEvents,
16+
mockTreeWithAllProcessesTerminated,
1617
} from '../mocks/resolver_tree';
1718
import { uniquePidForProcess } from '../../models/process_event';
1819
import { EndpointEvent } from '../../../../common/endpoint/types';
@@ -299,6 +300,34 @@ describe('data state', () => {
299300
expect(selectors.ariaFlowtoCandidate(state())(secondAncestorID)).toBe(null);
300301
});
301302
});
303+
describe('with a tree with all processes terminated', () => {
304+
const originID = 'c';
305+
const firstAncestorID = 'b';
306+
const secondAncestorID = 'a';
307+
beforeEach(() => {
308+
actions.push({
309+
type: 'serverReturnedResolverData',
310+
payload: {
311+
result: mockTreeWithAllProcessesTerminated({
312+
originID,
313+
firstAncestorID,
314+
secondAncestorID,
315+
}),
316+
// this value doesn't matter
317+
databaseDocumentID: '',
318+
},
319+
});
320+
});
321+
it('should have origin as terminated', () => {
322+
expect(selectors.isProcessTerminated(state())(originID)).toBe(true);
323+
});
324+
it('should have first ancestor as termianted', () => {
325+
expect(selectors.isProcessTerminated(state())(firstAncestorID)).toBe(true);
326+
});
327+
it('should have second ancestor as terminated', () => {
328+
expect(selectors.isProcessTerminated(state())(secondAncestorID)).toBe(true);
329+
});
330+
});
302331
describe('with a tree with 2 children and no ancestors', () => {
303332
const originID = 'c';
304333
const firstChildID = 'd';

x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,19 @@ export const terminatedProcesses = createSelector(resolverTreeResponse, function
105105
);
106106
});
107107

108+
/**
109+
* A function that given an entity id returns a boolean indicating if the id is in the set of terminated processes.
110+
*/
111+
export const isProcessTerminated = createSelector(terminatedProcesses, function (
112+
/* eslint-disable no-shadow */
113+
terminatedProcesses
114+
/* eslint-enable no-shadow */
115+
) {
116+
return (entityId: string) => {
117+
return terminatedProcesses.has(entityId);
118+
};
119+
});
120+
108121
/**
109122
* Process events that will be graphed.
110123
*/

x-pack/plugins/security_solution/public/resolver/store/mocks/endpoint_event.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ export function mockEndpointEvent({
1414
name,
1515
parentEntityId,
1616
timestamp,
17+
lifecycleType,
1718
}: {
1819
entityID: string;
1920
name: string;
2021
parentEntityId: string | undefined;
2122
timestamp: number;
23+
lifecycleType?: string;
2224
}): EndpointEvent {
2325
return {
2426
'@timestamp': timestamp,
2527
event: {
26-
type: 'start',
28+
type: lifecycleType ? lifecycleType : 'start',
2729
category: 'process',
2830
},
2931
process: {

x-pack/plugins/security_solution/public/resolver/store/mocks/resolver_tree.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,69 @@ export function mockTreeWith2AncestorsAndNoChildren({
4646
} as unknown) as ResolverTree;
4747
}
4848

49+
export function mockTreeWithAllProcessesTerminated({
50+
originID,
51+
firstAncestorID,
52+
secondAncestorID,
53+
}: {
54+
secondAncestorID: string;
55+
firstAncestorID: string;
56+
originID: string;
57+
}): ResolverTree {
58+
const secondAncestor: ResolverEvent = mockEndpointEvent({
59+
entityID: secondAncestorID,
60+
name: 'a',
61+
parentEntityId: 'none',
62+
timestamp: 0,
63+
});
64+
const firstAncestor: ResolverEvent = mockEndpointEvent({
65+
entityID: firstAncestorID,
66+
name: 'b',
67+
parentEntityId: secondAncestorID,
68+
timestamp: 1,
69+
});
70+
const originEvent: ResolverEvent = mockEndpointEvent({
71+
entityID: originID,
72+
name: 'c',
73+
parentEntityId: firstAncestorID,
74+
timestamp: 2,
75+
});
76+
const secondAncestorTermination: ResolverEvent = mockEndpointEvent({
77+
entityID: secondAncestorID,
78+
name: 'a',
79+
parentEntityId: 'none',
80+
timestamp: 0,
81+
lifecycleType: 'end',
82+
});
83+
const firstAncestorTermination: ResolverEvent = mockEndpointEvent({
84+
entityID: firstAncestorID,
85+
name: 'b',
86+
parentEntityId: secondAncestorID,
87+
timestamp: 1,
88+
lifecycleType: 'end',
89+
});
90+
const originEventTermination: ResolverEvent = mockEndpointEvent({
91+
entityID: originID,
92+
name: 'c',
93+
parentEntityId: firstAncestorID,
94+
timestamp: 2,
95+
lifecycleType: 'end',
96+
});
97+
return ({
98+
entityID: originID,
99+
children: {
100+
childNodes: [],
101+
},
102+
ancestry: {
103+
ancestors: [
104+
{ lifecycle: [secondAncestor, secondAncestorTermination] },
105+
{ lifecycle: [firstAncestor, firstAncestorTermination] },
106+
],
107+
},
108+
lifecycle: [originEvent, originEventTermination],
109+
} as unknown) as ResolverTree;
110+
}
111+
49112
export function mockTreeWithNoAncestorsAnd2Children({
50113
originID,
51114
firstChildID,

x-pack/plugins/security_solution/public/resolver/store/selectors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ export const userIsPanning = composeSelectors(cameraStateSelector, cameraSelecto
5353
*/
5454
export const isAnimating = composeSelectors(cameraStateSelector, cameraSelectors.isAnimating);
5555

56+
/**
57+
* Whether or not a given entity id is in the set of termination events.
58+
*/
59+
export const isProcessTerminated = composeSelectors(
60+
dataStateSelector,
61+
dataSelectors.isProcessTerminated
62+
);
63+
5664
/**
5765
* Given a nodeID (aka entity_id) get the indexed process event.
5866
* Legacy functions take process events instead of nodeID, use this to get

x-pack/plugins/security_solution/public/resolver/view/panel.tsx

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,10 @@ const PanelContent = memo(function PanelContent() {
162162
return 'processListWithCounts';
163163
}, [uiSelectedEvent, crumbEvent, crumbId, graphableProcessEntityIds]);
164164

165-
const terminatedProcesses = useSelector(selectors.terminatedProcesses);
166-
const processEntityId = uiSelectedEvent ? event.entityId(uiSelectedEvent) : undefined;
167-
const isProcessTerminated = processEntityId ? terminatedProcesses.has(processEntityId) : false;
168-
169165
const panelInstance = useMemo(() => {
170166
if (panelToShow === 'processDetails') {
171167
return (
172-
<ProcessDetails
173-
processEvent={uiSelectedEvent!}
174-
pushToQueryParams={pushToQueryParams}
175-
isProcessTerminated={isProcessTerminated}
176-
isProcessOrigin={false}
177-
/>
168+
<ProcessDetails processEvent={uiSelectedEvent!} pushToQueryParams={pushToQueryParams} />
178169
);
179170
}
180171

@@ -213,21 +204,14 @@ const PanelContent = memo(function PanelContent() {
213204
);
214205
}
215206
// The default 'Event List' / 'List of all processes' view
216-
return (
217-
<ProcessListWithCounts
218-
pushToQueryParams={pushToQueryParams}
219-
isProcessTerminated={isProcessTerminated}
220-
isProcessOrigin={false}
221-
/>
222-
);
207+
return <ProcessListWithCounts pushToQueryParams={pushToQueryParams} />;
223208
}, [
224209
uiSelectedEvent,
225210
crumbEvent,
226211
crumbId,
227212
pushToQueryParams,
228213
relatedStatsForIdFromParams,
229214
panelToShow,
230-
isProcessTerminated,
231215
]);
232216

233217
return <>{panelInstance}</>;

x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_detail.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66
import React, { memo, useMemo } from 'react';
7+
import { useSelector } from 'react-redux';
78
import { i18n } from '@kbn/i18n';
89
import {
910
htmlIdGenerator,
@@ -15,6 +16,7 @@ import {
1516
} from '@elastic/eui';
1617
import styled from 'styled-components';
1718
import { FormattedMessage } from 'react-intl';
19+
import * as selectors from '../../store/selectors';
1820
import * as event from '../../../../common/endpoint/models/event';
1921
import { CrumbInfo, formatDate, StyledBreadcrumbs } from './panel_content_utilities';
2022
import {
@@ -41,16 +43,14 @@ const StyledDescriptionList = styled(EuiDescriptionList)`
4143
*/
4244
export const ProcessDetails = memo(function ProcessDetails({
4345
processEvent,
44-
isProcessTerminated,
45-
isProcessOrigin,
4646
pushToQueryParams,
4747
}: {
4848
processEvent: ResolverEvent;
49-
isProcessTerminated: boolean;
50-
isProcessOrigin: boolean;
5149
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
5250
}) {
5351
const processName = event.eventName(processEvent);
52+
const entityId = event.entityId(processEvent);
53+
const isProcessTerminated = useSelector(selectors.isProcessTerminated)(entityId);
5454
const processInfoEntry = useMemo(() => {
5555
const eventTime = event.eventTimestamp(processEvent);
5656
const dateTime = eventTime ? formatDate(eventTime) : '';
@@ -151,8 +151,8 @@ export const ProcessDetails = memo(function ProcessDetails({
151151
if (!processEvent) {
152152
return { descriptionText: '' };
153153
}
154-
return cubeAssetsForNode(isProcessTerminated, isProcessOrigin);
155-
}, [processEvent, cubeAssetsForNode, isProcessTerminated, isProcessOrigin]);
154+
return cubeAssetsForNode(isProcessTerminated, false);
155+
}, [processEvent, cubeAssetsForNode, isProcessTerminated]);
156156

157157
const titleId = useMemo(() => htmlIdGenerator('resolverTable')(), []);
158158
return (
@@ -161,10 +161,7 @@ export const ProcessDetails = memo(function ProcessDetails({
161161
<EuiSpacer size="l" />
162162
<EuiTitle size="xs">
163163
<h4 aria-describedby={titleId}>
164-
<CubeForProcess
165-
isProcessTerminated={isProcessTerminated}
166-
isProcessOrigin={isProcessOrigin}
167-
/>
164+
<CubeForProcess isProcessTerminated={isProcessTerminated} />
168165
{processName}
169166
</h4>
170167
</EuiTitle>

x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_process_list.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,8 @@ const StyledLimitWarning = styled(LimitWarning)`
5050
*/
5151
export const ProcessListWithCounts = memo(function ProcessListWithCounts({
5252
pushToQueryParams,
53-
isProcessTerminated,
54-
isProcessOrigin,
5553
}: {
5654
pushToQueryParams: (queryStringKeyValuePair: CrumbInfo) => unknown;
57-
isProcessTerminated: boolean;
58-
isProcessOrigin: boolean;
5955
}) {
6056
interface ProcessTableView {
6157
name: string;
@@ -65,6 +61,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
6561

6662
const dispatch = useResolverDispatch();
6763
const { timestamp } = useContext(SideEffectContext);
64+
const isProcessTerminated = useSelector(selectors.isProcessTerminated);
6865
const handleBringIntoViewClick = useCallback(
6966
(processTableViewItem) => {
7067
dispatch({
@@ -92,6 +89,8 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
9289
sortable: true,
9390
truncateText: true,
9491
render(name: string, item: ProcessTableView) {
92+
const entityId = event.entityId(item.event);
93+
const isTerminated = isProcessTerminated(entityId);
9594
return name === '' ? (
9695
<EuiBadge color="warning">
9796
{i18n.translate(
@@ -108,10 +107,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
108107
pushToQueryParams({ crumbId: event.entityId(item.event), crumbEvent: '' });
109108
}}
110109
>
111-
<CubeForProcess
112-
isProcessTerminated={isProcessTerminated}
113-
isProcessOrigin={isProcessOrigin}
114-
/>
110+
<CubeForProcess isProcessTerminated={isTerminated} />
115111
{name}
116112
</EuiButtonEmpty>
117113
);
@@ -143,7 +139,7 @@ export const ProcessListWithCounts = memo(function ProcessListWithCounts({
143139
},
144140
},
145141
],
146-
[pushToQueryParams, handleBringIntoViewClick, isProcessOrigin, isProcessTerminated]
142+
[pushToQueryParams, handleBringIntoViewClick, isProcessTerminated]
147143
);
148144

149145
const { processNodePositions } = useSelector(selectors.layout);

x-pack/plugins/security_solution/public/resolver/view/panels/process_cube_icon.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ import { useResolverTheme } from '../assets';
1313
*/
1414
export const CubeForProcess = memo(function CubeForProcess({
1515
isProcessTerminated,
16-
isProcessOrigin,
1716
}: {
1817
isProcessTerminated: boolean;
19-
isProcessOrigin: boolean;
2018
}) {
2119
const { cubeAssetsForNode } = useResolverTheme();
22-
const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, isProcessOrigin);
20+
const { cubeSymbol, descriptionText } = cubeAssetsForNode(isProcessTerminated, false);
2321

2422
return (
2523
<>

0 commit comments

Comments
 (0)