Skip to content

Commit

Permalink
[8.x] [Security Solution][Notes] - update cases alerts table action c…
Browse files Browse the repository at this point in the history
…olumn width to show analyzer and session view icons (#195693) (#195981)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Security Solution][Notes] - update cases alerts table action column
width to show analyzer and session view icons
(#195693)](#195693)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Philippe
Oberti","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-10-11T19:05:43Z","message":"[Security
Solution][Notes] - update cases alerts table action column width to show
analyzer and session view icons
(#195693)","sha":"ab248df4f8752c066f99acd2150578eab370211d","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["backport","release_note:skip","v9.0.0","Team:Threat
Hunting:Investigations","v8.16.0"],"title":"[Security Solution][Notes] -
update cases alerts table action column width to show analyzer and
session view
icons","number":195693,"url":"https://github.com/elastic/kibana/pull/195693","mergeCommit":{"message":"[Security
Solution][Notes] - update cases alerts table action column width to show
analyzer and session view icons
(#195693)","sha":"ab248df4f8752c066f99acd2150578eab370211d"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195693","number":195693,"mergeCommit":{"message":"[Security
Solution][Notes] - update cases alerts table action column width to show
analyzer and session view icons
(#195693)","sha":"ab248df4f8752c066f99acd2150578eab370211d"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Philippe Oberti <[email protected]>
  • Loading branch information
kibanamachine and PhilippeOberti authored Oct 11, 2024
1 parent ce62f5b commit d4c0886
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jest.mock('../guided_onboarding_tour/use_hidden_by_flyout', () => ({
}));
jest.mock('../guided_onboarding_tour');
jest.mock('../user_privileges');
jest.mock('../user_privileges');
jest.mock('../../../detections/components/user_info', () => ({
useUserData: jest.fn().mockReturnValue([{ canUserCRUD: true, hasIndexWrite: true }]),
}));
Expand All @@ -45,6 +46,15 @@ jest.mock(
})
);

const mockUseUiSetting = jest.fn().mockReturnValue([true]);
jest.mock('@kbn/kibana-react-plugin/public', () => {
const original = jest.requireActual('@kbn/kibana-react-plugin/public');
return {
...original,
useUiSetting$: () => mockUseUiSetting(),
};
});

jest.mock('./add_note_icon_item', () => {
return {
AddEventNoteAction: jest.fn(() => <div data-test-subj="add-note-mock-action" />),
Expand Down Expand Up @@ -249,6 +259,7 @@ describe('Actions', () => {
expect(wrapper.find(GuidedOnboardingTourStep).exists()).toEqual(true);
expect(wrapper.find(SecurityTourStep).exists()).toEqual(false);
});

describe.each(Object.keys(isTourAnchorConditions))('tour condition true: %s', (key: string) => {
it('Single condition does not make tour step exist', () => {
const wrapper = mount(
Expand Down Expand Up @@ -281,6 +292,7 @@ describe('Actions', () => {
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
).toBe(true);
});

test('it enables for eventType=signal', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand All @@ -296,6 +308,7 @@ describe('Actions', () => {
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
).toBe(false);
});

test('it disables for event.kind: undefined and agent.type: endpoint', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand All @@ -311,6 +324,7 @@ describe('Actions', () => {
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
).toBe(true);
});

test('it enables for event.kind: event and agent.type: endpoint', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand All @@ -327,6 +341,7 @@ describe('Actions', () => {
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
).toBe(false);
});

test('it disables for event.kind: alert and agent.type: endpoint', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand All @@ -343,6 +358,7 @@ describe('Actions', () => {
wrapper.find('[data-test-subj="timeline-context-menu-button"]').first().prop('isDisabled')
).toBe(true);
});

test('it shows the analyze event button when the event is from an endpoint', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand All @@ -358,6 +374,7 @@ describe('Actions', () => {

expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(true);
});

test('it does not render the analyze event button when the event is from an unsupported source', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand All @@ -373,6 +390,60 @@ describe('Actions', () => {
expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(false);
});

test('it does not render the analyze event button on the cases alerts table with advanced settings disabled', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entity_id: ['1'] },
};
mockUseUiSetting.mockReturnValue([false]);

const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} timelineId={TableId.alertsOnCasePage} />
</TestProviders>
);

expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(false);
});

test('it does render the analyze event button on the cases alerts table with advanced settings enabled', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entity_id: ['1'] },
};
mockUseUiSetting.mockReturnValue([true]);

const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} timelineId={TableId.alertsOnCasePage} />
</TestProviders>
);

expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(true);
});

