Skip to content

Commit 060f089

Browse files
[Security Solution][Resolver] Show all event counts on nodes (#77819)
* [Security Solution][Resolver] Show all event counts Co-authored-by: Elastic Machine <[email protected]>
1 parent 07891be commit 060f089

File tree

4 files changed

+28
-223
lines changed

4 files changed

+28
-223
lines changed

x-pack/plugins/security_solution/public/resolver/view/clickthrough.test.tsx

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -276,48 +276,12 @@ describe('Resolver, when analyzing a tree that has two related events for the or
276276
);
277277
expect(edgesThatTerminateUnderneathSecondChild).toHaveLength(1);
278278
});
279-
280-
it('should render a related events button', async () => {
279+
it('should show exactly one option with the correct count', async () => {
281280
await expect(
282-
simulator.map(() => ({
283-
relatedEventButtons: simulator.processNodeSubmenuButton(entityIDs.origin).length,
284-
}))
285-
).toYieldEqualTo({
286-
relatedEventButtons: 1,
287-
});
288-
});
289-
describe('when the related events button is clicked', () => {
290-
beforeEach(async () => {
291-
const button = await simulator.resolveWrapper(() =>
292-
simulator.processNodeSubmenuButton(entityIDs.origin)
293-
);
294-
if (button) {
295-
button.simulate('click', { button: 0 });
296-
}
297-
});
298-
it('should open the submenu and display exactly one option with the correct count', async () => {
299-
await expect(
300-
simulator.map(() =>
301-
simulator.testSubject('resolver:map:node-submenu-item').map((node) => node.text())
302-
)
303-
).toYieldEqualTo(['2 registry']);
304-
});
305-
});
306-
describe('and when the related events button is clicked again', () => {
307-
beforeEach(async () => {
308-
const button = await simulator.resolveWrapper(() =>
309-
simulator.processNodeSubmenuButton(entityIDs.origin)
310-
);
311-
if (button) {
312-
button.simulate('click', { button: 0 });
313-
button.simulate('click', { button: 0 }); // The first click opened the menu, this second click closes it
314-
}
315-
});
316-
it('should close the submenu', async () => {
317-
await expect(
318-
simulator.map(() => simulator.testSubject('resolver:map:node-submenu-item').length)
319-
).toYieldEqualTo(0);
320-
});
281+
simulator.map(() =>
282+
simulator.testSubject('resolver:map:node-submenu-item').map((node) => node.text())
283+
)
284+
).toYieldEqualTo(['2 registry']);
321285
});
322286
});
323287
});

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,6 @@ const UnstyledProcessEventDot = React.memo(
257257
});
258258
}, [dispatch, nodeID]);
259259

