diff --git a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts index fb3ee79352ee8..3cb84713d7bb9 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules_custom.spec.ts @@ -18,13 +18,15 @@ import { DEFINITION_INDEX_PATTERNS, DEFINITION_TIMELINE, DEFINITION_STEP, + INVESTIGATION_NOTES_MARKDOWN, + INVESTIGATION_NOTES_TOGGLE, + RULE_ABOUT_DETAILS_HEADER_TOGGLE, RULE_NAME_HEADER, SCHEDULE_LOOPBACK, SCHEDULE_RUNS, SCHEDULE_STEP, ABOUT_RULE_DESCRIPTION, ABOUT_INVESTIGATION_NOTES, - INVESTIGATION_NOTES_TOGGLE, } from '../screens/rule_details'; import { CUSTOM_RULES_BTN, @@ -168,12 +170,12 @@ describe('Signal detection rules, custom', () => { .invoke('text') .should('eql', expectedTags); - cy.get(INVESTIGATION_NOTES_TOGGLE) - .eq(1) + cy.get(RULE_ABOUT_DETAILS_HEADER_TOGGLE) + .eq(INVESTIGATION_NOTES_TOGGLE) .click({ force: true }); cy.get(ABOUT_INVESTIGATION_NOTES) .invoke('text') - .should('eql', 'test markdown'); + .should('eql', INVESTIGATION_NOTES_MARKDOWN); cy.get(DEFINITION_INDEX_PATTERNS).then(patterns => { cy.wrap(patterns).each((pattern, index) => { diff --git a/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts b/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts index 4070cb945c097..db9866cdf7f63 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/create_new_rule.ts @@ -24,7 +24,8 @@ export const CUSTOM_QUERY_INPUT = '[data-test-subj="queryInput"]'; export const DEFINE_CONTINUE_BUTTON = '[data-test-subj="define-continue"]'; -export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]'; +export const INVESTIGATION_NOTES_TEXTAREA = + '[data-test-subj="detectionEngineStepAboutRuleNote"] textarea'; export const FALSE_POSITIVES_INPUT = '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] input'; @@ -53,11 +54,10 @@ export const RULE_DESCRIPTION_INPUT = export const RULE_NAME_INPUT = '[data-test-subj="detectionEngineStepAboutRuleName"] [data-test-subj="input"]'; +export const SCHEDULE_CONTINUE_BUTTON = '[data-test-subj="schedule-continue"]'; + export const SEVERITY_DROPDOWN = '[data-test-subj="detectionEngineStepAboutRuleSeverity"] [data-test-subj="select"]'; export const TAGS_INPUT = '[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxSearchInput"]'; - -export const INVESTIGATION_NOTES_TEXTAREA = - '[data-test-subj="detectionEngineStepAboutRuleNote"] textarea'; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts b/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts index 39f5c11e37d7a..ec57e142125da 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/rule_details.ts @@ -6,6 +6,8 @@ export const ABOUT_FALSE_POSITIVES = 3; +export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]'; + export const ABOUT_MITRE = 4; export const ABOUT_RULE_DESCRIPTION = '[data-test-subj=stepAboutRuleDetailsToggleDescriptionText]'; @@ -32,10 +34,16 @@ export const DEFINITION_INDEX_PATTERNS = export const DEFINITION_STEP = '[data-test-subj=definitionRule] [data-test-subj="listItemColumnStepRuleDescription"] .euiDescriptionList__description'; +export const INVESTIGATION_NOTES_MARKDOWN = 'test markdown'; + +export const INVESTIGATION_NOTES_TOGGLE = 1; + export const MACHINE_LEARNING_JOB_ID = '[data-test-subj="machineLearningJobId"]'; export const MACHINE_LEARNING_JOB_STATUS = '[data-test-subj="machineLearningJobStatus" ]'; +export const RULE_ABOUT_DETAILS_HEADER_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]'; + export const RULE_NAME_HEADER = '[data-test-subj="header-page-title"]'; export const RULE_TYPE = 0; @@ -45,7 +53,3 @@ export const SCHEDULE_STEP = '[data-test-subj="schedule"] .euiDescriptionList__ export const SCHEDULE_RUNS = 0; export const SCHEDULE_LOOPBACK = 1; - -export const INVESTIGATION_NOTES_TOGGLE = '[data-test-subj="stepAboutDetailsToggle"]'; - -export const ABOUT_INVESTIGATION_NOTES = '[data-test-subj="stepAboutDetailsNoteContent"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts b/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts index dc88381b60971..a20ad372a689c 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/create_new_rule.ts @@ -14,6 +14,7 @@ import { CUSTOM_QUERY_INPUT, DEFINE_CONTINUE_BUTTON, FALSE_POSITIVES_INPUT, + INVESTIGATION_NOTES_TEXTAREA, MACHINE_LEARNING_DROPDOWN, MACHINE_LEARNING_LIST, MACHINE_LEARNING_TYPE, @@ -28,7 +29,6 @@ import { SCHEDULE_CONTINUE_BUTTON, SEVERITY_DROPDOWN, TAGS_INPUT, - INVESTIGATION_NOTES_TEXTAREA, } from '../screens/create_new_rule'; export const createAndActivateRule = () => { diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts index f8e085c3f5221..78d59d10a8b04 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.test.ts @@ -4,23 +4,35 @@ * you may not use this file except in compliance with the Elastic License. */ import { cloneDeep, omit } from 'lodash/fp'; +import { Dispatch } from 'redux'; import { mockTimelineResults, mockTimelineResult, mockTimelineModel, } from '../../mock/timeline_results'; -import { TimelineResult } from '../../graphql/types'; import { timelineDefaults } from '../../store/timeline/defaults'; +import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../store/inputs/actions'; +import { + setKqlFilterQueryDraft as dispatchSetKqlFilterQueryDraft, + applyKqlFilterQuery as dispatchApplyKqlFilterQuery, + addTimeline as dispatchAddTimeline, +} from '../../store/timeline/actions'; +import { addNotes as dispatchAddNotes } from '../../store/app/actions'; import { defaultTimelineToTimelineModel, getNotesCount, getPinnedEventCount, isUntitled, omitTypenameInTimeline, - formatTimelineResultToModel, + dispatchUpdateTimeline, } from './helpers'; -import { OpenTimelineResult } from './types'; +import { OpenTimelineResult, DispatchUpdateTimeline } from './types'; +import { KueryFilterQueryKind } from '../../store/model'; + +jest.mock('../../store/inputs/actions'); +jest.mock('../../store/timeline/actions'); +jest.mock('../../store/app/actions'); describe('helpers', () => { let mockResults: OpenTimelineResult[]; @@ -650,31 +662,166 @@ describe('helpers', () => { }); }); - xdescribe('formatTimelineResultToModel', () => { - test('returns object with notes and timeline if timelineToOpen contains notes', () => { - const mockTimeline: TimelineResult = { - ...mockTimelineResult, - notes: [ - { - noteId: '123', - note: 'some note', + describe('dispatchUpdateTimeline', () => { + const dispatch = jest.fn() as Dispatch; + let timelineDispatch: DispatchUpdateTimeline; + + beforeEach(() => { + timelineDispatch = dispatchUpdateTimeline(dispatch); + }); + + test('it invokes date range picker dispatch', () => { + timelineDispatch({ + duplicate: true, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [], + timeline: mockTimelineModel, + })(); + + expect(dispatchSetTimelineRangeDatePicker).toHaveBeenCalledWith({ + from: 1585233356356, + to: 1585233716356, + }); + }); + + test('it invokes add timeline dispatch', () => { + timelineDispatch({ + duplicate: true, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [], + timeline: mockTimelineModel, + })(); + + expect(dispatchAddTimeline).toHaveBeenCalledWith({ + id: 'timeline-1', + timeline: mockTimelineModel, + }); + }); + + test('it does not invoke kql filter query dispatches if timeline.kqlQuery.filterQuery is null', () => { + timelineDispatch({ + duplicate: true, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [], + timeline: mockTimelineModel, + })(); + + expect(dispatchSetKqlFilterQueryDraft).not.toHaveBeenCalled(); + expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); + }); + + test('it does not invoke notes dispatch if duplicate is true', () => { + timelineDispatch({ + duplicate: true, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [], + timeline: mockTimelineModel, + })(); + + expect(dispatchAddNotes).not.toHaveBeenCalled(); + }); + + test('it does not invoke kql filter query dispatches if timeline.kqlQuery.kuery is null', () => { + const mockTimeline = { + ...mockTimelineModel, + kqlQuery: { + filterQuery: { + kuery: null, + serializedQuery: 'some-serialized-query', }, - ], + filterQueryDraft: null, + }, }; - const { notes, timeline } = formatTimelineResultToModel(mockTimeline, false); - - expect(notes).toEqual([{ note: 'some note', noteId: '123' }]); - expect(timeline).toEqual(mockTimelineModel); + timelineDispatch({ + duplicate: true, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [], + timeline: mockTimeline, + })(); + + expect(dispatchSetKqlFilterQueryDraft).not.toHaveBeenCalled(); + expect(dispatchApplyKqlFilterQuery).not.toHaveBeenCalled(); }); - test('returns object with notes as "undefined" and timeline of type TimelineModel if timelineToOpen contains notes', () => { - const { notes, ...mockTimeline }: TimelineResult = { - ...mockTimelineResult, + test('it invokes kql filter query dispatches if timeline.kqlQuery.filterQuery.kuery is not null', () => { + const mockTimeline = { + ...mockTimelineModel, + kqlQuery: { + filterQuery: { + kuery: { expression: 'expression', kind: 'kuery' as KueryFilterQueryKind }, + serializedQuery: 'some-serialized-query', + }, + filterQueryDraft: null, + }, }; - const { notes: resultingNotes, timeline } = formatTimelineResultToModel(mockTimeline, false); + timelineDispatch({ + duplicate: true, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [], + timeline: mockTimeline, + })(); + + expect(dispatchSetKqlFilterQueryDraft).toHaveBeenCalledWith({ + id: 'timeline-1', + filterQueryDraft: { + kind: 'kuery', + expression: 'expression', + }, + }); + expect(dispatchApplyKqlFilterQuery).toHaveBeenCalledWith({ + id: 'timeline-1', + filterQuery: { + kuery: { + kind: 'kuery', + expression: 'expression', + }, + serializedQuery: 'some-serialized-query', + }, + }); + }); + + test('it invokes note dispatch if duplicate is false', () => { + timelineDispatch({ + duplicate: false, + id: 'timeline-1', + from: 1585233356356, + to: 1585233716356, + notes: [ + { + created: 1585233356356, + updated: 1585233356356, + noteId: 'note-id', + note: 'I am a note', + }, + ], + timeline: mockTimelineModel, + })(); - expect(resultingNotes).toBeUndefined(); - expect(timeline).toEqual(mockTimelineModel); + expect(dispatchAddNotes).toHaveBeenCalledWith({ + notes: [ + { + created: new Date('2020-03-26T14:35:56.356Z'), + id: 'note-id', + lastEdit: new Date('2020-03-26T14:35:56.356Z'), + note: 'I am a note', + user: 'unknown', + saveObjectId: 'note-id', + version: undefined, + }, + ], + }); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts index 4f7d6cd64f1d9..8e67532379705 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts @@ -5,18 +5,23 @@ */ import ApolloClient from 'apollo-client'; -import { getOr, set } from 'lodash/fp'; +import { getOr, set, isEmpty } from 'lodash/fp'; import { Action } from 'typescript-fsa'; +import uuid from 'uuid'; import { Dispatch } from 'redux'; import { oneTimelineQuery } from '../../containers/timeline/one/index.gql_query'; import { TimelineResult, GetOneTimeline, NoteResult } from '../../graphql/types'; -import { addNotes as dispatchAddNotes } from '../../store/app/actions'; +import { + addNotes as dispatchAddNotes, + updateNote as dispatchUpdateNote, +} from '../../store/app/actions'; import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../store/inputs/actions'; import { setKqlFilterQueryDraft as dispatchSetKqlFilterQueryDraft, applyKqlFilterQuery as dispatchApplyKqlFilterQuery, addTimeline as dispatchAddTimeline, + addNote as dispatchAddGlobalTimelineNote, } from '../../store/timeline/actions'; import { ColumnHeaderOptions, TimelineModel } from '../../store/timeline/model'; @@ -32,6 +37,7 @@ import { import { OpenTimelineResult, UpdateTimeline, DispatchUpdateTimeline } from './types'; import { getTimeRangeSettings } from '../../utils/default_date_settings'; +import { createNote } from '../notes/helpers'; export const OPEN_TIMELINE_CLASS_NAME = 'open-timeline'; @@ -250,6 +256,7 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli notes, timeline, to, + ruleGuide, }: UpdateTimeline): (() => void) => () => { dispatch(dispatchSetTimelineRangeDatePicker({ from, to })); dispatch(dispatchAddTimeline({ id, timeline })); @@ -281,6 +288,14 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli }) ); } + + if (duplicate && ruleGuide && !isEmpty(ruleGuide)) { + const getNewNoteId = (): string => uuid.v4(); + const newNote = createNote({ newNote: ruleGuide, getNewNoteId }); + dispatch(dispatchUpdateNote({ note: newNote })); + dispatch(dispatchAddGlobalTimelineNote({ noteId: newNote.id, id })); + } + if (!duplicate) { dispatch( dispatchAddNotes({ diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts index 1265c056ec506..ed0f022d9c204 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts @@ -173,6 +173,7 @@ export interface UpdateTimeline { notes: NoteResult[] | null | undefined; timeline: TimelineModel; to: number; + ruleGuide?: string | undefined; } export type DispatchUpdateTimeline = ({ @@ -182,4 +183,5 @@ export type DispatchUpdateTimeline = ({ notes, timeline, to, + ruleGuide, }: UpdateTimeline) => () => void; diff --git a/x-pack/legacy/plugins/siem/public/mock/timeline_results.ts b/x-pack/legacy/plugins/siem/public/mock/timeline_results.ts index 3fe9ff3e02fd2..89b2d45e449d1 100644 --- a/x-pack/legacy/plugins/siem/public/mock/timeline_results.ts +++ b/x-pack/legacy/plugins/siem/public/mock/timeline_results.ts @@ -2139,7 +2139,7 @@ export const mockTimelineApolloResult = { stale: false, }; -export const defaultTimeline: CreateTimelineProps = { +export const defaultTimelineProps: CreateTimelineProps = { from: 1541444305937, timeline: { columns: [ @@ -2200,4 +2200,5 @@ export const defaultTimeline: CreateTimelineProps = { width: 1100, }, to: 1541444605937, + ruleGuide: '# this is some markdown documentation', }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx index a614a4ef970dd..f31d35166d313 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.test.tsx @@ -9,11 +9,11 @@ import moment from 'moment'; import { sendSignalToTimelineAction, determineToAndFrom } from './actions'; import { mockEcsDataWithSignal, - defaultTimeline, + defaultTimelineProps, apolloClient, mockTimelineApolloResult, } from '../../../../mock/'; -import { CreateTimeline, CreateTimelineNote, UpdateTimelineLoading } from './types'; +import { CreateTimeline, UpdateTimelineLoading } from './types'; import { Ecs } from '../../../../graphql/types'; jest.mock('apollo-client'); @@ -22,7 +22,6 @@ describe('signals actions', () => { const anchor = '2020-03-01T17:59:46.349Z'; const unix = moment(anchor).valueOf(); let createTimeline: CreateTimeline; - let createTimelineNote: CreateTimelineNote; let updateTimelineIsLoading: UpdateTimelineLoading; let clock: sinon.SinonFakeTimers; @@ -35,7 +34,6 @@ describe('signals actions', () => { jest.clearAllMocks(); createTimeline = jest.fn() as jest.Mocked; - createTimelineNote = jest.fn() as jest.Mocked; updateTimelineIsLoading = jest.fn() as jest.Mocked; jest.spyOn(apolloClient, 'query').mockResolvedValue(mockTimelineApolloResult); @@ -55,7 +53,6 @@ describe('signals actions', () => { createTimeline, ecsData: mockEcsDataWithSignal, updateTimelineIsLoading, - createTimelineNote, }); expect(updateTimelineIsLoading).toHaveBeenCalledTimes(1); @@ -68,7 +65,6 @@ describe('signals actions', () => { createTimeline, ecsData: mockEcsDataWithSignal, updateTimelineIsLoading, - createTimelineNote, }); const expected = { from: 1541444305937, @@ -223,47 +219,11 @@ describe('signals actions', () => { width: 1100, }, to: 1541444605937, + ruleGuide: '# this is some markdown documentation', }; - expect(createTimelineNote).toHaveBeenCalledTimes(1); expect(createTimeline).toHaveBeenCalledWith(expected); }); - test('it invokes createTimelineNote if signal.rule.note is not empty string', async () => { - await sendSignalToTimelineAction({ - apolloClient, - createTimeline, - ecsData: mockEcsDataWithSignal, - updateTimelineIsLoading, - createTimelineNote, - }); - - expect(createTimelineNote).toHaveBeenCalledTimes(1); - expect(createTimelineNote).toHaveBeenCalledWith({ - noteContent: '# this is some markdown documentation', - }); - }); - - test('it does not invoke createTimelineNote if signal.rule.note is empty string', async () => { - const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, - signal: { - rule: { - ...mockEcsDataWithSignal.signal?.rule!, - note: [''], - }, - }, - }; - - await sendSignalToTimelineAction({ - apolloClient, - createTimeline, - ecsData: ecsDataMock, - updateTimelineIsLoading, - createTimelineNote, - }); - - expect(createTimelineNote).not.toHaveBeenCalled(); - }); test('it invokes createTimeline with kqlQuery.filterQuery.kuery.kind as "kuery" if not specified in returned timeline template', async () => { const mockTimelineApolloResultModified = { @@ -286,7 +246,6 @@ describe('signals actions', () => { createTimeline, ecsData: mockEcsDataWithSignal, updateTimelineIsLoading, - createTimelineNote, }); // @ts-ignore const createTimelineArg = createTimeline.mock.calls[0][0]; @@ -316,7 +275,6 @@ describe('signals actions', () => { createTimeline, ecsData: mockEcsDataWithSignal, updateTimelineIsLoading, - createTimelineNote, }); // @ts-ignore const createTimelineArg = createTimeline.mock.calls[0][0]; @@ -335,7 +293,6 @@ describe('signals actions', () => { createTimeline, ecsData: mockEcsDataWithSignal, updateTimelineIsLoading, - createTimelineNote, }); expect(updateTimelineIsLoading).toHaveBeenCalledWith({ id: 'timeline-1', isLoading: true }); @@ -344,7 +301,7 @@ describe('signals actions', () => { isLoading: false, }); expect(createTimeline).toHaveBeenCalledTimes(1); - expect(createTimeline).toHaveBeenCalledWith(defaultTimeline); + expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps); }); }); @@ -365,64 +322,11 @@ describe('signals actions', () => { createTimeline, ecsData: ecsDataMock, updateTimelineIsLoading, - createTimelineNote, - }); - - expect(updateTimelineIsLoading).not.toHaveBeenCalled(); - expect(createTimeline).toHaveBeenCalledTimes(1); - expect(createTimeline).toHaveBeenCalledWith(defaultTimeline); - }); - - test('it invokes createTimelineNote if signal.rule.note is not empty string', async () => { - const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, - signal: { - rule: { - ...mockEcsDataWithSignal.signal?.rule!, - timeline_id: [''], - }, - }, - }; - - await sendSignalToTimelineAction({ - apolloClient, - createTimeline, - ecsData: ecsDataMock, - updateTimelineIsLoading, - createTimelineNote, - }); - - expect(updateTimelineIsLoading).not.toHaveBeenCalled(); - expect(createTimelineNote).toHaveBeenCalledTimes(1); - expect(createTimelineNote).toHaveBeenCalledWith({ - noteContent: '# this is some markdown documentation', - }); - }); - - test('it does not invoke createTimelineNote if signal.rule.note is empty string', async () => { - const ecsDataMock: Ecs = { - ...mockEcsDataWithSignal, - signal: { - rule: { - ...mockEcsDataWithSignal.signal?.rule!, - timeline_id: [''], - note: [''], - }, - }, - }; - - await sendSignalToTimelineAction({ - apolloClient, - createTimeline, - ecsData: ecsDataMock, - updateTimelineIsLoading, - createTimelineNote, }); expect(updateTimelineIsLoading).not.toHaveBeenCalled(); expect(createTimeline).toHaveBeenCalledTimes(1); - expect(createTimeline).toHaveBeenCalledWith(defaultTimeline); - expect(createTimelineNote).not.toHaveBeenCalled(); + expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps); }); }); @@ -442,12 +346,11 @@ describe('signals actions', () => { createTimeline, ecsData: ecsDataMock, updateTimelineIsLoading, - createTimelineNote, }); expect(updateTimelineIsLoading).not.toHaveBeenCalled(); expect(createTimeline).toHaveBeenCalledTimes(1); - expect(createTimeline).toHaveBeenCalledWith(defaultTimeline); + expect(createTimeline).toHaveBeenCalledWith(defaultTimelineProps); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx index ca7480d61a8a4..f52cf4fada157 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx @@ -92,11 +92,9 @@ export const sendSignalToTimelineAction = async ({ createTimeline, ecsData, updateTimelineIsLoading, - createTimelineNote, }: SendSignalToTimelineActionProps) => { let openSignalInBasicTimeline = true; const noteContent = ecsData.signal?.rule?.note != null ? ecsData.signal?.rule?.note[0] : ''; - const timelineId = ecsData.signal?.rule?.timeline_id != null ? ecsData.signal?.rule?.timeline_id[0] : ''; const { to, from } = determineToAndFrom({ ecsData }); @@ -156,6 +154,7 @@ export const sendSignalToTimelineAction = async ({ show: true, }, to, + ruleGuide: noteContent, }); } } catch { @@ -205,10 +204,7 @@ export const sendSignalToTimelineAction = async ({ }, }, to, + ruleGuide: noteContent, }); } - - if (noteContent.trim() !== '') { - createTimelineNote({ noteContent }); - } }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx index c4e4a74027869..6212cad7e1845 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.test.tsx @@ -14,7 +14,6 @@ import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps, - CreateTimelineNote, UpdateTimelineLoading, } from './types'; import { mockEcsDataWithSignal } from '../../../../mock/mock_ecs'; @@ -54,14 +53,12 @@ describe('signals default_config', () => { let setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; let createTimeline: CreateTimeline; let updateTimelineIsLoading: UpdateTimelineLoading; - let createTimelineNote: CreateTimelineNote; beforeEach(() => { setEventsLoading = jest.fn(); setEventsDeleted = jest.fn(); createTimeline = jest.fn(); updateTimelineIsLoading = jest.fn(); - createTimelineNote = jest.fn(); }); describe('timeline tooltip', () => { @@ -74,7 +71,6 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, - createTimelineNote, }); const timelineAction = signalsActions[0].getAction({ eventId: 'even-id', @@ -101,7 +97,6 @@ describe('signals default_config', () => { createTimeline, status: 'open', updateTimelineIsLoading, - createTimelineNote, }); signalOpenAction = signalsActions[1].getAction({ @@ -156,7 +151,6 @@ describe('signals default_config', () => { createTimeline, status: 'closed', updateTimelineIsLoading, - createTimelineNote, }); signalCloseAction = signalsActions[1].getAction({ diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx index d9ecf9af4e9ec..fd3b9a6f68e82 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx @@ -27,7 +27,6 @@ import { CreateTimeline, SetEventsDeletedProps, SetEventsLoadingProps, - CreateTimelineNote, UpdateTimelineLoading, } from './types'; @@ -196,7 +195,6 @@ export const getSignalsActions = ({ createTimeline, status, updateTimelineIsLoading, - createTimelineNote, }: { apolloClient?: ApolloClient<{}>; canUserCRUD: boolean; @@ -206,7 +204,6 @@ export const getSignalsActions = ({ createTimeline: CreateTimeline; status: 'open' | 'closed'; updateTimelineIsLoading: UpdateTimelineLoading; - createTimelineNote: CreateTimelineNote; }): TimelineAction[] => [ { getAction: ({ ecsData }: TimelineActionProps): JSX.Element => ( @@ -222,7 +219,6 @@ export const getSignalsActions = ({ createTimeline, ecsData, updateTimelineIsLoading, - createTimelineNote, }) } iconType="timeline" diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index 5d9021d98618a..f4bbd178ceca8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -9,7 +9,6 @@ import { isEmpty } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect, ConnectedProps } from 'react-redux'; import { Dispatch } from 'redux'; -import uuid from 'uuid'; import { Filter, esQuery } from '../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../containers/detection_engine/rules/fetch_index_patterns'; @@ -22,9 +21,6 @@ import { timelineActions, timelineSelectors } from '../../../../store/timeline'; import { TimelineModel } from '../../../../store/timeline/model'; import { timelineDefaults } from '../../../../store/timeline/defaults'; import { useApolloClient } from '../../../../utils/apollo_context'; -import { appActions } from '../../../../store/app'; -import { Note } from '../../../../lib/note'; -import { createNote } from '../../../../components/notes/helpers'; import { updateSignalStatusAction } from './actions'; import { @@ -48,7 +44,6 @@ import { SetEventsLoadingProps, UpdateSignalsStatusCallback, UpdateSignalsStatusProps, - CreateTimelineNoteProps, } from './types'; import { dispatchUpdateTimeline } from '../../../../components/open_timeline/helpers'; @@ -86,8 +81,6 @@ const SignalsTableComponent: React.FC = ({ to, updateTimeline, updateTimelineIsLoading, - associateNote, - updateNote, }) => { const [selectAll, setSelectAll] = useState(false); const apolloClient = useApolloClient(); @@ -121,7 +114,7 @@ const SignalsTableComponent: React.FC = ({ // Callback for creating a new timeline -- utilized by row/batch actions const createTimelineCallback = useCallback( - ({ from: fromTimeline, timeline, to: toTimeline }: CreateTimelineProps) => { + ({ from: fromTimeline, timeline, to: toTimeline, ruleGuide }: CreateTimelineProps) => { updateTimelineIsLoading({ id: 'timeline-1', isLoading: false }); updateTimeline({ duplicate: true, @@ -133,21 +126,12 @@ const SignalsTableComponent: React.FC = ({ show: true, }, to: toTimeline, + ruleGuide, })(); }, [updateTimeline, updateTimelineIsLoading] ); - const createTimelineNoteCallback = useCallback( - ({ noteContent }: CreateTimelineNoteProps) => { - const getNewNoteId = (): string => uuid.v4(); - const newNote = createNote({ newNote: noteContent, getNewNoteId }); - updateNote(newNote); - associateNote(newNote.id, 'timeline-1'); - }, - [updateNote, associateNote] - ); - const setEventsLoadingCallback = useCallback( ({ eventIds, isLoading }: SetEventsLoadingProps) => { setEventsLoading!({ id: SIGNALS_PAGE_TIMELINE_ID, eventIds, isLoading }); @@ -260,7 +244,6 @@ const SignalsTableComponent: React.FC = ({ setEventsDeleted: setEventsDeletedCallback, status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, updateTimelineIsLoading, - createTimelineNote: createTimelineNoteCallback, }), [ apolloClient, @@ -271,7 +254,6 @@ const SignalsTableComponent: React.FC = ({ setEventsLoadingCallback, setEventsDeletedCallback, updateTimelineIsLoading, - createTimelineNoteCallback, ] ); @@ -378,9 +360,6 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) => dispatch(timelineActions.updateIsLoading({ id, isLoading })), updateTimeline: dispatchUpdateTimeline(dispatch), - associateNote: (noteId: string, timelineId: string) => - dispatch(timelineActions.addNote({ id: timelineId, noteId })), - updateNote: (note: Note) => dispatch(appActions.updateNote({ note })), }); const connector = connect(makeMapStateToProps, mapDispatchToProps); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts index db5443dab3a6f..160904b83a85c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts @@ -46,21 +46,15 @@ export interface SendSignalToTimelineActionProps { createTimeline: CreateTimeline; ecsData: Ecs; updateTimelineIsLoading: UpdateTimelineLoading; - createTimelineNote: CreateTimelineNote; } -export interface CreateTimelineNoteProps { - noteContent: string; -} - -export type CreateTimelineNote = ({ noteContent }: CreateTimelineNoteProps) => void; - export type UpdateTimelineLoading = ({ id, isLoading }: { id: string; isLoading: boolean }) => void; export interface CreateTimelineProps { from: number; timeline: TimelineModel; to: number; + ruleGuide?: string; } export type CreateTimeline = ({ from, timeline, to }: CreateTimelineProps) => void;