From 37fc066ff6d220a6822ff91ebca18161034c1f85 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 6 Dec 2021 01:54:00 -0500 Subject: [PATCH 1/6] Query cmd_entry_leader for session leader and remove use of fake leader events --- .../common/types/process_tree/index.ts | 16 ++++++++++ .../public/components/ProcessTree/index.tsx | 3 ++ .../public/components/SessionView/hooks.ts | 29 ++++++------------- .../public/components/SessionView/index.tsx | 7 +++-- .../components/SessionViewPage/index.tsx | 2 +- .../public/hooks/use_process_tree.ts | 19 ++++++------ .../server/routes/process_events_route.ts | 27 +++++++++++++++-- 7 files changed, 69 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index 18fc08feea18c..d5817c5ba99de 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -15,6 +15,7 @@ export enum EventAction { exec = 'exec', exit = 'exit', output = 'output', + connect = 'connect', } export interface EventActionPartition { @@ -22,6 +23,7 @@ export interface EventActionPartition { exec: ProcessEvent[]; exit: ProcessEvent[]; output: ProcessEvent[]; + connect: ProcessEvent[]; } export interface User { @@ -29,6 +31,20 @@ export interface User { name: string; } +export interface EventResultBody { + hits: any[]; + total: number; +} + +export interface ProcessEventParse { + events: EventResultBody; + alerts: EventResultBody; +} + +export interface ProcessEventResults extends ProcessEventParse { + sessionLeader: ProcessEvent; +} + export interface ProcessFields { args: string[]; args_count: number; diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx index 382ecee2165aa..a18cabb66b12e 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx +++ b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx @@ -16,6 +16,7 @@ const HIDE_ORPHANS = true; interface ProcessTreeDeps { // process.entity_id to act as root node (typically a session (or entry session) leader). sessionEntityId: string; + sessionLeaderEvent: ProcessEvent | undefined; // bi-directional paging support. allows us to load // processes before and after a particular process.entity_id @@ -33,6 +34,7 @@ interface ProcessTreeDeps { export const ProcessTree = ({ sessionEntityId, + sessionLeaderEvent, forward, backward, searchQuery, @@ -43,6 +45,7 @@ export const ProcessTree = ({ const { sessionLeader, orphans, searchResults } = useProcessTree({ sessionEntityId, + sessionLeaderEvent, forward, backward, searchQuery, diff --git a/x-pack/plugins/session_view/public/components/SessionView/hooks.ts b/x-pack/plugins/session_view/public/components/SessionView/hooks.ts index 3ffbee15986f1..76d4375ed0caa 100644 --- a/x-pack/plugins/session_view/public/components/SessionView/hooks.ts +++ b/x-pack/plugins/session_view/public/components/SessionView/hooks.ts @@ -9,20 +9,13 @@ import { useQuery } from 'react-query'; import { EuiSearchBarOnChangeArgs } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { ProcessEvent } from '../../../common/types/process_tree'; +import { + ProcessEvent, + ProcessEventResults, + ProcessEventParse, +} from '../../../common/types/process_tree'; import { PROCESS_EVENTS_ROUTE } from '../../../common/constants'; -interface ProcessEventResults { - events: { - hits: any[]; - total: number; - }; - alerts: { - hits: any[]; - total: number; - }; -} - export const useFetchSessionViewProcessEvents = (sessionEntityId: string) => { const { http } = useKibana().services; @@ -35,7 +28,7 @@ export const useFetchSessionViewProcessEvents = (sessionEntityId: string) => { ); }; -export const useParseSessionViewProcessEvents = (getData: ProcessEventResults | undefined) => { +export const useParseSessionViewProcessEvents = (getData: ProcessEventParse | undefined) => { const [data, setData] = useState([]); const sortEvents = (a: ProcessEvent, b: ProcessEvent) => { @@ -49,19 +42,15 @@ export const useParseSessionViewProcessEvents = (getData: ProcessEventResults | }; useEffect(() => { - if (!getData) { - return; - } - - const events: ProcessEvent[] = (getData.events?.hits || []).map( + const events: ProcessEvent[] = (getData?.events?.hits || []).map( (event: any) => event._source as ProcessEvent ); - const alerts: ProcessEvent[] = (getData.alerts?.hits || []).map((event: any) => { + const alerts: ProcessEvent[] = (getData?.alerts?.hits || []).map((event: any) => { return event._source as ProcessEvent; }); const all: ProcessEvent[] = events.concat(alerts).sort(sortEvents); setData(all); - }, [getData]); + }, [getData?.events, getData?.alerts]); return { data, diff --git a/x-pack/plugins/session_view/public/components/SessionView/index.tsx b/x-pack/plugins/session_view/public/components/SessionView/index.tsx index db61b22111c99..6cade393ee755 100644 --- a/x-pack/plugins/session_view/public/components/SessionView/index.tsx +++ b/x-pack/plugins/session_view/public/components/SessionView/index.tsx @@ -16,7 +16,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { SectionLoading } from '../../shared_imports'; import { ProcessTree } from '../ProcessTree'; -import { Process } from '../../../common/types/process_tree'; +import { Process, ProcessEventResults } from '../../../common/types/process_tree'; import { SessionViewDetailPanel } from '../SessionViewDetailPanel'; import { useStyles } from './styles'; import { @@ -54,7 +54,9 @@ export const SessionView = ({ sessionEntityId, height }: SessionViewDeps) => { const { onSearch, searchQuery } = useSearchQuery(); const { isLoading, isError, data: getData } = useFetchSessionViewProcessEvents(sessionEntityId); - const { data } = useParseSessionViewProcessEvents(getData); + const { data } = useParseSessionViewProcessEvents( + getData ? { alerts: getData.alerts, events: getData.events } : ({} as ProcessEventResults) + ); const renderNoData = () => { return ( @@ -92,6 +94,7 @@ export const SessionView = ({ sessionEntityId, height }: SessionViewDeps) => {
{ const { data } = useQuery(['recent-session', 'recent_session'], () => http.get(RECENT_SESSION_ROUTE, { query: { - indexes: ['cmd*', '.siem-signals*'], + indexes: ['cmd_entry_leader*', '.siem-signals*'], }, }) ); diff --git a/x-pack/plugins/session_view/public/hooks/use_process_tree.ts b/x-pack/plugins/session_view/public/hooks/use_process_tree.ts index becd2804d6252..4f2f9a026fdf9 100644 --- a/x-pack/plugins/session_view/public/hooks/use_process_tree.ts +++ b/x-pack/plugins/session_view/public/hooks/use_process_tree.ts @@ -16,6 +16,7 @@ import { interface UseProcessTreeDeps { sessionEntityId: string; + sessionLeaderEvent: ProcessEvent | undefined; forward: ProcessEvent[]; backward?: ProcessEvent[]; searchQuery?: string; @@ -82,7 +83,7 @@ class ProcessImpl implements Process { return eventsPartition.fork[eventsPartition.fork.length - 1]; } - return {} as ProcessEvent; + return this.events[this.events.length - 1] || ({} as ProcessEvent); } getOutput() { @@ -110,20 +111,20 @@ class ProcessImpl implements Process { export const useProcessTree = ({ sessionEntityId, + sessionLeaderEvent, forward, backward, searchQuery, }: UseProcessTreeDeps) => { - // initialize map, as well as a placeholder for session leader process - // we add a fake session leader event, sourced from wide event data. - // this is because we might not always have a session leader event - // especially if we are paging in reverse from deep within a large session - const fakeLeaderEvent = forward.find((event) => event.event.kind === EventKind.event); + // initialize map const sessionLeaderProcess = new ProcessImpl(sessionEntityId); - if (fakeLeaderEvent) { - fakeLeaderEvent.process = { ...fakeLeaderEvent.process, ...fakeLeaderEvent.process.entry }; - sessionLeaderProcess.events.push(fakeLeaderEvent); + if (sessionLeaderEvent) { + sessionLeaderEvent.process = { + ...sessionLeaderEvent.process, + ...sessionLeaderEvent.process.entry, + }; + sessionLeaderProcess.events.push(sessionLeaderEvent); } const initializedProcessMap: ProcessMap = { diff --git a/x-pack/plugins/session_view/server/routes/process_events_route.ts b/x-pack/plugins/session_view/server/routes/process_events_route.ts index 10c0954f6ad07..7775cac41bb77 100644 --- a/x-pack/plugins/session_view/server/routes/process_events_route.ts +++ b/x-pack/plugins/session_view/server/routes/process_events_route.ts @@ -30,12 +30,34 @@ export const registerProcessEventsRoute = (router: IRouter) => { const { sessionEntityId } = request.query; + const sessionLeader = await client.search({ + index: ['cmd_entry_leader'], + body: { + query: { + match: { + 'process.entity_id': sessionEntityId, + }, + }, + size: 1, + sort: [{ '@timestamp': 'asc' }], + }, + }); + const search = await client.search({ index: ['cmd'], body: { query: { - match: { - 'process.entry.entity_id': sessionEntityId, + bool: { + must_not: { + match: { + 'process.entity_id': sessionEntityId, + }, + }, + must: { + match: { + 'process.entry.entity_id': sessionEntityId, + }, + }, }, }, size: PROCESS_EVENTS_PER_PAGE, @@ -65,6 +87,7 @@ export const registerProcessEventsRoute = (router: IRouter) => { body: { events: search.body.hits, alerts: alerts.body.hits, + sessionLeader: sessionLeader.body.hits.hits[0]?._source, }, }); } From 9f391696fd9b439257eb77b644be7e3a54693a19 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 6 Dec 2021 15:02:58 -0500 Subject: [PATCH 2/6] Move process tree hooks under the component, break functions into individual helpers with unit tests --- .../mocks/constants/process_tree.mock.ts | 6 + .../constants/session_view_process.mock.ts | 73 ++++++++++-- .../session_view_process_events.mock.ts | 43 +++++++ .../common/types/process_tree/index.ts | 4 + .../components/ProcessTree/helpers.test.ts | 76 +++++++++++++ .../public/components/ProcessTree/helpers.ts | 94 ++++++++++++++++ .../ProcessTree/hooks.ts} | 105 +++--------------- .../public/components/ProcessTree/index.tsx | 4 +- 8 files changed, 304 insertions(+), 101 deletions(-) create mode 100644 x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts create mode 100644 x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts create mode 100644 x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts rename x-pack/plugins/session_view/public/{hooks/use_process_tree.ts => components/ProcessTree/hooks.ts} (61%) diff --git a/x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts b/x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts b/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts index f99f9f1bfabbc..107d220816ea4 100644 --- a/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts +++ b/x-pack/plugins/session_view/common/mocks/constants/session_view_process.mock.ts @@ -5,9 +5,17 @@ * 2.0. */ -import { Process, ProcessEvent, EventAction, EventKind } from '../../types/process_tree'; +import { + Process, + ProcessEvent, + ProcessFields, + EventAction, + EventKind, + ProcessMap, + User, +} from '../../types/process_tree'; -const mockEvents = [ +export const mockEvents = [ { '@timestamp': new Date('2021-11-23T15:25:04.210Z'), process: { @@ -91,7 +99,7 @@ const mockEvents = [ }, executable: '/usr/bin/vi', interactive: true, - entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3726', + entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3727', parent: { pid: 2442, pgid: 2442, @@ -163,7 +171,7 @@ const mockEvents = [ }, executable: '/usr/bin/vi', interactive: true, - entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3726', + entity_id: '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3728', parent: { pid: 2442, pgid: 2442, @@ -226,7 +234,7 @@ const mockEvents = [ }, ]; -const mockAlerts = [ +export const mockAlerts = [ { kibana: { alert: { @@ -419,8 +427,8 @@ const mockAlerts = [ }, ]; -const processMock: Process = { - id: '3f44d854-fe8d-5666-abc9-9efe7f970b4b', +export const processMock: Process = { + id: '3d0192c6-7c54-5ee6-a110-3539a7cf42bc', events: [], children: [], autoExpand: false, @@ -431,7 +439,31 @@ const processMock: Process = { getAlerts: () => [], hasExec: () => false, getOutput: () => '', - getDetails: () => ({} as ProcessEvent), + getDetails: () => + ({ + '@timestamp': new Date('2021-11-23T15:25:04.210Z'), + event: { + kind: EventKind.event, + category: 'process', + action: EventAction.exec, + }, + process: { + args: [], + args_count: 0, + command_line: '', + entity_id: '3d0192c6-7c54-5ee6-a110-3539a7cf42bc', + executable: '', + interactive: false, + name: '', + working_directory: '/home/vagrant', + pid: 1, + pgid: 1, + user: {} as User, + parent: {} as ProcessFields, + session: {} as ProcessFields, + entry: {} as ProcessFields, + }, + } as ProcessEvent), isUserEntered: () => false, getMaxAlertLevel: () => null, }; @@ -451,3 +483,28 @@ export const sessionViewAlertProcessMock: Process = { hasExec: () => true, isUserEntered: () => true, }; + +export const mockProcessMap = mockEvents.reduce( + (processMap, event) => { + processMap[event.process.entity_id] = { + id: event.process.entity_id, + events: [event], + children: [], + parent: undefined, + autoExpand: false, + searchMatched: null, + hasOutput: () => false, + hasAlerts: () => false, + getAlerts: () => [], + hasExec: () => false, + getOutput: () => '', + getDetails: () => event, + isUserEntered: () => false, + getMaxAlertLevel: () => null, + }; + return processMap; + }, + { + [sessionViewBasicProcessMock.id]: sessionViewBasicProcessMock, + } as ProcessMap +); diff --git a/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts b/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts index 7c3330117f338..bf555a9bd2f27 100644 --- a/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts +++ b/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts @@ -1913,4 +1913,47 @@ export const sessionViewProcessEventsMock = () => ({ }, ], }, + sessionLeader: { + '@timestamp': '2021-11-23T13:40:16.528Z', + process: { + pid: 51744, + pgid: 51744, + user: { name: 'vagrant', id: 1000 }, + executable: '/usr/bin/cat', + interactive: true, + entity_id: 'bb5efe42-54a6-597c-bd0e-33a1a3fc372e', + parent: { + pid: 51547, + pgid: 51547, + user: { name: 'vagrant', id: 1000 }, + executable: '/bin/bash', + interactive: true, + entity_id: 'ae06c110-ad2d-5830-b47c-08ad62f1734c', + }, + session: { + pid: 51547, + pgid: 51547, + user: { name: 'vagrant', id: 1000 }, + executable: '/bin/bash', + interactive: true, + entity_id: 'ae06c110-ad2d-5830-b47c-08ad62f1734c', + }, + entry: { + pid: 51547, + pgid: 51547, + user: { name: 'vagrant', id: 1000 }, + executable: '/bin/bash', + interactive: true, + entity_id: 'ae06c110-ad2d-5830-b47c-08ad62f1734c', + start: '2021-11-23T13:40:07.183Z', + }, + args_count: 2, + args: ['cat', 'EventConverter/src/main.ts'], + working_directory: '/home/vagrant', + }, + event: { action: 'exec', category: 'process', kind: 'event' }, + network: { application: 'ssh' }, + source: { ip: '10.0.2.2' }, + client: { ip: '10.0.2.2' }, + }, }); diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index d5817c5ba99de..5a254336c6d4a 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -141,3 +141,7 @@ export interface Process { isUserEntered(): boolean; getMaxAlertLevel(): number | null; } + +export type ProcessMap = { + [key: string]: Process; +}; diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts new file mode 100644 index 0000000000000..94b60ee0b6e3a --- /dev/null +++ b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION } from 'constants'; +import { + mockEvents, + mockProcessMap, + sessionViewBasicProcessMock, +} from '../../../common/mocks/constants/session_view_process.mock'; +import { Process, ProcessEvent, ProcessMap } from '../../../common/types/process_tree'; +import { + updateProcessMap, + buildProcessTree, + searchProcessTree, + autoExpandProcessTree, +} from './helpers'; + +const SESSION_ENTITY_ID = '3d0192c6-7c54-5ee6-a110-3539a7cf42bc'; +const SEARCH_QUERY = 'vi'; +const SEARCH_RESULT_PROCESS_ID = '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3727'; + +describe('process tree hook helpers tests', () => { + let processMap: ProcessMap; + + beforeEach(() => { + processMap = {}; + }); + + it('updateProcessMap works', () => { + updateProcessMap(processMap, mockEvents); + + // processes are added to processMap + mockEvents.forEach((event) => { + expect(processMap[event.process.entity_id]).toBeTruthy(); + }); + }); + + it('buildProcessTree works', () => { + processMap = mockProcessMap; + const orphans: Process[] = []; + buildProcessTree(processMap, mockEvents, orphans, SESSION_ENTITY_ID); + + const sessionLeaderChildrenIds = new Set( + processMap[SESSION_ENTITY_ID].children.map((child) => child.id) + ); + + // processes are added under their parent's childrean array in processMap + mockEvents.forEach((event) => { + expect(sessionLeaderChildrenIds.has(event.process.entity_id)); + }); + }); + + it('searchProcessTree works', () => { + const searchResults = searchProcessTree(mockProcessMap, SEARCH_QUERY); + + // search returns the process with search query in its event args + expect(searchResults[0].id).toBe(SEARCH_RESULT_PROCESS_ID); + }); + + it('autoExpandProcessTree works', () => { + processMap = mockProcessMap; + // mock what buildProcessTree does + const childProcesses = Object.values(processMap).filter( + (process) => process.id !== SESSION_ENTITY_ID + ); + processMap[SESSION_ENTITY_ID].children = childProcesses; + + expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeFalsy(); + autoExpandProcessTree(processMap); + // session leader should have autoExpand to be true + expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeTruthy(); + }); +}); diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts new file mode 100644 index 0000000000000..7343a9a978957 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts @@ -0,0 +1,94 @@ +/* + * 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 { Process, ProcessEvent, ProcessMap } from '../../../common/types/process_tree'; +import { ProcessImpl } from './hooks'; + +export const updateProcessMap = (processMap: ProcessMap, events: ProcessEvent[]) => { + events.forEach((event) => { + const { entity_id: id } = event.process; + let process = processMap[id]; + + if (!process) { + process = new ProcessImpl(id); + processMap[id] = process; + } + + process.events.push(event); + }); +}; + +export const buildProcessTree = ( + processMap: ProcessMap, + events: ProcessEvent[], + orphans: Process[], + sessionEntityId: string, + backwardDirection: boolean = false +) => { + events.forEach((event) => { + const process = processMap[event.process.entity_id]; + const parentProcess = processMap[event.process.parent?.entity_id]; + + if (parentProcess) { + process.parent = parentProcess; // handy for recursive operations (like auto expand) + + if (!parentProcess.children.includes(process) && parentProcess.id !== process.id) { + if (backwardDirection) { + parentProcess.children.unshift(process); + } else { + parentProcess.children.push(process); + } + } + } else if (process.id !== sessionEntityId && !orphans.includes(process)) { + // if no parent process, process is probably orphaned + orphans.push(process); + } + }); +}; + +export const searchProcessTree = (processMap: ProcessMap, searchQuery: string | undefined) => { + const results = []; + + if (searchQuery) { + for (const processId of Object.keys(processMap)) { + const process = processMap[processId]; + const event = process.getDetails(); + const { working_directory: workingDirectory, args } = event.process; + + // TODO: the text we search is the same as what we render. + // should this be customizable?? + const text = `${workingDirectory} ${args.join(' ')}`; + + process.searchMatched = text.includes(searchQuery) ? searchQuery : null; + + if (process.searchMatched) { + results.push(process); + } + } + } else { + for (const processId of Object.keys(processMap)) { + processMap[processId].searchMatched = null; + processMap[processId].autoExpand = false; + } + } + + return results; +}; + +export const autoExpandProcessTree = (processMap: ProcessMap) => { + for (const processId of Object.keys(processMap)) { + const process = processMap[processId]; + + if (process.searchMatched || process.isUserEntered()) { + let { parent } = process; + + while (parent && parent.id !== parent.parent?.id) { + parent.autoExpand = true; + parent = parent.parent; + } + } + } +}; diff --git a/x-pack/plugins/session_view/public/hooks/use_process_tree.ts b/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts similarity index 61% rename from x-pack/plugins/session_view/public/hooks/use_process_tree.ts rename to x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts index 4f2f9a026fdf9..619397c61b718 100644 --- a/x-pack/plugins/session_view/public/hooks/use_process_tree.ts +++ b/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts @@ -12,7 +12,14 @@ import { EventActionPartition, Process, ProcessEvent, -} from '../../common/types/process_tree'; + ProcessMap, +} from '../../../common/types/process_tree'; +import { + updateProcessMap, + buildProcessTree, + searchProcessTree, + autoExpandProcessTree, +} from './helpers'; interface UseProcessTreeDeps { sessionEntityId: string; @@ -22,11 +29,7 @@ interface UseProcessTreeDeps { searchQuery?: string; } -type ProcessMap = { - [key: string]: Process; -}; - -class ProcessImpl implements Process { +export class ProcessImpl implements Process { id: string; events: ProcessEvent[]; children: Process[]; @@ -137,86 +140,6 @@ export const useProcessTree = ({ const [searchResults, setSearchResults] = useState([]); const [orphans, setOrphans] = useState([]); - const updateProcessMap = (events: ProcessEvent[]) => { - events.forEach((event) => { - const { entity_id: id } = event.process; - let process = processMap[id]; - - if (!process) { - process = new ProcessImpl(id); - processMap[id] = process; - } - - process.events.push(event); - }); - }; - - const buildProcessTree = (events: ProcessEvent[], backwardDirection: boolean = false) => { - events.forEach((event) => { - const process = processMap[event.process.entity_id]; - const parentProcess = processMap[event.process.parent?.entity_id]; - - if (parentProcess) { - process.parent = parentProcess; // handy for recursive operations (like auto expand) - - if (!parentProcess.children.includes(process) && parentProcess.id !== process.id) { - if (backwardDirection) { - parentProcess.children.unshift(process); - } else { - parentProcess.children.push(process); - } - } - } else if (process.id !== sessionEntityId && !orphans.includes(process)) { - // if no parent process, process is probably orphaned - orphans.push(process); - } - }); - }; - - const searchProcessTree = () => { - const results = []; - - if (searchQuery) { - for (const processId of Object.keys(processMap)) { - const process = processMap[processId]; - const event = process.getDetails(); - const { working_directory: workingDirectory, args } = event.process; - - // TODO: the text we search is the same as what we render. - // should this be customizable?? - const text = `${workingDirectory} ${args.join(' ')}`; - - process.searchMatched = text.includes(searchQuery) ? searchQuery : null; - - if (process.searchMatched) { - results.push(process); - } - } - } else { - for (const processId of Object.keys(processMap)) { - processMap[processId].searchMatched = null; - processMap[processId].autoExpand = false; - } - } - - setSearchResults(results); - }; - - const autoExpandProcessTree = () => { - for (const processId of Object.keys(processMap)) { - const process = processMap[processId]; - - if (process.searchMatched || process.isUserEntered()) { - let { parent } = process; - - while (parent && parent.id !== parent.parent?.id) { - parent.autoExpand = true; - parent = parent.parent; - } - } - } - }; - const processNewEvents = ( events: ProcessEvent[] | undefined, backwardDirection: boolean = false @@ -225,9 +148,9 @@ export const useProcessTree = ({ return; } - updateProcessMap(events); - buildProcessTree(events, backwardDirection); - autoExpandProcessTree(); + updateProcessMap(processMap, events); + buildProcessTree(processMap, events, orphans, sessionEntityId, backwardDirection); + autoExpandProcessTree(processMap); }; useEffect(() => { @@ -245,8 +168,8 @@ export const useProcessTree = ({ }, [forward, backward]); useEffect(() => { - searchProcessTree(); - autoExpandProcessTree(); + setSearchResults(searchProcessTree(processMap, searchQuery)); + autoExpandProcessTree(processMap); // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchQuery]); diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx index a18cabb66b12e..f106c0bdc1055 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx +++ b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx @@ -6,7 +6,7 @@ */ import React, { useRef, useLayoutEffect, useCallback } from 'react'; import { ProcessTreeNode } from '../ProcessTreeNode'; -import { useProcessTree } from '../../hooks/use_process_tree'; +import { useProcessTree } from './hooks'; import { ProcessEvent, Process } from '../../../common/types/process_tree'; import { useScroll } from '../../hooks/use_scroll'; import { useStyles } from './styles'; @@ -20,7 +20,7 @@ interface ProcessTreeDeps { // bi-directional paging support. allows us to load // processes before and after a particular process.entity_id - // implementation in-complete. see use_process_tree.js + // implementation in-complete. see hooks.js forward: ProcessEvent[]; // load next backward?: ProcessEvent[]; // load previous From 337efe05098bc232f142345cd3e5e3c563663b55 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 6 Dec 2021 15:08:35 -0500 Subject: [PATCH 3/6] Remoe unused file --- .../common/mocks/constants/process_tree.mock.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts diff --git a/x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts b/x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts deleted file mode 100644 index 1fec1c76430eb..0000000000000 --- a/x-pack/plugins/session_view/common/mocks/constants/process_tree.mock.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* - * 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. - */ From 40e4795f1981c3e6ef2fd2e7fc30728da44681d8 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 6 Dec 2021 17:53:25 -0500 Subject: [PATCH 4/6] Revert fetching for session leader from cmd_entry_leaders --- .../common/types/process_tree/index.ts | 8 +----- .../public/components/ProcessTree/hooks.ts | 17 ++++++------ .../public/components/ProcessTree/index.tsx | 3 --- .../public/components/SessionView/hooks.ts | 17 +++++------- .../public/components/SessionView/index.tsx | 7 ++--- .../components/SessionViewPage/index.tsx | 2 +- .../server/routes/process_events_route.ts | 27 ++----------------- 7 files changed, 21 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/session_view/common/types/process_tree/index.ts b/x-pack/plugins/session_view/common/types/process_tree/index.ts index 5a254336c6d4a..0189751a49c2f 100644 --- a/x-pack/plugins/session_view/common/types/process_tree/index.ts +++ b/x-pack/plugins/session_view/common/types/process_tree/index.ts @@ -15,7 +15,6 @@ export enum EventAction { exec = 'exec', exit = 'exit', output = 'output', - connect = 'connect', } export interface EventActionPartition { @@ -23,7 +22,6 @@ export interface EventActionPartition { exec: ProcessEvent[]; exit: ProcessEvent[]; output: ProcessEvent[]; - connect: ProcessEvent[]; } export interface User { @@ -36,15 +34,11 @@ export interface EventResultBody { total: number; } -export interface ProcessEventParse { +export interface ProcessEventResults { events: EventResultBody; alerts: EventResultBody; } -export interface ProcessEventResults extends ProcessEventParse { - sessionLeader: ProcessEvent; -} - export interface ProcessFields { args: string[]; args_count: number; diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts b/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts index 619397c61b718..51e4dd05354c7 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts @@ -23,7 +23,6 @@ import { interface UseProcessTreeDeps { sessionEntityId: string; - sessionLeaderEvent: ProcessEvent | undefined; forward: ProcessEvent[]; backward?: ProcessEvent[]; searchQuery?: string; @@ -114,20 +113,20 @@ export class ProcessImpl implements Process { export const useProcessTree = ({ sessionEntityId, - sessionLeaderEvent, forward, backward, searchQuery, }: UseProcessTreeDeps) => { - // initialize map + // initialize map, as well as a placeholder for session leader process + // we add a fake session leader event, sourced from wide event data. + // this is because we might not always have a session leader event + // especially if we are paging in reverse from deep within a large session + const fakeLeaderEvent = forward.find((event) => event.event.kind === EventKind.event); const sessionLeaderProcess = new ProcessImpl(sessionEntityId); - if (sessionLeaderEvent) { - sessionLeaderEvent.process = { - ...sessionLeaderEvent.process, - ...sessionLeaderEvent.process.entry, - }; - sessionLeaderProcess.events.push(sessionLeaderEvent); + if (fakeLeaderEvent) { + fakeLeaderEvent.process = { ...fakeLeaderEvent.process, ...fakeLeaderEvent.process.entry }; + sessionLeaderProcess.events.push(fakeLeaderEvent); } const initializedProcessMap: ProcessMap = { diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx index f106c0bdc1055..22a5a66245cbc 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx +++ b/x-pack/plugins/session_view/public/components/ProcessTree/index.tsx @@ -16,7 +16,6 @@ const HIDE_ORPHANS = true; interface ProcessTreeDeps { // process.entity_id to act as root node (typically a session (or entry session) leader). sessionEntityId: string; - sessionLeaderEvent: ProcessEvent | undefined; // bi-directional paging support. allows us to load // processes before and after a particular process.entity_id @@ -34,7 +33,6 @@ interface ProcessTreeDeps { export const ProcessTree = ({ sessionEntityId, - sessionLeaderEvent, forward, backward, searchQuery, @@ -45,7 +43,6 @@ export const ProcessTree = ({ const { sessionLeader, orphans, searchResults } = useProcessTree({ sessionEntityId, - sessionLeaderEvent, forward, backward, searchQuery, diff --git a/x-pack/plugins/session_view/public/components/SessionView/hooks.ts b/x-pack/plugins/session_view/public/components/SessionView/hooks.ts index 76d4375ed0caa..de748ed0fdc20 100644 --- a/x-pack/plugins/session_view/public/components/SessionView/hooks.ts +++ b/x-pack/plugins/session_view/public/components/SessionView/hooks.ts @@ -9,11 +9,7 @@ import { useQuery } from 'react-query'; import { EuiSearchBarOnChangeArgs } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { - ProcessEvent, - ProcessEventResults, - ProcessEventParse, -} from '../../../common/types/process_tree'; +import { ProcessEvent, ProcessEventResults } from '../../../common/types/process_tree'; import { PROCESS_EVENTS_ROUTE } from '../../../common/constants'; export const useFetchSessionViewProcessEvents = (sessionEntityId: string) => { @@ -28,8 +24,9 @@ export const useFetchSessionViewProcessEvents = (sessionEntityId: string) => { ); }; -export const useParseSessionViewProcessEvents = (getData: ProcessEventParse | undefined) => { +export const useParseSessionViewProcessEvents = (getData: ProcessEventResults | undefined) => { const [data, setData] = useState([]); + const { events, alerts } = getData || {}; const sortEvents = (a: ProcessEvent, b: ProcessEvent) => { if (a['@timestamp'].valueOf() < b['@timestamp'].valueOf()) { @@ -42,15 +39,15 @@ export const useParseSessionViewProcessEvents = (getData: ProcessEventParse | un }; useEffect(() => { - const events: ProcessEvent[] = (getData?.events?.hits || []).map( + const eventsSource: ProcessEvent[] = (events?.hits || []).map( (event: any) => event._source as ProcessEvent ); - const alerts: ProcessEvent[] = (getData?.alerts?.hits || []).map((event: any) => { + const alertsSource: ProcessEvent[] = (alerts?.hits || []).map((event: any) => { return event._source as ProcessEvent; }); - const all: ProcessEvent[] = events.concat(alerts).sort(sortEvents); + const all: ProcessEvent[] = eventsSource.concat(alertsSource).sort(sortEvents); setData(all); - }, [getData?.events, getData?.alerts]); + }, [events, alerts]); return { data, diff --git a/x-pack/plugins/session_view/public/components/SessionView/index.tsx b/x-pack/plugins/session_view/public/components/SessionView/index.tsx index 6cade393ee755..db61b22111c99 100644 --- a/x-pack/plugins/session_view/public/components/SessionView/index.tsx +++ b/x-pack/plugins/session_view/public/components/SessionView/index.tsx @@ -16,7 +16,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { SectionLoading } from '../../shared_imports'; import { ProcessTree } from '../ProcessTree'; -import { Process, ProcessEventResults } from '../../../common/types/process_tree'; +import { Process } from '../../../common/types/process_tree'; import { SessionViewDetailPanel } from '../SessionViewDetailPanel'; import { useStyles } from './styles'; import { @@ -54,9 +54,7 @@ export const SessionView = ({ sessionEntityId, height }: SessionViewDeps) => { const { onSearch, searchQuery } = useSearchQuery(); const { isLoading, isError, data: getData } = useFetchSessionViewProcessEvents(sessionEntityId); - const { data } = useParseSessionViewProcessEvents( - getData ? { alerts: getData.alerts, events: getData.events } : ({} as ProcessEventResults) - ); + const { data } = useParseSessionViewProcessEvents(getData); const renderNoData = () => { return ( @@ -94,7 +92,6 @@ export const SessionView = ({ sessionEntityId, height }: SessionViewDeps) => {
{ const { data } = useQuery(['recent-session', 'recent_session'], () => http.get(RECENT_SESSION_ROUTE, { query: { - indexes: ['cmd_entry_leader*', '.siem-signals*'], + indexes: ['cmd*', '.siem-signals*'], }, }) ); diff --git a/x-pack/plugins/session_view/server/routes/process_events_route.ts b/x-pack/plugins/session_view/server/routes/process_events_route.ts index 7775cac41bb77..10c0954f6ad07 100644 --- a/x-pack/plugins/session_view/server/routes/process_events_route.ts +++ b/x-pack/plugins/session_view/server/routes/process_events_route.ts @@ -30,34 +30,12 @@ export const registerProcessEventsRoute = (router: IRouter) => { const { sessionEntityId } = request.query; - const sessionLeader = await client.search({ - index: ['cmd_entry_leader'], - body: { - query: { - match: { - 'process.entity_id': sessionEntityId, - }, - }, - size: 1, - sort: [{ '@timestamp': 'asc' }], - }, - }); - const search = await client.search({ index: ['cmd'], body: { query: { - bool: { - must_not: { - match: { - 'process.entity_id': sessionEntityId, - }, - }, - must: { - match: { - 'process.entry.entity_id': sessionEntityId, - }, - }, + match: { + 'process.entry.entity_id': sessionEntityId, }, }, size: PROCESS_EVENTS_PER_PAGE, @@ -87,7 +65,6 @@ export const registerProcessEventsRoute = (router: IRouter) => { body: { events: search.body.hits, alerts: alerts.body.hits, - sessionLeader: sessionLeader.body.hits.hits[0]?._source, }, }); } From db18b638616d4025aa77341f2ed97c065ea2f861 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 6 Dec 2021 19:08:20 -0500 Subject: [PATCH 5/6] Address comments --- .../session_view_process_events.mock.ts | 43 ------------------- .../components/ProcessTree/helpers.test.ts | 10 ++--- .../public/components/ProcessTree/helpers.ts | 28 ++++++++++++ .../public/components/ProcessTree/hooks.ts | 38 +++++++--------- 4 files changed, 48 insertions(+), 71 deletions(-) diff --git a/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts b/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts index bf555a9bd2f27..7c3330117f338 100644 --- a/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts +++ b/x-pack/plugins/session_view/common/mocks/responses/session_view_process_events.mock.ts @@ -1913,47 +1913,4 @@ export const sessionViewProcessEventsMock = () => ({ }, ], }, - sessionLeader: { - '@timestamp': '2021-11-23T13:40:16.528Z', - process: { - pid: 51744, - pgid: 51744, - user: { name: 'vagrant', id: 1000 }, - executable: '/usr/bin/cat', - interactive: true, - entity_id: 'bb5efe42-54a6-597c-bd0e-33a1a3fc372e', - parent: { - pid: 51547, - pgid: 51547, - user: { name: 'vagrant', id: 1000 }, - executable: '/bin/bash', - interactive: true, - entity_id: 'ae06c110-ad2d-5830-b47c-08ad62f1734c', - }, - session: { - pid: 51547, - pgid: 51547, - user: { name: 'vagrant', id: 1000 }, - executable: '/bin/bash', - interactive: true, - entity_id: 'ae06c110-ad2d-5830-b47c-08ad62f1734c', - }, - entry: { - pid: 51547, - pgid: 51547, - user: { name: 'vagrant', id: 1000 }, - executable: '/bin/bash', - interactive: true, - entity_id: 'ae06c110-ad2d-5830-b47c-08ad62f1734c', - start: '2021-11-23T13:40:07.183Z', - }, - args_count: 2, - args: ['cat', 'EventConverter/src/main.ts'], - working_directory: '/home/vagrant', - }, - event: { action: 'exec', category: 'process', kind: 'event' }, - network: { application: 'ssh' }, - source: { ip: '10.0.2.2' }, - client: { ip: '10.0.2.2' }, - }, }); diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts index 94b60ee0b6e3a..20929bc9adb01 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts +++ b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.test.ts @@ -4,13 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION } from 'constants'; import { mockEvents, mockProcessMap, - sessionViewBasicProcessMock, } from '../../../common/mocks/constants/session_view_process.mock'; -import { Process, ProcessEvent, ProcessMap } from '../../../common/types/process_tree'; +import { Process, ProcessMap } from '../../../common/types/process_tree'; import { updateProcessMap, buildProcessTree, @@ -30,7 +28,7 @@ describe('process tree hook helpers tests', () => { }); it('updateProcessMap works', () => { - updateProcessMap(processMap, mockEvents); + processMap = updateProcessMap(processMap, mockEvents); // processes are added to processMap mockEvents.forEach((event) => { @@ -41,7 +39,7 @@ describe('process tree hook helpers tests', () => { it('buildProcessTree works', () => { processMap = mockProcessMap; const orphans: Process[] = []; - buildProcessTree(processMap, mockEvents, orphans, SESSION_ENTITY_ID); + processMap = buildProcessTree(processMap, mockEvents, orphans, SESSION_ENTITY_ID); const sessionLeaderChildrenIds = new Set( processMap[SESSION_ENTITY_ID].children.map((child) => child.id) @@ -69,7 +67,7 @@ describe('process tree hook helpers tests', () => { processMap[SESSION_ENTITY_ID].children = childProcesses; expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeFalsy(); - autoExpandProcessTree(processMap); + processMap = autoExpandProcessTree(processMap); // session leader should have autoExpand to be true expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeTruthy(); }); diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts index 7343a9a978957..eef0b66897905 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts +++ b/x-pack/plugins/session_view/public/components/ProcessTree/helpers.ts @@ -19,6 +19,8 @@ export const updateProcessMap = (processMap: ProcessMap, events: ProcessEvent[]) process.events.push(event); }); + + return processMap; }; export const buildProcessTree = ( @@ -47,6 +49,8 @@ export const buildProcessTree = ( orphans.push(process); } }); + + return processMap; }; export const searchProcessTree = (processMap: ProcessMap, searchQuery: string | undefined) => { @@ -91,4 +95,28 @@ export const autoExpandProcessTree = (processMap: ProcessMap) => { } } } + + return processMap; +}; + +export const processNewEvents = ( + eventsProcessMap: ProcessMap, + events: ProcessEvent[] | undefined, + orphans: Process[], + sessionEntityId: string, + backwardDirection: boolean = false +) => { + if (!events || events.length === 0) { + return eventsProcessMap; + } + + const updatedProcessMap = updateProcessMap(eventsProcessMap, events); + const builtProcessMap = buildProcessTree( + updatedProcessMap, + events, + orphans, + sessionEntityId, + backwardDirection + ); + return autoExpandProcessTree(builtProcessMap); }; diff --git a/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts b/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts index 51e4dd05354c7..7627a2ea02467 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/ProcessTree/hooks.ts @@ -14,12 +14,7 @@ import { ProcessEvent, ProcessMap, } from '../../../common/types/process_tree'; -import { - updateProcessMap, - buildProcessTree, - searchProcessTree, - autoExpandProcessTree, -} from './helpers'; +import { processNewEvents, searchProcessTree, autoExpandProcessTree } from './helpers'; interface UseProcessTreeDeps { sessionEntityId: string; @@ -139,29 +134,28 @@ export const useProcessTree = ({ const [searchResults, setSearchResults] = useState([]); const [orphans, setOrphans] = useState([]); - const processNewEvents = ( - events: ProcessEvent[] | undefined, - backwardDirection: boolean = false - ) => { - if (!events || events.length === 0) { - return; - } - - updateProcessMap(processMap, events); - buildProcessTree(processMap, events, orphans, sessionEntityId, backwardDirection); - autoExpandProcessTree(processMap); - }; - useEffect(() => { + let eventsProcessMap: ProcessMap = processMap; if (backward) { - processNewEvents(backward.slice(0, backward.length - backwardIndex), true); + eventsProcessMap = processNewEvents( + eventsProcessMap, + backward.slice(0, backward.length - backwardIndex), + orphans, + sessionEntityId, + true + ); setBackwardIndex(backward.length); } - processNewEvents(forward.slice(forwardIndex)); + eventsProcessMap = processNewEvents( + eventsProcessMap, + forward.slice(forwardIndex), + orphans, + sessionEntityId + ); setForwardIndex(forward.length); - setProcessMap({ ...processMap }); + setProcessMap({ ...eventsProcessMap }); setOrphans([...orphans]); // eslint-disable-next-line react-hooks/exhaustive-deps }, [forward, backward]); From 1ee6b4127ca3fffc0250f9382485c081e0c04daa Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 6 Dec 2021 20:31:49 -0500 Subject: [PATCH 6/6] Add unit tests to ProcessTreeAlerts component --- .../public/components/ProcessTreeAlerts/index.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx b/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx index 31cdaa521bbe1..03c59c1ba89e8 100644 --- a/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx +++ b/x-pack/plugins/session_view/public/components/ProcessTreeAlerts/index.tsx @@ -45,7 +45,7 @@ export function ProcessTreeAlerts({ alerts }: ProcessTreeAlertsDeps) { const { name, query, severity } = rule; return ( - +
@@ -77,7 +77,11 @@ export function ProcessTreeAlerts({ alerts }: ProcessTreeAlertsDeps) { {event.action}
- +
@@ -92,5 +96,9 @@ export function ProcessTreeAlerts({ alerts }: ProcessTreeAlertsDeps) { ); }; - return
{alerts.map(renderAlertDetails)}
; + return ( +
+ {alerts.map(renderAlertDetails)} +
+ ); }