Skip to content

Commit 39763e5

Browse files
[Security Solution] Add investigate in timeline action to footer Take action dropdown (#261297)
## Summary This PR adds the investigate in Timeline action to the footer of the new flyout. The actions are accessible when the flyout is opened in Security Solution https://github.com/user-attachments/assets/3f70bc77-bd77-4a93-b0f2-ab8155756ff4 But NOT when the flyout is opened in Discover <img width="1357" height="853" alt="Screenshot 2026-04-05 at 10 45 21 PM" src="https://github.com/user-attachments/assets/3b14092b-8dc6-4dac-ae04-b5629fa19adf" /> ### 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 - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels.
1 parent e9004c7 commit 39763e5

2 files changed

Lines changed: 61 additions & 11 deletions

File tree

x-pack/solutions/security/plugins/security_solution/public/flyout_v2/document/components/take_action_button.test.tsx

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { useAddToCaseActions } from '../../../detections/components/alerts_table
1414
import { useAlertsActions } from '../../../detections/components/alerts_table/timeline_actions/use_alerts_actions';
1515
import { useAlertAssigneesActions } from '../../../detections/components/alerts_table/timeline_actions/use_alert_assignees_actions';
1616
import { useAlertTagsActions } from '../../../detections/components/alerts_table/timeline_actions/use_alert_tags_actions';
17+
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
18+
import { useIsInSecurityApp } from '../../../common/hooks/is_in_security_app';
1719
import { TakeActionButton } from './take_action_button';
1820
import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from './test_ids';
1921

@@ -23,6 +25,10 @@ jest.mock(
2325
'../../../detections/components/alerts_table/timeline_actions/use_alert_assignees_actions'
2426
);
2527
jest.mock('../../../detections/components/alerts_table/timeline_actions/use_alert_tags_actions');
28+
jest.mock(
29+
'../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline'
30+
);
31+
jest.mock('../../../common/hooks/is_in_security_app');
2632

2733
const mockUseAddToCaseActions = useAddToCaseActions as jest.Mock;
2834
const mockUseAlertsActions = useAlertsActions as jest.Mock;
@@ -36,7 +42,8 @@ const createMockHit = (flattened: Record<string, unknown> = {}): DataTableRecord
3642
flattened,
3743
isAnchor: false,
3844
} as DataTableRecord);
39-
45+
const mockUseInvestigateInTimeline = useInvestigateInTimeline as jest.Mock;
46+
const mockUseIsInSecurityApp = useIsInSecurityApp as jest.Mock;
4047
const mockEcsData: Ecs = { _id: 'test-id', _index: 'test-index' };
4148
const mockNonEcsData: TimelineNonEcsData[] = [{ field: 'host.name', value: ['test-host'] }];
4249
const mockRefetchFlyoutData = jest.fn().mockResolvedValue(undefined);
@@ -62,6 +69,8 @@ describe('<TakeActionButton />', () => {
6269
alertAssigneesPanels: [],
6370
});
6471
mockUseAlertTagsActions.mockReturnValue({ alertTagsItems: [], alertTagsPanels: [] });
72+
mockUseInvestigateInTimeline.mockReturnValue({ investigateInTimelineActionItems: [] });
73+
mockUseIsInSecurityApp.mockReturnValue(true);
6574
});
6675