test('it does render the analyze event button on the alerts page alerts table even with advanced settings disabled', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entity_id: ['1'] },
};
mockUseUiSetting.mockReturnValue([false]);

const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} timelineId={TableId.alertsOnAlertsPage} />
</TestProviders>
);

expect(wrapper.find('[data-test-subj="view-in-analyzer"]').exists()).toBe(true);
});

test('it should not show session view button on action tabs for basic users', () => {
const ecsData = {
...mockTimelineData[0].ecs,
Expand Down Expand Up @@ -434,6 +505,44 @@ describe('Actions', () => {

expect(wrapper.find('[data-test-subj="session-view-button"]').exists()).toEqual(true);
});

test('it does not render the session view button on the cases alerts table with advanced settings disabled', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entry_leader: { entity_id: ['test_id'], start: ['2022-05-08T13:44:00.13Z'] } },
_index: '.ds-logs-endpoint.events.process-default',
};
mockUseUiSetting.mockReturnValue([false]);

const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} timelineId={TableId.alertsOnCasePage} />
</TestProviders>
);

expect(wrapper.find('[data-test-subj="session-view-button"]').exists()).toBe(false);
});

test('it does render the session view button on the cases alerts table with advanced settings enabled', () => {
const ecsData = {
...mockTimelineData[0].ecs,
event: { kind: ['alert'] },
agent: { type: ['endpoint'] },
process: { entry_leader: { entity_id: ['test_id'], start: ['2022-05-08T13:44:00.13Z'] } },
_index: '.ds-logs-endpoint.events.process-default',
};
mockUseUiSetting.mockReturnValue([true]);

const wrapper = mount(
<TestProviders>
<Actions {...defaultProps} ecsData={ecsData} timelineId={TableId.alertsOnCasePage} />
</TestProviders>
);

expect(wrapper.find('[data-test-subj="session-view-button"]').exists()).toBe(true);
});
});

