Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] [Security Solution][Notes] - update cases alerts table action column width to show analyzer and session view icons (#195693) #195981

Merged
merged 1 commit into from
Oct 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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