Skip to content

Commit

Permalink
[Security Solution] Enable actions in document details preview footer (
Browse files Browse the repository at this point in the history
…#203691)

## Summary

Updated document details preview footer to also show actions
### Before
<img width="1481" alt="image"
src="https://github.com/user-attachments/assets/aace12aa-f3d1-4fe2-a3b7-392878207f39"
/>


### After 
- Users can perform alert/event actions in a preview

![image](https://github.com/user-attachments/assets/d42be26d-9d4a-4701-bc88-92549ebfb65c)

- In analyzer, when examining an event, event actions are also available

![image](https://github.com/user-attachments/assets/d30515d9-b428-4112-86f8-9bb872eaf921)

- No change to flyout in rule creation workflow, action is not available
in preview nor non-preview



### 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
  • Loading branch information
christineweng authored Dec 16, 2024
1 parent 7fd9d7c commit 7aac71c
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,58 @@ import { mockContextValue } from '../shared/mocks/mock_context';
import { DocumentDetailsContext } from '../shared/context';
import { PreviewPanelFooter } from './footer';
import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids';
import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from '../shared/components/test_ids';
import { createTelemetryServiceMock } from '../../../common/lib/telemetry/telemetry_service.mock';
import { useKibana } from '../../../common/lib/kibana';
import { useAlertExceptionActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions';
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
import { useAddToCaseActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions';

jest.mock('@kbn/expandable-flyout');

const mockedTelemetry = createTelemetryServiceMock();
jest.mock('../../../common/lib/kibana', () => {
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
return {
useKibana: () => ({
services: {
telemetry: mockedTelemetry,
},
}),
...original,
useLocation: jest.fn().mockReturnValue({ search: '' }),
};
});

jest.mock('../../../common/lib/kibana');
jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions');
jest.mock(
'../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'
);
jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions');

const mockedTelemetry = createTelemetryServiceMock();

describe('<PreviewPanelFooter />', () => {
beforeAll(() => {
beforeEach(() => {
jest.mocked(useExpandableFlyoutApi).mockReturnValue(mockFlyoutApi);
(useKibana as jest.Mock).mockReturnValue({
services: {
osquery: { isOsqueryAvailable: jest.fn() },
telemetry: mockedTelemetry,
cases: { hooks: { useIsAddToCaseOpen: jest.fn().mockReturnValue(false) } },
},
});
(useAlertExceptionActions as jest.Mock).mockReturnValue({ exceptionActionItems: [] });
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
investigateInTimelineActionItems: [],
});
(useAddToCaseActions as jest.Mock).mockReturnValue({ addToCaseActionItems: [] });
});

it('should not render the take action dropdown if preview mode', () => {
const { queryByTestId } = render(
<TestProviders>
<DocumentDetailsContext.Provider value={{ ...mockContextValue, isPreview: true }}>
<PreviewPanelFooter />
</DocumentDetailsContext.Provider>
</TestProviders>
);

expect(queryByTestId(PREVIEW_FOOTER_TEST_ID)).not.toBeInTheDocument();
});

it('should render footer for alert', () => {
Expand All @@ -43,7 +77,7 @@ describe('<PreviewPanelFooter />', () => {
</TestProviders>
);
expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toBeInTheDocument();
expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toHaveTextContent('Show full alert details');
expect(getByTestId(PREVIEW_FOOTER_LINK_TEST_ID)).toHaveTextContent('Show full alert details');
});

it('should render footer for event', () => {
Expand All @@ -56,7 +90,21 @@ describe('<PreviewPanelFooter />', () => {
</DocumentDetailsContext.Provider>
</TestProviders>
);
expect(getByTestId(PREVIEW_FOOTER_TEST_ID)).toHaveTextContent('Show full event details');
expect(getByTestId(PREVIEW_FOOTER_LINK_TEST_ID)).toHaveTextContent('Show full event details');
});

it('should render the take action button', () => {
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
investigateInTimelineActionItems: [{ name: 'test', onClick: jest.fn() }],
});
const { getByTestId } = render(
<TestProviders>
<DocumentDetailsContext.Provider value={mockContextValue}>
<PreviewPanelFooter />
</DocumentDetailsContext.Provider>
</TestProviders>
);
expect(getByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID)).toBeInTheDocument();
});

it('should open document details flyout when clicked', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
* 2.0.
*/

import { EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { FC } from 'react';
import React, { useCallback, useMemo } from 'react';
import { EuiLink, EuiFlyoutFooter, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { TakeActionButton } from '../shared/components/take_action_button';
import { getField } from '../shared/utils';
import { EventKind } from '../shared/constants/event_kinds';
import { FlyoutFooter } from '../../shared/components/flyout_footer';
import { DocumentDetailsRightPanelKey } from '../shared/constants/panel_keys';
import { useDocumentDetailsContext } from '../shared/context';
import { PREVIEW_FOOTER_TEST_ID, PREVIEW_FOOTER_LINK_TEST_ID } from './test_ids';
Expand All @@ -21,8 +22,8 @@ import { DocumentEventTypes } from '../../../common/lib/telemetry';
/**
* Footer at the bottom of preview panel with a link to open document details flyout
*/
export const PreviewPanelFooter = () => {
const { eventId, indexName, scopeId, getFieldsData } = useDocumentDetailsContext();
export const PreviewPanelFooter: FC = () => {
const { eventId, indexName, scopeId, getFieldsData, isPreview } = useDocumentDetailsContext();
const { openFlyout } = useExpandableFlyoutApi();
const { telemetry } = useKibana().services;

Expand All @@ -48,24 +49,36 @@ export const PreviewPanelFooter = () => {
});
}, [openFlyout, eventId, indexName, scopeId, telemetry]);

const fullDetailsLink = useMemo(
() => (
<EuiLink
onClick={openDocumentFlyout}
target="_blank"
data-test-subj={PREVIEW_FOOTER_LINK_TEST_ID}
>
<>
{i18n.translate('xpack.securitySolution.flyout.preview.openFlyoutLabel', {
values: { isAlert },
defaultMessage: 'Show full {isAlert, select, true{alert} other{event}} details',
})}
</>
</EuiLink>
),
[isAlert, openDocumentFlyout]
);

if (isPreview) return null;

return (
<FlyoutFooter data-test-subj={PREVIEW_FOOTER_TEST_ID}>
<EuiFlexGroup justifyContent="center">
<EuiFlexItem grow={false}>
<EuiLink
onClick={openDocumentFlyout}
target="_blank"
data-test-subj={PREVIEW_FOOTER_LINK_TEST_ID}
>
<>
{i18n.translate('xpack.securitySolution.flyout.preview.openFlyoutLabel', {
values: { isAlert },
defaultMessage: 'Show full {isAlert, select, true{alert} other{event}} details',
})}
</>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
</FlyoutFooter>
<EuiFlyoutFooter data-test-subj={PREVIEW_FOOTER_TEST_ID}>
<EuiPanel color="transparent">
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
<EuiFlexItem grow={false}>{fullDetailsLink}</EuiFlexItem>
<EuiFlexItem grow={false}>
<TakeActionButton />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlyoutFooter>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ import { TestProviders } from '../../../common/mock';
import { mockContextValue } from '../shared/mocks/mock_context';
import { DocumentDetailsContext } from '../shared/context';
import { FLYOUT_FOOTER_TEST_ID } from './test_ids';
import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from '../shared/components/test_ids';
import { useKibana } from '../../../common/lib/kibana';
import { useAlertExceptionActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions';
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
import { useAddToCaseActions } from '../../../detections/components/alerts_table/timeline_actions/use_add_to_case_actions';

jest.mock('../../../common/lib/kibana');
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
return {
...original,
useLocation: jest.fn().mockReturnValue({ search: '' }),
};
});
jest.mock('../../../detections/components/alerts_table/timeline_actions/use_add_exception_actions');
jest.mock(
'../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'
Expand All @@ -39,14 +47,13 @@ describe('PanelFooter', () => {
it('should render the take action dropdown', () => {
(useKibana as jest.Mock).mockReturnValue({
services: {
osquery: {
isOsqueryAvailable: jest.fn(),
},
osquery: { isOsqueryAvailable: jest.fn() },
cases: { hooks: { useIsAddToCaseOpen: jest.fn().mockReturnValue(false) } },
},
});
(useAlertExceptionActions as jest.Mock).mockReturnValue({ exceptionActionItems: [] });
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
investigateInTimelineActionItems: [],
investigateInTimelineActionItems: [{ name: 'test', onClick: jest.fn() }],
});
(useAddToCaseActions as jest.Mock).mockReturnValue({ addToCaseActionItems: [] });

Expand All @@ -58,5 +65,6 @@ describe('PanelFooter', () => {
</TestProviders>
);
expect(wrapper.getByTestId(FLYOUT_FOOTER_TEST_ID)).toBeInTheDocument();
expect(wrapper.getByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID)).toBeInTheDocument();
});
});
Loading

0 comments on commit 7aac71c

Please sign in to comment.