260-
const handleRelatedEventRequest = useCallback(() => {
261-
dispatch({
262-
type: 'userRequestedRelatedEventData',
263-
payload: nodeID,
264-
});
265-
}, [dispatch, nodeID]);
266-
267260
const handleClick = useCallback(
268261
(clickEvent) => {
269262
if (animationTarget.current?.beginElement) {
@@ -439,11 +432,7 @@ const UnstyledProcessEventDot = React.memo(
439432
<EuiFlexItem grow={false} className="related-dropdown">
440433
{grandTotal !== null && grandTotal > 0 && (
441434
<NodeSubMenu
442-
count={grandTotal}
443-
buttonBorderColor={labelButtonFill}
444435
buttonFill={colorMap.resolverBackground}
445-
menuAction={handleRelatedEventRequest}
446-
projectionMatrix={projectionMatrix}
447436
relatedEventStats={relatedEventStats}
448437
nodeID={nodeID}
449438
/>

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

Lines changed: 23 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
/* eslint-disable react/display-name */
8-
97
import { i18n } from '@kbn/i18n';
10-
import React, { useState, useCallback, useRef, useLayoutEffect, useMemo } from 'react';
11-
import { EuiI18nNumber, EuiButton, EuiPopover, ButtonColor } from '@elastic/eui';
8+
import React, { useMemo } from 'react';
9+
import { EuiI18nNumber } from '@elastic/eui';
1210
import styled from 'styled-components';
1311
import { ResolverNodeStats } from '../../../common/endpoint/types';
1412
import { useRelatedEventByCategoryNavigation } from './use_related_event_by_category_navigation';
15-
import { Matrix3 } from '../types';
1613
import { useColors } from './use_colors';
1714

1815
/**
@@ -45,107 +42,27 @@ interface ResolverSubmenuOption {
4542

4643
export type ResolverSubmenuOptionList = ResolverSubmenuOption[] | string;
4744

48-
const StyledActionButton = styled(EuiButton)`
49-
&.euiButton--small {
50-
height: fit-content;
51-
line-height: 1;
52-
padding: 0.25em;
53-
font-size: 0.85rem;
54-
}
55-
`;
56-
57-
/**
58-
* This will be the "host button" that displays the "total number of related events" and opens
59-
* the sumbmenu (with counts by category) when clicked.
60-
*/
61-
const SubButton = React.memo(
62-
({
63-
hasMenu,
64-
menuIsOpen,
65-
action,
66-
count,
67-
nodeID,
68-
}: {
69-
hasMenu: boolean;
70-
menuIsOpen?: boolean;
71-
action: (evt: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
72-
count?: number;
73-
nodeID: string;
74-
}) => {
75-
const iconType = menuIsOpen === true ? 'arrowUp' : 'arrowDown';
76-
return (
77-
<StyledActionButton
78-
onClick={action}
79-
iconType={hasMenu ? iconType : 'none'}
80-
fill={false}
81-
color={'primary'}
82-
size="s"
83-
iconSide="right"
84-
tabIndex={-1}
85-
data-test-subj="resolver:submenu:button"
86-
data-test-resolver-node-id={nodeID}
87-
id={nodeID}
88-
>
89-
{count ? <EuiI18nNumber value={count} /> : ''} {subMenuAssets.relatedEvents.title}
90-
</StyledActionButton>
91-
);
92-
}
93-
);
94-
9545
/**
9646
* A Submenu to be displayed in one of two forms:
9747
* 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.
9848
* 2) Provided `optionsWithActions` is undefined, it will call the supplied `menuAction` when its host button is clicked.
9949
*/
10050
const NodeSubMenuComponents = React.memo(
10151
({
102-
count,
103-
buttonBorderColor,
104-
menuAction,
10552
className,
106-
projectionMatrix,
10753
nodeID,
10854
relatedEventStats,
10955
}: {
11056
className?: string;
111-
menuAction?: () => unknown;
112-
buttonBorderColor: ButtonColor;
11357
// eslint-disable-next-line react/no-unused-prop-types
11458
buttonFill: string;
115-
count?: number;
11659
/**
11760
* Receive the projection matrix, so we can see when the camera position changed, so we can force the submenu to reposition itself.
11861
*/
119-
projectionMatrix: Matrix3;
12062
nodeID: string;
12163
relatedEventStats: ResolverNodeStats | undefined;
12264
}) => {
123-
// keep a ref to the popover so we can call its reposition method
124-
const popoverRef = useRef<EuiPopover>(null);
125-
126-
const [menuIsOpen, setMenuOpen] = useState(false);
127-
const handleMenuOpenClick = useCallback(
128-
(clickEvent: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
129-
// stopping propagation/default to prevent other node animations from triggering
130-
clickEvent.preventDefault();
131-
clickEvent.stopPropagation();
132-
setMenuOpen(!menuIsOpen);
133-
},
134-
[menuIsOpen]
135-
);
136-
const handleMenuActionClick = useCallback(
137-
(clickEvent: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
138-
// stopping propagation/default to prevent other node animations from triggering
139-
clickEvent.preventDefault();
140-
clickEvent.stopPropagation();
141-
if (typeof menuAction === 'function') menuAction();
142-
setMenuOpen(true);
143-
},
144-
[menuAction]
145-
);
146-
14765
// The last projection matrix that was used to position the popover
148-
const projectionMatrixAtLastRender = useRef<Matrix3>();
14966
const relatedEventCallbacks = useRelatedEventByCategoryNavigation({
15067
nodeID,
15168
categories: relatedEventStats?.events?.byCategory,
@@ -164,90 +81,39 @@ const NodeSubMenuComponents = React.memo(
16481
}
16582
}, [relatedEventStats, relatedEventCallbacks]);
16683

167-
useLayoutEffect(() => {
168-
if (
169-
/**
170-
* If there is a popover component reference,
171-
* and this isn't the first render,
172-
* and the projectionMatrix has changed since last render,
173-
* then force the popover to reposition itself.
174-
*/
175-
popoverRef.current &&
176-
projectionMatrixAtLastRender.current &&
177-
projectionMatrixAtLastRender.current !== projectionMatrix
178-
) {
179-
popoverRef.current.positionPopoverFixed();
180-
}
181-
182-
// no matter what, keep track of the last project matrix that was used to size the popover
183-
projectionMatrixAtLastRender.current = projectionMatrix;
184-
}, [projectionMatrixAtLastRender, projectionMatrix]);
18584
const { pillStroke: pillBorderStroke, resolverBackground: pillFill } = useColors();
18685
const listStylesFromTheme = useMemo(() => {
18786
return {
18887
border: `1.5px solid ${pillBorderStroke}`,
18988
backgroundColor: pillFill,
19089
};
19190
}, [pillBorderStroke, pillFill]);
192-
if (relatedEventStats === undefined) {
193-
/**
194-
* When called with a `menuAction`
195-
* Render without dropdown and call the supplied action when host button is clicked
196-
*/
197-
return (
198-
<div className={className}>
199-
<EuiButton
200-
onClick={handleMenuActionClick}
201-
color={buttonBorderColor}
202-
size="s"
203-
tabIndex={-1}
204-
>
205-
{subMenuAssets.relatedEvents.title}
206-
</EuiButton>
207-
</div>
208-
);
209-
}
21091

21192
if (relatedEventOptions === undefined) {
21293
return null;
21394
}
21495

21596
return (
216-
<>
217-
<SubButton
218-
hasMenu={true}
219-
menuIsOpen={menuIsOpen}
220-
action={handleMenuOpenClick}
221-
count={count}
222-
nodeID={nodeID}
223-
/>
224-
{menuIsOpen ? (
225-
<ul
226-
className={`${className} options`}
227-
aria-hidden={!menuIsOpen}
228-
aria-describedby={nodeID}
229-
>
230-
{relatedEventOptions
231-
.sort((opta, optb) => {
232-
return opta.optionTitle.localeCompare(optb.optionTitle);
233-
})
234-
.map((opt) => {
235-
return (
236-
<li
237-
className="item"
238-
data-test-subj="resolver:map:node-submenu-item"
239-
style={listStylesFromTheme}
240-
key={opt.optionTitle}
241-
>
242-
<button type="button" className="kbn-resetFocusState" onClick={opt.action}>
243-
{opt.prefix} {opt.optionTitle}
244-
</button>
245-
</li>
246-
);
247-
})}
248-
</ul>
249-
) : null}
250-
</>
97+
<ul className={`${className} options`} aria-describedby={nodeID}>
98+
{relatedEventOptions
99+
.sort((opta, optb) => {
100+
return opta.optionTitle.localeCompare(optb.optionTitle);
101+
})
102+
.map((opt) => {
103+
return (
104+
<li
105+
className="item"
106+
data-test-subj="resolver:map:node-submenu-item"
107+
style={listStylesFromTheme}
108+
key={opt.optionTitle}
109+
>
110+
<button type="button" className="kbn-resetFocusState" onClick={opt.action}>
111+
{opt.prefix} {opt.optionTitle}
112+
</button>
113+
</li>
114+
);
115+
})}
116+
</ul>
251117
);
252118
}
253119
);
@@ -265,7 +131,7 @@ export const NodeSubMenu = styled(NodeSubMenuComponents)`
265131
flex-flow: row wrap;
266132
background: transparent;
267133
position: absolute;
268-
top: 6.5em;
134+
top: 4.5em;
269135
contain: content;
270136
width: 12em;
271137
z-index: 2;
@@ -300,17 +166,4 @@ export const NodeSubMenu = styled(NodeSubMenuComponents)`
300166
&.options .item button:active {
301167
transform: scale(0.95);
302168
}
303-
304-
& .euiButton {
305-
background-color: ${(props) => props.buttonFill};
306-
border-color: ${(props) => props.buttonBorderColor};
307-
border-style: solid;
308-
border-width: 1px;
309-
310-
&:hover,
311-
&:active,
312-
&:focus {
313-
background-color: ${(props) => props.buttonFill};
314-
}
315-
}
316169
`;

x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
1313
const esArchiver = getService('esArchiver');
1414
const queryBar = getService('queryBar');
1515

16-
// FLAKY: https://github.com/elastic/kibana/issues/77835
1716
describe('Endpoint Event Resolver', function () {
1817
before(async () => {
1918
await esArchiver.load('endpoint/resolver_tree', { useCreate: true });

0 commit comments

Comments
 (0)