Skip to content

Commit

Permalink
[Security Solution][Endpoint] Hide agent types on Types filter when…
Browse files Browse the repository at this point in the history
… on a flyout and other UI changes (elastic#176280)

## Summary

For `Types` filter on a flyout/responder (when a single agent is
selected), that shows agent type and action type filter options, it
doesn't make sense to allow selecting agent types as there can only be
one agent type. This PR fixes that bug by:

1. Not showing the agent types filter options


Additionally the PR updates 
1. Agent type name to **Elastic Defend** _instead of Endpoint_.
2. Adds `Agent type` value to expanded output tray.
3. Uses the correct field for getting host names for sentinel one hosts
from sentinel one alerts
4. Correctly calculates the available filter options in Types filter.


### Team consensus
- [x] remove agent types from flyout types filter

### clip/screenshot (Types filter)
#### flyout/responder view
![Screenshot 2024-02-06 at 5 04
38 PM](https://github.com/elastic/kibana/assets/1849116/e0540bcc-cf51-4983-97df-de1561c23930)

#### history page view
![Screenshot 2024-02-06 at 5 05
01 PM](https://github.com/elastic/kibana/assets/1849116/27546ece-2327-4a9f-82f6-97f83c5826b9)

#### Output sections 
![Screenshot 2024-02-06 at 4 40
53 PM](https://github.com/elastic/kibana/assets/1849116/c31ff450-a626-4652-9298-d777af11f057)
![Screenshot 2024-02-06 at 4 41
01 PM](https://github.com/elastic/kibana/assets/1849116/5ba77d8b-d898-4921-8dff-00faf85b5d3f)

### Updated Responder header info for Sentinel One host
![Screenshot 2024-02-06 at 5 07
50 PM](https://github.com/elastic/kibana/assets/1849116/97ea17ba-2f4c-4850-bcac-20c822465b96)


### Checklist
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [x] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
  • Loading branch information
ashokaditya authored and fkanout committed Mar 4, 2024
1 parent 93503ff commit 2c4178d
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const UNSAVED_TIMELINE_SAVE_PROMPT_TITLE = i18n.translate(
export const getAgentTypeName = (agentType: ResponseActionAgentType) => {
switch (agentType) {
case 'endpoint':
return 'Endpoint';
return 'Elastic Defend';
case 'sentinel_one':
return 'SentinelOne';
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const getThirdPartyAgentInfo = (
) as ResponseActionAgentType,
},
host: {
name: getFieldValue({ category: 'host', field: 'host.os.name' }, eventData),
name: getFieldValue({ category: 'host', field: 'host.name' }, eventData),
os: {
name: getFieldValue({ category: 'host', field: 'host.os.name' }, eventData),
family: getFieldValue({ category: 'host', field: 'host.os.family' }, eventData),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import React, { memo, useMemo } from 'react';
import { EuiCodeBlock, EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { css, euiStyled } from '@kbn/kibana-react-plugin/common';
import { map } from 'lodash';
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
import { getAgentTypeName } from '../../../../common/translations';
import { RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP } from '../../../../../common/endpoint/service/response_actions/constants';
import {
isExecuteAction,
Expand Down Expand Up @@ -178,7 +180,19 @@ export const ActionsLogExpandedTray = memo<{
}>(({ action, 'data-test-subj': dataTestSubj }) => {
const getTestId = useTestIdGenerator(dataTestSubj);

const { hosts, startedAt, completedAt, command: _command, comment, parameters } = action;
const isSentinelOneV1Enabled = useIsExperimentalFeatureEnabled(
'responseActionsSentinelOneV1Enabled'
);

const {
hosts,
startedAt,
completedAt,
command: _command,
comment,
parameters,
agentType,
} = action;

const parametersList = useMemo(
() =>
Expand All @@ -192,45 +206,61 @@ export const ActionsLogExpandedTray = memo<{

const command = RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP[_command];

const dataList = useMemo(
() =>
[
{
title: OUTPUT_MESSAGES.expandSection.placedAt,
description: `${startedAt}`,
},
{
title: OUTPUT_MESSAGES.expandSection.startedAt,
description: `${startedAt}`,
},
{
title: OUTPUT_MESSAGES.expandSection.completedAt,
description: `${completedAt ?? emptyValue}`,
},
{
title: OUTPUT_MESSAGES.expandSection.input,
description: `${command}`,
},
{
title: OUTPUT_MESSAGES.expandSection.parameters,
description: parametersList ? parametersList.join(', ') : emptyValue,
},
{
title: OUTPUT_MESSAGES.expandSection.comment,
description: comment ? comment : emptyValue,
},
{
title: OUTPUT_MESSAGES.expandSection.hostname,
description: map(hosts, (host) => host.name).join(', ') || emptyValue,
},
].map(({ title, description }) => {
return {
title: <StyledEuiCodeBlock>{title}</StyledEuiCodeBlock>,
description: <StyledEuiCodeBlock>{description}</StyledEuiCodeBlock>,
};
}),
[command, comment, completedAt, hosts, parametersList, startedAt]
);
const dataList = useMemo(() => {
const list = [
{
title: OUTPUT_MESSAGES.expandSection.placedAt,
description: `${startedAt}`,
},
{
title: OUTPUT_MESSAGES.expandSection.startedAt,
description: `${startedAt}`,
},
{
title: OUTPUT_MESSAGES.expandSection.completedAt,
description: `${completedAt ?? emptyValue}`,
},
{
title: OUTPUT_MESSAGES.expandSection.input,
description: `${command}`,
},
{
title: OUTPUT_MESSAGES.expandSection.parameters,
description: parametersList ? parametersList.join(', ') : emptyValue,
},
{
title: OUTPUT_MESSAGES.expandSection.comment,
description: comment ? comment : emptyValue,
},
{
title: OUTPUT_MESSAGES.expandSection.hostname,
description: map(hosts, (host) => host.name).join(', ') || emptyValue,
},
];

if (isSentinelOneV1Enabled) {
list.push({
title: OUTPUT_MESSAGES.expandSection.agentType,
description: getAgentTypeName(agentType) || emptyValue,
});
}

return list.map(({ title, description }) => {
return {
title: <StyledEuiCodeBlock>{title}</StyledEuiCodeBlock>,
description: <StyledEuiCodeBlock>{description}</StyledEuiCodeBlock>,
};
});
}, [
agentType,
command,
comment,
completedAt,
hosts,
isSentinelOneV1Enabled,
parametersList,
startedAt,
]);

const outputList = useMemo(
() => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,28 @@ const getTypesFilterInitialState = (
// v8.13 onwards
// for showing agent types and action types in the same filter
if (isSentinelOneV1Enabled) {
if (!isFlyout) {
return [
{
label: FILTER_NAMES.agentTypes,
isGroupLabel: true,
},
...RESPONSE_ACTION_AGENT_TYPE.map((type) =>
getFilterOptions({
key: type,
label: getAgentTypeName(type),
checked: !isFlyout && agentTypes?.includes(type) ? 'on' : undefined,
})
),
{
label: FILTER_NAMES.actionTypes,
isGroupLabel: true,
},
...defaultFilterOptions,
];
}

return [
{
label: FILTER_NAMES.agentTypes,
isGroupLabel: true,
},
...RESPONSE_ACTION_AGENT_TYPE.map((type) =>
getFilterOptions({
key: type,
label: getAgentTypeName(type),
checked: !isFlyout && agentTypes?.includes(type) ? 'on' : undefined,
})
),
{
label: FILTER_NAMES.actionTypes,
isGroupLabel: true,
Expand Down Expand Up @@ -336,7 +346,10 @@ export const useActionsLogFilter = ({
() => items.filter((item) => item.checked === 'on').length,
[items]
);
const numFilters = useMemo(() => items.filter((item) => item.checked !== 'on').length, [items]);
const numFilters = useMemo(
() => items.filter((item) => item.key && item.checked !== 'on').length,
[items]
);

return {
areHostsSelectedOnMount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import { getActionListMock } from '../mocks';
import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_list';
import { v4 as uuidv4 } from 'uuid';
import {
RESPONSE_ACTION_AGENT_TYPE,
RESPONSE_ACTION_API_COMMAND_TO_CONSOLE_COMMAND_MAP,
RESPONSE_ACTION_API_COMMANDS_NAMES,
RESPONSE_ACTION_TYPE,
} from '../../../../../common/endpoint/service/response_actions/constants';
import { useUserPrivileges as _useUserPrivileges } from '../../../../common/components/user_privileges';
import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks';
Expand Down Expand Up @@ -571,6 +573,30 @@ describe('Response actions history', () => {
);
});

it('should contain agent type info in each expanded row', async () => {
mockedContext.setExperimentalFlag({ responseActionsSentinelOneV1Enabled: true });
render();
const { getAllByTestId } = renderResult;

const expandButtons = getAllByTestId(`${testPrefix}-expand-button`);
expandButtons.map((button) => userEvent.click(button));
const trays = getAllByTestId(`${testPrefix}-details-tray`);
expect(trays).toBeTruthy();
expect(Array.from(trays[0].querySelectorAll('dt')).map((title) => title.textContent)).toEqual(
[
'Command placed',
'Execution started on',
'Execution completed',
'Input',
'Parameters',
'Comment',
'Hostname',
'Agent type',
'Output:',
]
);
});

it('should refresh data when autoRefresh is toggled on', async () => {
const listHookResponse = getBaseMockedActionList();
useGetEndpointActionListMock.mockReturnValue(listHookResponse);
Expand Down Expand Up @@ -1380,4 +1406,49 @@ describe('Response actions history', () => {
);
});
});

describe('Types filter', () => {
const filterPrefix = 'types-filter';
it('should show a list of action types when opened', () => {
render();
const { getByTestId, getAllByTestId } = renderResult;

userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`));
const filterList = getByTestId(`${testPrefix}-${filterPrefix}-popoverList`);
expect(filterList).toBeTruthy();
expect(getAllByTestId(`${filterPrefix}-option`).length).toEqual(RESPONSE_ACTION_TYPE.length);
expect(getAllByTestId(`${filterPrefix}-option`).map((option) => option.textContent)).toEqual([
'Triggered by rule',
'Triggered manually',
]);
});

it('should show a list of agent and action types when opened in page view', () => {
mockedContext.setExperimentalFlag({ responseActionsSentinelOneV1Enabled: true });
render({ isFlyout: false });
const { getByTestId, getAllByTestId } = renderResult;

userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`));
const filterList = getByTestId(`${testPrefix}-${filterPrefix}-popoverList`);
expect(filterList).toBeTruthy();
expect(getAllByTestId(`${filterPrefix}-option`).length).toEqual(
[...RESPONSE_ACTION_AGENT_TYPE, ...RESPONSE_ACTION_TYPE].length
);
expect(getAllByTestId(`${filterPrefix}-option`).map((option) => option.textContent)).toEqual([
'Elastic Defend',
'SentinelOne',
'Triggered by rule',
'Triggered manually',
]);
});

it('should have `clear all` button `disabled` when no selected values', () => {
render();
const { getByTestId } = renderResult;

userEvent.click(getByTestId(`${testPrefix}-${filterPrefix}-popoverButton`));
const clearAllButton = getByTestId(`${testPrefix}-${filterPrefix}-clearAllButton`);
expect(clearAllButton.hasAttribute('disabled')).toBeTruthy();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export const OUTPUT_MESSAGES = Object.freeze({
defaultMessage: 'Hostname',
}
),
agentType: i18n.translate(
'xpack.securitySolution.responseActionsList.list.item.expandSection.agentType',
{
defaultMessage: 'Agent type',
}
),
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ describe('Response actions history page', () => {
}, []);

expect(selectedFilterOptions.length).toEqual(1);
expect(selectedFilterOptions).toEqual(['Endpoint. Checked option.']);
expect(selectedFilterOptions).toEqual(['Elastic Defend. Checked option.']);
expect(history.location.search).toEqual('?agentTypes=endpoint');
});
});
Expand Down

0 comments on commit 2c4178d

Please sign in to comment.