6776
it('should render the take action button', () => {
@@ -97,17 +106,40 @@ describe('<TakeActionButton />', () => {
97106
);
98107
});
99108

100-
it('should render action items in the popover', () => {
101-
const mockItems = [
102-
{ name: 'Add to new case', onClick: jest.fn() },
103-
{ name: 'Add to existing case', onClick: jest.fn() },
104-
];
105-
mockUseAddToCaseActions.mockReturnValue({ addToCaseActionItems: mockItems });
109+
it('should call useInvestigateInTimeline with the correct arguments', () => {
110+
renderTakeActionButton();
106111

107-
const { getByTestId } = renderTakeActionButton();
108-
fireEvent.click(getByTestId(FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID));
112+
expect(mockUseInvestigateInTimeline).toHaveBeenCalledWith(
113+
expect.objectContaining({ ecsRowData: mockEcsData })
114+
);
115+
});
116+
117+
it('should include investigateInTimelineActionItems when in Security app', () => {
118+
mockUseIsInSecurityApp.mockReturnValue(true);
119+
const timelineItem = { name: 'Investigate in timeline', onClick: jest.fn() };
120+
mockUseInvestigateInTimeline.mockReturnValue({
121+
investigateInTimelineActionItems: [timelineItem],
122+
});
123+
124+
renderTakeActionButton();
125+
126+
expect(mockUseInvestigateInTimeline).toHaveBeenCalledWith(
127+
expect.objectContaining({ ecsRowData: mockEcsData })
128+
);
129+
});
130+
131+
it('should not include investigateInTimelineActionItems when not in Security app (e.g. Discover)', () => {
132+
mockUseIsInSecurityApp.mockReturnValue(false);
133+
const timelineItem = { name: 'Investigate in timeline', onClick: jest.fn() };
134+
mockUseInvestigateInTimeline.mockReturnValue({
135+
investigateInTimelineActionItems: [timelineItem],
136+
});
137+
138+
const { queryByText } = renderTakeActionButton();
139+
140+
fireEvent.click(queryByText('Take action')!.closest('button')!);
109141

110-
expect(getByTestId('takeActionPanelMenu')).toBeInTheDocument();
142+
expect(queryByText('Investigate in timeline')).not.toBeInTheDocument();
111143
});
112144

113145
it('should pass onAlertUpdated as refetch to useAlertsActions', () => {

x-pack/solutions/security/plugins/security_solution/public/flyout_v2/document/components/take_action_button.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { useAddToCaseActions } from '../../../detections/components/alerts_table
1919
import { useAlertsActions } from '../../../detections/components/alerts_table/timeline_actions/use_alerts_actions';
2020
import { useAlertAssigneesActions } from '../../../detections/components/alerts_table/timeline_actions/use_alert_assignees_actions';
2121
import { useAlertTagsActions } from '../../../detections/components/alerts_table/timeline_actions/use_alert_tags_actions';
22+
import { useInvestigateInTimeline } from '../../../detections/components/alerts_table/timeline_actions/use_investigate_in_timeline';
23+
import { useIsInSecurityApp } from '../../../common/hooks/is_in_security_app';
2224
import { FLYOUT_FOOTER_DROPDOWN_BUTTON_TEST_ID } from './test_ids';
2325

2426
const TAKE_ACTION = i18n.translate('xpack.securitySolution.flyoutV2.footer.takeActionButtonLabel', {
@@ -62,6 +64,8 @@ export const TakeActionButton = memo(
6264
setIsPopoverOpen(false);
6365
}, []);
6466

67+
const isInSecurityApp = useIsInSecurityApp();
68+
6569
const eventId = hit.raw._id as string;
6670
const isAlert = (getFieldValue(hit, EVENT_KIND) as string) === EventKind.signal;
6771
const rawStatus = getFieldValue(hit, ALERT_WORKFLOW_STATUS);
@@ -99,14 +103,28 @@ export const TakeActionButton = memo(
99103
refetch: onAssigneesUpdate,
100104
});
101105

106+
const { investigateInTimelineActionItems } = useInvestigateInTimeline({
107+
ecsRowData: ecsData,
108+
onInvestigateInTimelineAlertClick: closePopoverHandler,
109+
});
110+
102111
const items = useMemo(
103112
() => [
104113
...addToCaseActionItems,
105114
...(isAlert ? statusActionItems : []),
106115
...(isAlert ? alertAssigneesItems : []),
107116
...(isAlert ? alertTagsItems : []),
117+
...(isInSecurityApp ? investigateInTimelineActionItems : []),
108118
],
109-
[addToCaseActionItems, alertTagsItems, isAlert, statusActionItems, alertAssigneesItems]
119+
[
120+
addToCaseActionItems,
121+
alertTagsItems,
122+
investigateInTimelineActionItems,
123+
isAlert,
124+
isInSecurityApp,
125+
statusActionItems,
126+
alertAssigneesItems,
127+
]
110128
);
111129

112130
const panels = useMemo(

0 commit comments

Comments
 (0)