Skip to content

Commit

Permalink
[8.8] [SecuritySolution] Update timeline actions (#155692) (#156595)
Browse files Browse the repository at this point in the history
# Backport

This will backport the following commits from `main` to `8.8`:
- [[SecuritySolution] Update timeline actions
(#155692)](#155692)

<!--- Backport version: 8.9.7 -->

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

<!--BACKPORT [{"author":{"name":"Angela
Chuang","email":"[email protected]"},"sourceCommit":{"committedDate":"2023-05-03T17:03:48Z","message":"[SecuritySolution]
Update timeline actions (#155692)\n\n## Summary\r\n\r\nissue:
#155034
|\r\nhttps://github.com//issues/155586\r\n\r\nPlease find
expected behaviours
here:\r\nhttps://github.com//issues/155034#issuecomment-1523735414","sha":"5be0f40052b23d4699f41cd4e0189ffce6468297","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:Threat
Hunting","Team: SecuritySolution","Team:Threat
Hunting:Explore","v8.8.0","Feature:Lens
Charts","v8.9.0"],"number":155692,"url":"https://github.com/elastic/kibana/pull/155692","mergeCommit":{"message":"[SecuritySolution]
Update timeline actions (#155692)\n\n## Summary\r\n\r\nissue:
#155034
|\r\nhttps://github.com//issues/155586\r\n\r\nPlease find
expected behaviours
here:\r\nhttps://github.com//issues/155034#issuecomment-1523735414","sha":"5be0f40052b23d4699f41cd4e0189ffce6468297"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"8.8","label":"v8.8.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/155692","number":155692,"mergeCommit":{"message":"[SecuritySolution]
Update timeline actions (#155692)\n\n## Summary\r\n\r\nissue:
#155034
|\r\nhttps://github.com//issues/155586\r\n\r\nPlease find
expected behaviours
here:\r\nhttps://github.com//issues/155034#issuecomment-1523735414","sha":"5be0f40052b23d4699f41cd4e0189ffce6468297"}}]}]
BACKPORT-->

Co-authored-by: Angela Chuang <[email protected]>
  • Loading branch information
kibanamachine and angorayc authored May 3, 2023
1 parent 8b39f11 commit 12884ac
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import {
GLOBAL_SEARCH_BAR_FILTER_ITEM,
GLOBAL_SEARCH_BAR_FILTER_ITEM_DELETE,
} from '../../screens/search_bar';
import { TIMELINE_DATA_PROVIDERS_CONTAINER } from '../../screens/timeline';
import { closeTimelineUsingCloseButton } from '../../tasks/security_main';
import { TOASTER } from '../../screens/alerts_detection_rules';

describe('Histogram legend hover actions', { testIsolation: false }, () => {
const ruleConfigs = getNewRule();
Expand Down Expand Up @@ -60,8 +59,7 @@ describe('Histogram legend hover actions', { testIsolation: false }, () => {
it('Add To Timeline', function () {
clickAlertsHistogramLegend();
clickAlertsHistogramLegendAddToTimeline(ruleConfigs.name);
cy.get(TIMELINE_DATA_PROVIDERS_CONTAINER).should('be.visible');
cy.get(TIMELINE_DATA_PROVIDERS_CONTAINER).should('contain.text', getNewRule().name);
closeTimelineUsingCloseButton();

cy.get(TOASTER).should('have.text', `Added ${ruleConfigs.name} to timeline`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
*/

import type { SecurityAppStore } from '../../../common/store/types';
import type { DataProvider } from '../../../../common/types';
import { TimelineId } from '../../../../common/types';
import { addProvider } from '../../../timelines/store/timeline/actions';
import { createAddToNewTimelineCellActionFactory, getToastMessage } from './add_to_new_timeline';
import { addProvider, showTimeline } from '../../../timelines/store/timeline/actions';
import { createInvestigateInNewTimelineCellActionFactory } from './investigate_in_new_timeline';
import type { CellActionExecutionContext } from '@kbn/cell-actions';
import { GEO_FIELD_TYPE } from '../../../timelines/components/timeline/body/renderers/constants';
import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock';
Expand Down Expand Up @@ -52,7 +51,7 @@ const defaultAddProviderAction = {
};

describe('createAddToNewTimelineCellAction', () => {
const addToTimelineCellActionFactory = createAddToNewTimelineCellActionFactory({
const addToTimelineCellActionFactory = createInvestigateInNewTimelineCellActionFactory({
store,
services,
});
Expand Down Expand Up @@ -175,73 +174,22 @@ describe('createAddToNewTimelineCellAction', () => {
},
});
});
});

describe('getToastMessage', () => {
it('handles empty input', () => {
const result = getToastMessage({ queryMatch: { value: null } } as unknown as DataProvider);
expect(result).toEqual('');
});
it('handles array input', () => {
const result = getToastMessage({
queryMatch: { value: ['hello', 'world'] },
} as unknown as DataProvider);
expect(result).toEqual('hello, world alerts');
});

it('handles single filter', () => {
const result = getToastMessage({
queryMatch: { value },
and: [{ queryMatch: { field: 'kibana.alert.severity', value: 'critical' } }],
} as unknown as DataProvider);
expect(result).toEqual(`critical severity alerts from ${value}`);
});

it('handles multiple filters', () => {
const result = getToastMessage({
queryMatch: { value },
and: [
{
queryMatch: { field: 'kibana.alert.workflow_status', value: 'open' },
},
{
queryMatch: { field: 'kibana.alert.severity', value: 'critical' },
},
],
} as unknown as DataProvider);
expect(result).toEqual(`open, critical severity alerts from ${value}`);
});

it('ignores unrelated filters', () => {
const result = getToastMessage({
queryMatch: { value },
and: [
{
queryMatch: { field: 'kibana.alert.workflow_status', value: 'open' },
},
{
queryMatch: { field: 'kibana.alert.severity', value: 'critical' },
},
// currently only supporting the above fields
{
queryMatch: { field: 'user.name', value: 'something' },
},
],
} as unknown as DataProvider);
expect(result).toEqual(`open, critical severity alerts from ${value}`);
});

it('returns entity only when unrelated filters are passed', () => {
const result = getToastMessage({
queryMatch: { value },
and: [{ queryMatch: { field: 'user.name', value: 'something' } }],
} as unknown as DataProvider);
expect(result).toEqual(`${value} alerts`);
});
it('should open the timeline', async () => {
await addToTimelineAction.execute({
...context,
metadata: {
andFilters: [{ field: 'kibana.alert.severity', value: 'low' }],
},
});

it('returns entity only when no filters are passed', () => {
const result = getToastMessage({ queryMatch: { value }, and: [] } as unknown as DataProvider);
expect(result).toEqual(`${value} alerts`);
expect(mockDispatch).toBeCalledWith({
type: showTimeline.type,
payload: {
id: TimelineId.active,
show: true,
},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,23 @@

import { createCellActionFactory, type CellActionTemplate } from '@kbn/cell-actions';
import { timelineActions } from '../../../timelines/store/timeline';
import { addProvider } from '../../../timelines/store/timeline/actions';
import type { DataProvider } from '../../../../common/types';
import { addProvider, showTimeline } from '../../../timelines/store/timeline/actions';
import { TimelineId } from '../../../../common/types';
import type { SecurityAppStore } from '../../../common/store';
import { fieldHasCellActions } from '../../utils';
import {
ADD_TO_NEW_TIMELINE,
ADD_TO_TIMELINE_FAILED_TEXT,
ADD_TO_TIMELINE_FAILED_TITLE,
ADD_TO_TIMELINE_ICON,
ADD_TO_TIMELINE_SUCCESS_TITLE,
ALERTS_COUNT,
SEVERITY,
INVESTIGATE_IN_TIMELINE,
} from '../constants';
import { createDataProviders, isValidDataProviderField } from '../data_provider';
import { SecurityCellActionType } from '../../constants';
import type { StartServices } from '../../../types';
import type { SecurityCellAction } from '../../types';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';

const severityField = 'kibana.alert.severity';
const statusField = 'kibana.alert.workflow_status';

export const getToastMessage = ({ queryMatch: { value }, and = [] }: DataProvider) => {
if (value == null) {
return '';
}
const fieldValue = Array.isArray(value) ? value.join(', ') : value.toString();

const descriptors = and.reduce<string[]>((msg, { queryMatch }) => {
if (Array.isArray(queryMatch.value)) {
return msg;
}
if (queryMatch.field === severityField) {
msg.push(SEVERITY(queryMatch.value.toString()));
}
if (queryMatch.field === statusField) {
msg.push(queryMatch.value.toString());
}
return msg;
}, []);

return ALERTS_COUNT(fieldValue, descriptors.join(', '));
};

export const createAddToNewTimelineCellActionFactory = createCellActionFactory(
export const createInvestigateInNewTimelineCellActionFactory = createCellActionFactory(
({
store,
services,
Expand All @@ -63,10 +34,10 @@ export const createAddToNewTimelineCellActionFactory = createCellActionFactory(
const { notifications: notificationsService } = services;

return {
type: SecurityCellActionType.ADD_TO_TIMELINE,
type: SecurityCellActionType.INVESTIGATE_IN_NEW_TIMELINE,
getIconType: () => ADD_TO_TIMELINE_ICON,
getDisplayName: () => ADD_TO_NEW_TIMELINE,
getDisplayNameTooltip: () => ADD_TO_NEW_TIMELINE,
getDisplayName: () => INVESTIGATE_IN_TIMELINE,
getDisplayNameTooltip: () => INVESTIGATE_IN_TIMELINE,
isCompatible: async ({ field }) =>
fieldHasCellActions(field.name) && isValidDataProviderField(field.name, field.type),
execute: async ({ field, metadata }) => {
Expand Down Expand Up @@ -101,10 +72,9 @@ export const createAddToNewTimelineCellActionFactory = createCellActionFactory(
id: TimelineId.active,
})
);
store.dispatch(showTimeline({ id: TimelineId.active, show: true }));

store.dispatch(addProvider({ id: TimelineId.active, providers: dataProviders }));
notificationsService.toasts.addSuccess({
title: ADD_TO_TIMELINE_SUCCESS_TITLE(getToastMessage(dataProviders[0])),
});
} else {
notificationsService.toasts.addWarning({
title: ADD_TO_TIMELINE_FAILED_TITLE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const ADD_TO_TIMELINE = i18n.translate(
defaultMessage: 'Add to timeline',
}
);
export const ADD_TO_NEW_TIMELINE = i18n.translate(
export const INVESTIGATE_IN_TIMELINE = i18n.translate(
'xpack.securitySolution.actions.cellValue.addToNewTimeline.displayName',
{
defaultMessage: 'Investigate in timeline',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
*/

export { createAddToTimelineCellActionFactory } from './cell_action/add_to_timeline';
export { createAddToNewTimelineCellActionFactory } from './cell_action/add_to_new_timeline';
export { createInvestigateInNewTimelineCellActionFactory } from './cell_action/investigate_in_new_timeline';
export { createAddToTimelineLensAction } from './lens/add_to_timeline';
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { Subject } from 'rxjs';
import type { CellValueContext, EmbeddableInput, IEmbeddable } from '@kbn/embeddable-plugin/public';
import { ErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { LENS_EMBEDDABLE_TYPE } from '@kbn/lens-plugin/public';
import type { SecurityAppStore } from '../../../common/store/types';
import { createAddToTimelineLensAction } from './add_to_timeline';
import { createAddToTimelineLensAction, getInvestigatedValue } from './add_to_timeline';
import { KibanaServices } from '../../../common/lib/kibana';
import { APP_UI_ID } from '../../../../common/constants';
import { Subject } from 'rxjs';
import { TimelineId } from '../../../../common/types';
import { addProvider, showTimeline } from '../../../timelines/store/timeline/actions';
import type { DataProvider } from '../../../../common/types';
import { TimelineId, EXISTS_OPERATOR } from '../../../../common/types';
import { addProvider } from '../../../timelines/store/timeline/actions';
import type { ActionExecutionContext } from '@kbn/ui-actions-plugin/public';

jest.mock('../../../common/lib/kibana');
const currentAppId$ = new Subject<string | undefined>();
KibanaServices.get().application.currentAppId$ = currentAppId$.asObservable();
const mockWarningToast = jest.fn();
const mockSuccessToast = jest.fn();
KibanaServices.get().notifications.toasts.addWarning = mockWarningToast;

KibanaServices.get().notifications.toasts.addSuccess = mockSuccessToast;
const mockDispatch = jest.fn();
const store = {
dispatch: mockDispatch,
Expand Down Expand Up @@ -158,7 +159,7 @@ describe('createAddToTimelineLensAction', () => {
describe('execute', () => {
it('should execute normally', async () => {
await addToTimelineAction.execute(context);
expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledWith({
type: addProvider.type,
payload: {
Expand All @@ -180,13 +181,8 @@ describe('createAddToTimelineLensAction', () => {
],
},
});
expect(mockDispatch).toHaveBeenCalledWith({
type: showTimeline.type,
payload: {
id: TimelineId.active,
show: true,
},
});

expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockWarningToast).not.toHaveBeenCalled();
});

Expand All @@ -195,7 +191,7 @@ describe('createAddToTimelineLensAction', () => {
...context,
data: [{ columnMeta }],
});
expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledWith({
type: addProvider.type,
payload: {
Expand All @@ -217,13 +213,7 @@ describe('createAddToTimelineLensAction', () => {
],
},
});
expect(mockDispatch).toHaveBeenCalledWith({
type: showTimeline.type,
payload: {
id: TimelineId.active,
show: true,
},
});
expect(mockSuccessToast).toHaveBeenCalledTimes(1);
expect(mockWarningToast).not.toHaveBeenCalled();
});

Expand All @@ -242,7 +232,7 @@ describe('createAddToTimelineLensAction', () => {
},
],
});
expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledWith({
type: addProvider.type,
payload: {
Expand All @@ -264,13 +254,8 @@ describe('createAddToTimelineLensAction', () => {
],
},
});
expect(mockDispatch).toHaveBeenCalledWith({
type: showTimeline.type,
payload: {
id: TimelineId.active,
show: true,
},
});

expect(mockSuccessToast).toHaveBeenCalledTimes(1);
expect(mockWarningToast).not.toHaveBeenCalled();
});

Expand All @@ -293,3 +278,28 @@ describe('createAddToTimelineLensAction', () => {
});
});
});

describe('getInvestigatedValue', () => {
it('handles empty input', () => {
const result = getInvestigatedValue([
{ queryMatch: { value: null } },
] as unknown as DataProvider[]);
expect(result).toEqual('');
});
it('handles array input', () => {
const result = getInvestigatedValue([
{
queryMatch: { value: ['hello', 'world'] },
},
] as unknown as DataProvider[]);
expect(result).toEqual('hello, world');
});
it('handles number value', () => {
const result = getInvestigatedValue([
{
queryMatch: { value: '', operator: EXISTS_OPERATOR, field: 'host.name' },
},
] as unknown as DataProvider[]);
expect(result).toEqual('host.name');
});
});
Loading

0 comments on commit 12884ac

Please sign in to comment.