describe('Show notes action', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ const ActionsComponent: React.FC<ActionProps> = ({

const { startTransaction } = useStartTransaction();

const isEnterprisePlus = useLicense().isEnterprise();

const onPinEvent: OnPinEvent = useCallback(
(evtId) => dispatch(timelineActions.pinEvent({ id: timelineId, eventId: evtId })),
[dispatch, timelineId]
Expand Down Expand Up @@ -132,7 +130,6 @@ const ActionsComponent: React.FC<ActionProps> = ({
scopeId: timelineId,
});

const isDisabled = !useIsInvestigateInResolverActionEnabled(ecsData);
const { setGlobalFullScreen } = useGlobalFullScreen();
const { setTimelineFullScreen } = useTimelineFullScreen();
const handleClick = useCallback(() => {
Expand Down Expand Up @@ -292,6 +289,30 @@ const ActionsComponent: React.FC<ActionProps> = ({
[documentBasedNotes, timelineNoteIds, securitySolutionNotesEnabled]
);

// we hide the analyzer icon if the data is not available for the resolver
// or if we are on the cases alerts table and the the visualization in flyout advanced setting is disabled
const ecsHasDataForAnalyzer = useIsInvestigateInResolverActionEnabled(ecsData);
const showAnalyzerIcon = useMemo(() => {
return (
ecsHasDataForAnalyzer &&
(timelineId !== TableId.alertsOnCasePage ||
(timelineId === TableId.alertsOnCasePage && visualizationInFlyoutEnabled))
);
}, [ecsHasDataForAnalyzer, timelineId, visualizationInFlyoutEnabled]);

// we hide the session view icon if the session view is not available
// or if we are on the cases alerts table and the the visualization in flyout advanced setting is disabled
// or if the user is not on an enterprise license or on the kubernetes page
const isEnterprisePlus = useLicense().isEnterprise();
const showSessionViewIcon = useMemo(() => {
return (
sessionViewConfig !== null &&
(isEnterprisePlus || timelineId === TableId.kubernetesPageSessions) &&
(timelineId !== TableId.alertsOnCasePage ||
(timelineId === TableId.alertsOnCasePage && visualizationInFlyoutEnabled))
);
}, [sessionViewConfig, isEnterprisePlus, timelineId, visualizationInFlyoutEnabled]);

return (
<ActionsContainer data-test-subj="actions-container">
<>
Expand Down Expand Up @@ -359,7 +380,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
onRuleChange={onRuleChange}
refetch={refetch}
/>
{isDisabled === false ? (
{showAnalyzerIcon ? (
<div>
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
<EuiToolTip
Expand All @@ -380,8 +401,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
</EventsTdContent>
</div>
) : null}
{sessionViewConfig !== null &&
(isEnterprisePlus || timelineId === TableId.kubernetesPageSessions) ? (
{showSessionViewIcon ? (
<div>
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
<EuiToolTip data-test-subj="expand-event-tool-tip" content={i18n.OPEN_SESSION_VIEW}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import type {
RenderCustomActionsRowArgs,
} from '@kbn/triggers-actions-ui-plugin/public/types';
import { TableId } from '@kbn/securitysolution-data-table';
import { useUiSetting$ } from '@kbn/kibana-react-plugin/public';
import { ENABLE_VISUALIZATIONS_IN_FLYOUT_SETTING } from '../../../../common/constants';
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
import { StatefulEventContext } from '../../../common/components/events_viewer/stateful_event_context';
import { eventsViewerSelector } from '../../../common/components/events_viewer/selectors';
Expand All @@ -23,22 +25,48 @@ import { getAlertsDefaultModel } from '../../components/alerts_table/default_con
import type { State } from '../../../common/store';
import { RowAction } from '../../../common/components/control_columns/row_action';

// we show a maximum of 6 action buttons
// - open flyout
// - investigate in timeline
// - 3-dot menu for more actions
// - add new note
// - session view
// - analyzer graph
const MAX_ACTION_BUTTON_COUNT = 6;

export const getUseActionColumnHook =
(tableId: TableId): AlertsTableConfigurationRegistry['useActionsColumn'] =>
() => {
let ACTION_BUTTON_COUNT = MAX_ACTION_BUTTON_COUNT;

// hiding the session view icon for users without enterprise plus license
const license = useLicense();
const isEnterprisePlus = license.isEnterprise();
let ACTION_BUTTON_COUNT = tableId === TableId.alertsOnCasePage ? 4 : isEnterprisePlus ? 6 : 5;
if (!isEnterprisePlus) {
ACTION_BUTTON_COUNT--;
}

// we only want to show the note icon if the expandable flyout and the new notes system are enabled
// TODO delete most likely in 8.16
// we only want to show the note icon if the new notes system feature flag is enabled
const securitySolutionNotesEnabled = useIsExperimentalFeatureEnabled(
'securitySolutionNotesEnabled'
);
if (!securitySolutionNotesEnabled) {
ACTION_BUTTON_COUNT--;
}

// we do not show the analyzer graph and session view icons on the cases alerts tab alerts table
// if the visualization in flyout advanced settings is disabled because these aren't supported inside the table
if (tableId === TableId.alertsOnCasePage) {
const [visualizationInFlyoutEnabled] = useUiSetting$<boolean>(
ENABLE_VISUALIZATIONS_IN_FLYOUT_SETTING
);
if (!isEnterprisePlus && !visualizationInFlyoutEnabled) {
ACTION_BUTTON_COUNT -= 1;
} else if (isEnterprisePlus && !visualizationInFlyoutEnabled) {
ACTION_BUTTON_COUNT -= 2;
}
}

const eventContext = useContext(StatefulEventContext);

const leadingControlColumn = useMemo(
Expand Down

0 comments on commit d4c0886

Please sign in to comment.