diff --git a/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_new_timeline.test.ts b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_new_timeline.test.ts new file mode 100644 index 0000000000000..7f3ed646da749 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_new_timeline.test.ts @@ -0,0 +1,247 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +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 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'; +import { timelineActions } from '../../../timelines/store/timeline'; + +const services = createStartServicesMock(); +const mockWarningToast = services.notifications.toasts.addWarning; + +const mockDispatch = jest.fn(); +const store = { + dispatch: mockDispatch, +} as unknown as SecurityAppStore; + +const value = 'the-value'; + +const context = { + field: { name: 'user.name', value, type: 'text' }, +} as CellActionExecutionContext; + +const defaultAddProviderAction = { + type: addProvider.type, + payload: { + id: TimelineId.active, + providers: [ + { + and: [], + enabled: true, + excluded: false, + id: 'event-field-default-timeline-1-user_name-0-the-value', + kqlQuery: '', + name: 'user.name', + queryMatch: { + field: 'user.name', + operator: ':', + value: 'the-value', + }, + }, + ], + }, +}; + +describe('createAddToNewTimelineCellAction', () => { + const addToTimelineCellActionFactory = createAddToNewTimelineCellActionFactory({ + store, + services, + }); + const addToTimelineAction = addToTimelineCellActionFactory({ id: 'testAddToTimeline', order: 1 }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return display name', () => { + expect(addToTimelineAction.getDisplayName(context)).toEqual('Investigate in timeline'); + }); + + it('should return icon type', () => { + expect(addToTimelineAction.getIconType(context)).toEqual('timeline'); + }); + + describe('isCompatible', () => { + it('should return true if everything is okay', async () => { + expect(await addToTimelineAction.isCompatible(context)).toEqual(true); + }); + it('should return false if field not allowed', async () => { + expect( + await addToTimelineAction.isCompatible({ + ...context, + field: { ...context.field, name: 'signal.reason' }, + }) + ).toEqual(false); + }); + }); + + describe('execute', () => { + it('should execute normally', async () => { + await addToTimelineAction.execute(context); + expect(mockDispatch).toHaveBeenCalledWith(defaultAddProviderAction); + expect(mockWarningToast).not.toHaveBeenCalled(); + }); + + it('should show warning if no provider added', async () => { + await addToTimelineAction.execute({ + ...context, + field: { + ...context.field, + type: GEO_FIELD_TYPE, + }, + }); + expect(mockDispatch).not.toHaveBeenCalled(); + expect(mockWarningToast).toHaveBeenCalled(); + }); + + describe('should execute correctly when negateFilters is provided', () => { + it('should not exclude if negateFilters is false', async () => { + await addToTimelineAction.execute({ + ...context, + metadata: { + negateFilters: false, + }, + }); + expect(mockDispatch).toHaveBeenCalledWith(defaultAddProviderAction); + expect(mockWarningToast).not.toHaveBeenCalled(); + }); + + it('should exclude if negateFilters is true', async () => { + await addToTimelineAction.execute({ + ...context, + metadata: { + negateFilters: true, + }, + }); + expect(mockDispatch).toHaveBeenCalledWith({ + ...defaultAddProviderAction, + payload: { + ...defaultAddProviderAction.payload, + providers: [{ ...defaultAddProviderAction.payload.providers[0], excluded: true }], + }, + }); + expect(mockWarningToast).not.toHaveBeenCalled(); + }); + }); + + it('should clear the timeline', async () => { + await addToTimelineAction.execute(context); + expect(mockDispatch.mock.calls[0][0].type).toEqual(timelineActions.createTimeline.type); + }); + + it('should add the providers to the timeline', async () => { + await addToTimelineAction.execute({ + ...context, + metadata: { + andFilters: [{ field: 'kibana.alert.severity', value: 'low' }], + }, + }); + + expect(mockDispatch).toBeCalledWith({ + ...defaultAddProviderAction, + payload: { + ...defaultAddProviderAction.payload, + providers: [ + { + ...defaultAddProviderAction.payload.providers[0], + id: 'event-field-default-timeline-1-user_name-0-the-value', + queryMatch: defaultAddProviderAction.payload.providers[0].queryMatch, + and: [ + { + enabled: true, + excluded: false, + id: 'event-field-default-timeline-1-kibana_alert_severity-0-low', + kqlQuery: '', + name: 'kibana.alert.severity', + queryMatch: { + field: 'kibana.alert.severity', + operator: ':', + value: 'low', + }, + and: [], + }, + ], + }, + ], + }, + }); + }); + }); + + 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('returns entity only when no filters are passed', () => { + const result = getToastMessage({ queryMatch: { value }, and: [] } as unknown as DataProvider); + expect(result).toEqual(`${value} alerts`); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_new_timeline.ts b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_new_timeline.ts new file mode 100644 index 0000000000000..886162803bbf1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_new_timeline.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +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 { 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, +} 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((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( + ({ + store, + services, + }: { + store: SecurityAppStore; + services: StartServices; + }): CellActionTemplate => { + const { notifications: notificationsService } = services; + + return { + type: SecurityCellActionType.ADD_TO_TIMELINE, + getIconType: () => ADD_TO_TIMELINE_ICON, + getDisplayName: () => ADD_TO_NEW_TIMELINE, + getDisplayNameTooltip: () => ADD_TO_NEW_TIMELINE, + isCompatible: async ({ field }) => + fieldHasCellActions(field.name) && isValidDataProviderField(field.name, field.type), + execute: async ({ field, metadata }) => { + const dataProviders = + createDataProviders({ + contextId: TimelineId.active, + fieldType: field.type, + values: field.value, + field: field.name, + negate: metadata?.negateFilters === true, + }) ?? []; + + for (const andFilter of metadata?.andFilters ?? []) { + const andDataProviders = + createDataProviders({ + contextId: TimelineId.active, + field: andFilter.field, + values: andFilter.value, + }) ?? []; + if (andDataProviders) { + for (const dataProvider of dataProviders) { + dataProvider.and.push(...andDataProviders); + } + } + } + + if (dataProviders.length > 0) { + // clear timeline + store.dispatch( + timelineActions.createTimeline({ + ...timelineDefaults, + id: TimelineId.active, + }) + ); + 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, + text: ADD_TO_TIMELINE_FAILED_TEXT, + }); + } + }, + }; + } +); diff --git a/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.test.ts b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.test.ts index 84351fd0beaa7..340a7cc11c2e2 100644 --- a/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.test.ts +++ b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.test.ts @@ -8,15 +8,11 @@ import type { SecurityAppStore } from '../../../common/store/types'; import { TimelineId } from '../../../../common/types'; import { addProvider } from '../../../timelines/store/timeline/actions'; -import { createAddToTimelineCellActionFactory, getToastMessage } from './add_to_timeline'; +import { createAddToTimelineCellActionFactory } from './add_to_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'; -const mockId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; -jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'), -})); const services = createStartServicesMock(); const mockWarningToast = services.notifications.toasts.addWarning; @@ -131,114 +127,5 @@ describe('createAddToTimelineCellAction', () => { expect(mockWarningToast).not.toHaveBeenCalled(); }); }); - - it('should clear the timeline if andFilters are included', async () => { - await addToTimelineAction.execute({ - ...context, - metadata: { - andFilters: [{ field: 'kibana.alert.severity', value: 'low' }], - }, - }); - - expect(mockDispatch.mock.calls[0][0].type).toEqual( - 'x-pack/security_solution/local/timeline/CREATE_TIMELINE' - ); - - expect(mockDispatch.mock.calls[1][0]).toEqual({ - ...defaultDataProvider, - payload: { - ...defaultDataProvider.payload, - providers: [ - { - ...defaultDataProvider.payload.providers[0], - id: mockId, - queryMatch: { - ...defaultDataProvider.payload.providers[0].queryMatch, - displayValue: 'the-value', - }, - and: [ - { - enabled: true, - excluded: false, - id: mockId, - kqlQuery: '', - name: 'kibana.alert.severity', - queryMatch: { - displayValue: 'low', - field: 'kibana.alert.severity', - operator: ':', - value: 'low', - }, - }, - ], - }, - ], - }, - }); - }); - }); - - describe('getToastMessage', () => { - it('handles empty input', () => { - const result = getToastMessage([], null); - expect(result).toEqual(''); - }); - it('handles array input', () => { - const result = getToastMessage([], ['hello', 'world']); - expect(result).toEqual('hello, world'); - }); - - it('handles single filter', () => { - const result = getToastMessage( - [{ field: 'kibana.alert.severity', value: 'critical' }], - value - ); - expect(result).toEqual(`critical severity alerts from ${value}`); - }); - - it('handles multiple filters', () => { - const result = getToastMessage( - [ - { field: 'kibana.alert.workflow_status', value: 'open' }, - { field: 'kibana.alert.severity', value: 'critical' }, - ], - value - ); - expect(result).toEqual(`open, critical severity alerts from ${value}`); - }); - - it('ignores unrelated filters', () => { - const result = getToastMessage( - [ - { field: 'kibana.alert.workflow_status', value: 'open' }, - { field: 'kibana.alert.severity', value: 'critical' }, - // currently only supporting the above fields - { field: 'user.name', value: 'something' }, - ], - value - ); - expect(result).toEqual(`open, critical severity alerts from ${value}`); - }); - - it('returns entity only when unrelated filters are passed', () => { - const result = getToastMessage([{ field: 'user.name', value: 'something' }], value); - expect(result).toEqual(`${value} alerts`); - }); - - it('returns entity only when no filters are passed', () => { - const result = getToastMessage([], value); - expect(result).toEqual(`${value} alerts`); - }); - - it('returns entity only when wildcard filters are passed', () => { - const result = getToastMessage( - [ - { field: 'kibana.alert.severity', value: '*' }, - { field: 'kibana.alert.workflow_status', value: '*' }, - ], - value - ); - expect(result).toEqual(`${value} alerts`); - }); }); }); diff --git a/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.ts b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.ts index f2e44605dbeed..1fb2f5cee22f9 100644 --- a/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.ts +++ b/x-pack/plugins/security_solution/public/actions/add_to_timeline/cell_action/add_to_timeline.ts @@ -7,11 +7,6 @@ import { createCellActionFactory } from '@kbn/cell-actions'; import type { CellActionTemplate } from '@kbn/cell-actions'; -import type { CellActionField } from '@kbn/cell-actions/src/types'; -import { isArray } from 'lodash/fp'; -import { timelineActions } from '../../../timelines/store/timeline'; -import type { Filter } from '../../../overview/components/detection_response/hooks/use_navigate_to_timeline'; -import { getDataProviders } from '../../../overview/components/detection_response/hooks/use_navigate_to_timeline'; import { addProvider } from '../../../timelines/store/timeline/actions'; import { TimelineId } from '../../../../common/types'; import type { SecurityAppStore } from '../../../common/store'; @@ -22,60 +17,11 @@ import { ADD_TO_TIMELINE_FAILED_TITLE, ADD_TO_TIMELINE_ICON, ADD_TO_TIMELINE_SUCCESS_TITLE, - ALERTS_COUNT, - SEVERITY, } 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'; - -export const getToastMessage = (filters: Filter[], fieldValue?: CellActionField['value']) => { - if (fieldValue == null) { - return ''; - } - if (isArray(fieldValue)) { - return fieldValue.join(', '); - } - const descriptorFields = ['kibana.alert.severity', 'kibana.alert.workflow_status']; - const severityDescriptors = ['critical', 'high', 'low', 'medium']; - const alertDescriptors = [...severityDescriptors, 'acknowledged', 'closed', 'open']; - - const descriptors = filters.reduce((msg, filter) => { - if (isArray(filter.value)) { - return msg; - } - if (descriptorFields.includes(filter.field) && alertDescriptors.includes(filter.value)) { - const val = severityDescriptors.includes(filter.value) - ? SEVERITY(filter.value) - : filter.value; - return msg.length === 0 ? val : `${msg}, ${val}`; - } - return msg; - }, ''); - - return ALERTS_COUNT(fieldValue, descriptors); -}; - -const handleAndFilters = (filters: Filter[], field: CellActionField, store: SecurityAppStore) => { - const dataProviders = getDataProviders([ - [{ value: field.value ?? '', field: field.name }, ...filters], - ]); - - // clear timeline to accurately get count - store.dispatch( - timelineActions.createTimeline({ - ...timelineDefaults, - id: TimelineId.active, - }) - ); - - return { - messageValue: getToastMessage(filters, field.value), - dataProviders, - }; -}; export const createAddToTimelineCellActionFactory = createCellActionFactory( ({ @@ -95,28 +41,22 @@ export const createAddToTimelineCellActionFactory = createCellActionFactory( isCompatible: async ({ field }) => fieldHasCellActions(field.name) && isValidDataProviderField(field.name, field.type), execute: async ({ field, metadata }) => { - let messageValue = ''; - let dataProviders = []; - if (metadata && metadata.andFilters) { - const filters = handleAndFilters(metadata.andFilters, field, store); - messageValue = filters.messageValue; - dataProviders = filters.dataProviders; - } else { - dataProviders = - createDataProviders({ - contextId: TimelineId.active, - fieldType: field.type, - values: field.value, - field: field.name, - negate: metadata?.negateFilters === true, - }) ?? []; - if (field.value != null) { - messageValue = Array.isArray(field.value) ? field.value.join(', ') : field.value; - } - } + const dataProviders = + createDataProviders({ + contextId: TimelineId.active, + fieldType: field.type, + values: field.value, + field: field.name, + negate: metadata?.negateFilters === true, + }) ?? []; if (dataProviders.length > 0) { store.dispatch(addProvider({ id: TimelineId.active, providers: dataProviders })); + + let messageValue = ''; + if (field.value != null) { + messageValue = Array.isArray(field.value) ? field.value.join(', ') : field.value; + } notificationsService.toasts.addSuccess({ title: ADD_TO_TIMELINE_SUCCESS_TITLE(messageValue), }); diff --git a/x-pack/plugins/security_solution/public/actions/add_to_timeline/constants.ts b/x-pack/plugins/security_solution/public/actions/add_to_timeline/constants.ts index 645cb8481f8ba..0396cad110367 100644 --- a/x-pack/plugins/security_solution/public/actions/add_to_timeline/constants.ts +++ b/x-pack/plugins/security_solution/public/actions/add_to_timeline/constants.ts @@ -15,6 +15,12 @@ export const ADD_TO_TIMELINE = i18n.translate( defaultMessage: 'Add to timeline', } ); +export const ADD_TO_NEW_TIMELINE = i18n.translate( + 'xpack.securitySolution.actions.cellValue.addToNewTimeline.displayName', + { + defaultMessage: 'Investigate in timeline', + } +); export const SEVERITY = (level: string) => i18n.translate('xpack.securitySolution.actions.addToTimeline.severityLevel', { diff --git a/x-pack/plugins/security_solution/public/actions/add_to_timeline/index.ts b/x-pack/plugins/security_solution/public/actions/add_to_timeline/index.ts index c639dde1e2337..72e6eee17e4d4 100644 --- a/x-pack/plugins/security_solution/public/actions/add_to_timeline/index.ts +++ b/x-pack/plugins/security_solution/public/actions/add_to_timeline/index.ts @@ -6,4 +6,5 @@ */ export { createAddToTimelineCellActionFactory } from './cell_action/add_to_timeline'; +export { createAddToNewTimelineCellActionFactory } from './cell_action/add_to_new_timeline'; export { createAddToTimelineLensAction } from './lens/add_to_timeline'; diff --git a/x-pack/plugins/security_solution/public/actions/register.ts b/x-pack/plugins/security_solution/public/actions/register.ts index bc3c6da04c66e..93a01b1825558 100644 --- a/x-pack/plugins/security_solution/public/actions/register.ts +++ b/x-pack/plugins/security_solution/public/actions/register.ts @@ -14,6 +14,7 @@ import { createFilterInCellActionFactory, createFilterOutCellActionFactory } fro import { createAddToTimelineLensAction, createAddToTimelineCellActionFactory, + createAddToNewTimelineCellActionFactory, } from './add_to_timeline'; import { createShowTopNCellActionFactory } from './show_top_n'; import { @@ -52,6 +53,7 @@ const registerCellActions = ( filterIn: createFilterInCellActionFactory({ store, services }), filterOut: createFilterOutCellActionFactory({ store, services }), addToTimeline: createAddToTimelineCellActionFactory({ store, services }), + addToNewTimeline: createAddToNewTimelineCellActionFactory({ store, services }), showTopN: createShowTopNCellActionFactory({ store, history, services }), copyToClipboard: createCopyToClipboardCellActionFactory({ services }), toggleColumn: createToggleColumnCellActionFactory({ store }), @@ -65,13 +67,6 @@ const registerCellActions = ( 'copyToClipboard', ]); - registerCellActionsTrigger( - uiActions, - SecurityCellActionsTrigger.ALERTS_COUNT, - { addToTimeline: cellActions.addToTimeline }, - ['addToTimeline'] - ); - registerCellActionsTrigger(uiActions, SecurityCellActionsTrigger.DETAILS_FLYOUT, cellActions, [ 'filterIn', 'filterOut', @@ -80,6 +75,10 @@ const registerCellActions = ( 'showTopN', 'copyToClipboard', ]); + + registerCellActionsTrigger(uiActions, SecurityCellActionsTrigger.ALERTS_COUNT, cellActions, [ + 'addToNewTimeline', + ]); }; const registerCellActionsTrigger = ( diff --git a/x-pack/plugins/security_solution/public/actions/types.ts b/x-pack/plugins/security_solution/public/actions/types.ts index 9a68f2bd83922..589eb6c99774d 100644 --- a/x-pack/plugins/security_solution/public/actions/types.ts +++ b/x-pack/plugins/security_solution/public/actions/types.ts @@ -6,7 +6,12 @@ */ import type { CellAction, CellActionExecutionContext, CellActionFactory } from '@kbn/cell-actions'; -import type { Filter } from '../overview/components/detection_response/hooks/use_navigate_to_timeline'; +import type { QueryOperator } from '../../common/types'; +export interface AndFilter { + field: string; + value: string | string[]; + operator?: QueryOperator; +} export interface SecurityMetadata extends Record { /** @@ -31,7 +36,7 @@ export interface SecurityMetadata extends Record { * `metadata.andFilters` is used by the addToTimelineAction to add * an "and" query to the main data provider */ - andFilters?: Filter[]; + andFilters?: AndFilter[]; } export interface SecurityCellActionExecutionContext extends CellActionExecutionContext { @@ -39,20 +44,15 @@ export interface SecurityCellActionExecutionContext extends CellActionExecutionC } export type SecurityCellAction = CellAction; -// All security cell actions names -export type SecurityCellActionName = - | 'filterIn' - | 'filterOut' - | 'addToTimeline' - | 'showTopN' - | 'copyToClipboard' - | 'toggleColumn'; - export interface SecurityCellActions { filterIn?: CellActionFactory; filterOut?: CellActionFactory; addToTimeline?: CellActionFactory; + addToNewTimeline?: CellActionFactory; showTopN?: CellActionFactory; copyToClipboard?: CellActionFactory; toggleColumn?: CellActionFactory; } + +// All security cell actions names +export type SecurityCellActionName = keyof SecurityCellActions;