Skip to content

Commit

Permalink
update timeline actions
Browse files Browse the repository at this point in the history
  • Loading branch information
angorayc committed Apr 28, 2023
1 parent d80fdd6 commit 8de035d
Show file tree
Hide file tree
Showing 9 changed files with 102 additions and 146 deletions.
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 8de035d

Please sign in to comment.