From e69bec2293ac99ced9b8a148b637aea178cd7850 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Wed, 16 Mar 2022 18:07:25 -0400 Subject: [PATCH 1/7] Add alertFlyoutCallback to process_tree_alert --- .../public/components/process_tree/index.tsx | 3 +++ .../components/process_tree_alert/index.tsx | 15 +++++++++++++-- .../components/process_tree_alerts/index.tsx | 3 +++ .../public/components/process_tree_node/index.tsx | 4 ++++ .../public/components/session_view/index.tsx | 8 +++++++- .../plugins/session_view/public/methods/index.tsx | 8 ++------ x-pack/plugins/session_view/public/types.ts | 1 + 7 files changed, 33 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.tsx index 652d2209790b0..58ad8155f31a7 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.tsx @@ -36,6 +36,7 @@ interface ProcessTreeDeps { selectedProcess?: Process | null; onProcessSelected: (process: Process | null) => void; setSearchResults?: (results: Process[]) => void; + alertsFlyoutCallback?: (alertUuid: string) => void; } export const ProcessTree = ({ @@ -51,6 +52,7 @@ export const ProcessTree = ({ selectedProcess, onProcessSelected, setSearchResults, + alertsFlyoutCallback, }: ProcessTreeDeps) => { const [isInvestigatedEventVisible, setIsInvestigatedEventVisible] = useState(true); const [isInvestigatedEventAbove, setIsInvestigatedEventAbove] = useState(false); @@ -185,6 +187,7 @@ export const ProcessTree = ({ selectedProcessId={selectedProcess?.id} scrollerRef={scrollerRef} onChangeJumpToEventVisibility={onChangeJumpToEventVisibility} + alertsFlyoutCallback={alertsFlyoutCallback} /> )}
void; selectAlert: (alertUuid: string) => void; + alertsFlyoutCallback?: (alertUuid: string) => void; } export const ProcessTreeAlert = ({ @@ -25,6 +26,7 @@ export const ProcessTreeAlert = ({ isSelected, onClick, selectAlert, + alertsFlyoutCallback, }: ProcessTreeAlertDeps) => { const styles = useStyles({ isInvestigated, isSelected }); @@ -36,7 +38,7 @@ export const ProcessTreeAlert = ({ } }, [isInvestigated, isSelected, uuid, selectAlert]); - if (!(alert.kibana && rule)) { + if (!(alert.kibana && rule && uuid)) { return null; } @@ -46,6 +48,10 @@ export const ProcessTreeAlert = ({ onClick(alert.kibana?.alert ?? null); }; + const handleExpandClick = () => { + alertsFlyoutCallback?.(uuid); + }; + return ( - + {name} diff --git a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx index dcca29dcf4f84..79a3a429b6bdb 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx @@ -16,6 +16,7 @@ interface ProcessTreeAlertsDeps { jumpToAlertID?: string; isProcessSelected?: boolean; onAlertSelected: (e: MouseEvent) => void; + alertsFlyoutCallback?: (alertUuid: string) => void; } export function ProcessTreeAlerts({ @@ -23,6 +24,7 @@ export function ProcessTreeAlerts({ jumpToAlertID, isProcessSelected = false, onAlertSelected, + alertsFlyoutCallback, }: ProcessTreeAlertsDeps) { const [selectedAlert, setSelectedAlert] = useState(null); const styles = useStyles(); @@ -83,6 +85,7 @@ export function ProcessTreeAlerts({ isSelected={isProcessSelected && selectedAlert?.uuid === alertUuid} onClick={handleAlertClick} selectAlert={selectAlert} + alertsFlyoutCallback={alertsFlyoutCallback} /> ); })} diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx index 2b7a4f8c79469..77f7ae0daa557 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx @@ -40,6 +40,7 @@ export interface ProcessDeps { selectedProcessId?: string; scrollerRef: RefObject; onChangeJumpToEventVisibility: (isVisible: boolean, isAbove: boolean) => void; + alertsFlyoutCallback?: (alertUuid: string) => void; } /** @@ -55,6 +56,7 @@ export function ProcessTreeNode({ selectedProcessId, scrollerRef, onChangeJumpToEventVisibility, + alertsFlyoutCallback, }: ProcessDeps) { const textRef = useRef(null); @@ -242,6 +244,7 @@ export function ProcessTreeNode({ jumpToAlertID={jumpToAlertID} isProcessSelected={selectedProcessId === process.id} onAlertSelected={onProcessClicked} + alertsFlyoutCallback={alertsFlyoutCallback} /> )} @@ -259,6 +262,7 @@ export function ProcessTreeNode({ selectedProcessId={selectedProcessId} scrollerRef={scrollerRef} onChangeJumpToEventVisibility={onChangeJumpToEventVisibility} + alertsFlyoutCallback={alertsFlyoutCallback} /> ); })} diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index 58899b2bf5c75..cf868fe700cde 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -25,7 +25,12 @@ import { useFetchSessionViewProcessEvents } from './hooks'; /** * The main wrapper component for the session view. */ -export const SessionView = ({ sessionEntityId, height, jumpToEvent }: SessionViewDeps) => { +export const SessionView = ({ + sessionEntityId, + height, + jumpToEvent, + alertsFlyoutCallback, +}: SessionViewDeps) => { const [isDetailOpen, setIsDetailOpen] = useState(false); const [selectedProcess, setSelectedProcess] = useState(null); @@ -162,6 +167,7 @@ export const SessionView = ({ sessionEntityId, height, jumpToEvent }: SessionVie fetchNextPage={fetchNextPage} fetchPreviousPage={fetchPreviousPage} setSearchResults={setSearchResults} + alertsFlyoutCallback={alertsFlyoutCallback} />
)} diff --git a/x-pack/plugins/session_view/public/methods/index.tsx b/x-pack/plugins/session_view/public/methods/index.tsx index 1eecdcbb3e50e..3654e296e7412 100644 --- a/x-pack/plugins/session_view/public/methods/index.tsx +++ b/x-pack/plugins/session_view/public/methods/index.tsx @@ -15,15 +15,11 @@ const queryClient = new QueryClient(); const SessionViewLazy = lazy(() => import('../components/session_view')); -export const getSessionViewLazy = ({ sessionEntityId, height, jumpToEvent }: SessionViewDeps) => { +export const getSessionViewLazy = (props: SessionViewDeps) => { return ( }> - + ); diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index d84623af7c0ed..e79bd2961f7b9 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -20,6 +20,7 @@ export interface SessionViewDeps { // if provided, the session view will jump to and select the provided event if it belongs to the session leader // session view will fetch a page worth of events starting from jumpToEvent as well as a page backwards. jumpToEvent?: ProcessEvent; + alertsFlyoutCallback?: (alertUuid: string) => void; } export interface EuiTabProps { From 201b579bbeb71fda8d994af9461e43a73f3a61ee Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Thu, 17 Mar 2022 12:47:09 -0400 Subject: [PATCH 2/7] Add useCallback hook to callback functions --- .../components/process_tree_alert/index.tsx | 28 +++++++++++-------- .../components/process_tree_alerts/index.tsx | 13 +++++---- .../components/process_tree_node/index.tsx | 21 ++++++++------ 3 files changed, 36 insertions(+), 26 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx index 374d829367900..fe83ccdb3ac2c 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiBadge, EuiIcon, EuiText, EuiButtonIcon } from '@elastic/eui'; import { ProcessEvent, ProcessEventAlert } from '../../../common/types/process_tree'; import { getBadgeColorFromAlertStatus } from './helpers'; @@ -33,25 +33,29 @@ export const ProcessTreeAlert = ({ const { uuid, rule, workflow_status: status } = alert.kibana?.alert || {}; useEffect(() => { - if (isInvestigated && isSelected && uuid) { + if (isInvestigated && uuid) { selectAlert(uuid); } - }, [isInvestigated, isSelected, uuid, selectAlert]); + }, [isInvestigated, uuid, selectAlert]); - if (!(alert.kibana && rule && uuid)) { + const handleExpandClick = useCallback(() => { + if (alertsFlyoutCallback && uuid) { + alertsFlyoutCallback(uuid); + } + }, [alertsFlyoutCallback, uuid]); + + const handleClick = useCallback(() => { + if (alert.kibana?.alert) { + onClick(alert.kibana.alert); + } + }, [alert.kibana?.alert, onClick]); + + if (!(alert.kibana && rule)) { return null; } const { name } = rule; - const handleClick = () => { - onClick(alert.kibana?.alert ?? null); - }; - - const handleExpandClick = () => { - alertsFlyoutCallback?.(uuid); - }; - return ( { + onAlertSelected(MOUSE_EVENT_PLACEHOLDER); + setSelectedAlert(alert); + }, + [onAlertSelected] + ); + if (alerts.length === 0) { return null; } - const handleAlertClick = (alert: ProcessEventAlert | null) => { - onAlertSelected(MOUSE_EVENT_PLACEHOLDER); - setSelectedAlert(alert); - }; - return (
{ - e.stopPropagation(); + const onProcessClicked = useCallback( + (e: MouseEvent) => { + e.stopPropagation(); - const selection = window.getSelection(); + const selection = window.getSelection(); - // do not select the command if the user was just selecting text for copy. - if (selection && selection.type === 'Range') { - return; - } + // do not select the command if the user was just selecting text for copy. + if (selection && selection.type === 'Range') { + return; + } - onProcessSelected?.(process); - }; + onProcessSelected?.(process); + }, + [onProcessSelected, process] + ); const processDetails = process.getDetails(); From 57f24b2ae135d7259e46407aa2f0d0f82a98c258 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Thu, 17 Mar 2022 13:47:11 -0400 Subject: [PATCH 3/7] Rename to loadAlertDetails and add handleOnAlertDetailsClosed --- .../public/components/process_tree/index.tsx | 9 ++++++--- .../public/components/process_tree_alert/index.tsx | 11 ++++++----- .../public/components/process_tree_alerts/index.tsx | 9 ++++++--- .../public/components/process_tree_node/index.tsx | 12 ++++++++---- .../public/components/session_view/index.tsx | 6 ++++-- x-pack/plugins/session_view/public/types.ts | 5 ++++- 6 files changed, 34 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.tsx index be128c1daeabf..434d9f8fcf7a4 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.tsx @@ -36,7 +36,8 @@ interface ProcessTreeDeps { selectedProcess?: Process | null; onProcessSelected: (process: Process | null) => void; setSearchResults?: (results: Process[]) => void; - alertsFlyoutCallback?: (alertUuid: string) => void; + loadAlertDetails?: (alertUuid: string) => void; + handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; timeStampOn?: boolean; verboseModeOn?: boolean; } @@ -54,7 +55,8 @@ export const ProcessTree = ({ selectedProcess, onProcessSelected, setSearchResults, - alertsFlyoutCallback, + loadAlertDetails, + handleOnAlertDetailsClosed, timeStampOn, verboseModeOn, }: ProcessTreeDeps) => { @@ -191,7 +193,8 @@ export const ProcessTree = ({ selectedProcessId={selectedProcess?.id} scrollerRef={scrollerRef} onChangeJumpToEventVisibility={onChangeJumpToEventVisibility} - alertsFlyoutCallback={alertsFlyoutCallback} + loadAlertDetails={loadAlertDetails} + handleOnAlertDetailsClosed={handleOnAlertDetailsClosed} timeStampOn={timeStampOn} verboseModeOn={verboseModeOn} /> diff --git a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx index fe83ccdb3ac2c..d35b2ac62be81 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx @@ -17,7 +17,8 @@ interface ProcessTreeAlertDeps { isSelected: boolean; onClick: (alert: ProcessEventAlert | null) => void; selectAlert: (alertUuid: string) => void; - alertsFlyoutCallback?: (alertUuid: string) => void; + loadAlertDetails?: (alertUuid: string) => void; + handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; } export const ProcessTreeAlert = ({ @@ -26,7 +27,7 @@ export const ProcessTreeAlert = ({ isSelected, onClick, selectAlert, - alertsFlyoutCallback, + loadAlertDetails, }: ProcessTreeAlertDeps) => { const styles = useStyles({ isInvestigated, isSelected }); @@ -39,10 +40,10 @@ export const ProcessTreeAlert = ({ }, [isInvestigated, uuid, selectAlert]); const handleExpandClick = useCallback(() => { - if (alertsFlyoutCallback && uuid) { - alertsFlyoutCallback(uuid); + if (loadAlertDetails && uuid) { + loadAlertDetails(uuid); } - }, [alertsFlyoutCallback, uuid]); + }, [loadAlertDetails, uuid]); const handleClick = useCallback(() => { if (alert.kibana?.alert) { diff --git a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx index 01d4699716af3..bbf181e7af349 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx @@ -16,7 +16,8 @@ interface ProcessTreeAlertsDeps { jumpToAlertID?: string; isProcessSelected?: boolean; onAlertSelected: (e: MouseEvent) => void; - alertsFlyoutCallback?: (alertUuid: string) => void; + loadAlertDetails?: (alertUuid: string) => void; + handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; } export function ProcessTreeAlerts({ @@ -24,7 +25,8 @@ export function ProcessTreeAlerts({ jumpToAlertID, isProcessSelected = false, onAlertSelected, - alertsFlyoutCallback, + loadAlertDetails, + handleOnAlertDetailsClosed, }: ProcessTreeAlertsDeps) { const [selectedAlert, setSelectedAlert] = useState(null); const styles = useStyles(); @@ -88,7 +90,8 @@ export function ProcessTreeAlerts({ isSelected={isProcessSelected && selectedAlert?.uuid === alertUuid} onClick={handleAlertClick} selectAlert={selectAlert} - alertsFlyoutCallback={alertsFlyoutCallback} + loadAlertDetails={loadAlertDetails} + handleOnAlertDetailsClosed={handleOnAlertDetailsClosed} /> ); })} diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx index 0f93b0043300a..cae1be6583a22 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx @@ -43,7 +43,8 @@ export interface ProcessDeps { verboseModeOn?: boolean; scrollerRef: RefObject; onChangeJumpToEventVisibility: (isVisible: boolean, isAbove: boolean) => void; - alertsFlyoutCallback?: (alertUuid: string) => void; + loadAlertDetails?: (alertUuid: string) => void; + handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; } /** @@ -61,7 +62,8 @@ export function ProcessTreeNode({ verboseModeOn = true, scrollerRef, onChangeJumpToEventVisibility, - alertsFlyoutCallback, + loadAlertDetails, + handleOnAlertDetailsClosed, }: ProcessDeps) { const textRef = useRef(null); @@ -253,7 +255,8 @@ export function ProcessTreeNode({ jumpToAlertID={jumpToAlertID} isProcessSelected={selectedProcessId === process.id} onAlertSelected={onProcessClicked} - alertsFlyoutCallback={alertsFlyoutCallback} + loadAlertDetails={loadAlertDetails} + handleOnAlertDetailsClosed={handleOnAlertDetailsClosed} /> )} @@ -273,7 +276,8 @@ export function ProcessTreeNode({ verboseModeOn={verboseModeOn} scrollerRef={scrollerRef} onChangeJumpToEventVisibility={onChangeJumpToEventVisibility} - alertsFlyoutCallback={alertsFlyoutCallback} + loadAlertDetails={loadAlertDetails} + handleOnAlertDetailsClosed={handleOnAlertDetailsClosed} /> ); })} diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index e30f8eaf296cb..abb7623083a0a 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -32,7 +32,8 @@ export const SessionView = ({ sessionEntityId, height, jumpToEvent, - alertsFlyoutCallback, + loadAlertDetails, + handleOnAlertDetailsClosed, }: SessionViewDeps) => { const [isDetailOpen, setIsDetailOpen] = useState(false); const [selectedProcess, setSelectedProcess] = useState(null); @@ -187,7 +188,8 @@ export const SessionView = ({ fetchNextPage={fetchNextPage} fetchPreviousPage={fetchPreviousPage} setSearchResults={setSearchResults} - alertsFlyoutCallback={alertsFlyoutCallback} + loadAlertDetails={loadAlertDetails} + handleOnAlertDetailsClosed={handleOnAlertDetailsClosed} timeStampOn={displayOptions.timestamp} verboseModeOn={displayOptions.verboseMode} /> diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index e79bd2961f7b9..4a282d0dc3cf8 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -20,7 +20,10 @@ export interface SessionViewDeps { // if provided, the session view will jump to and select the provided event if it belongs to the session leader // session view will fetch a page worth of events starting from jumpToEvent as well as a page backwards. jumpToEvent?: ProcessEvent; - alertsFlyoutCallback?: (alertUuid: string) => void; + // Callback to open the alerts flyout + loadAlertDetails?: (alertUuid: string) => void; + // Callback used when alert flyout panel is closed + handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; } export interface EuiTabProps { From 8a5c745843c9ffa22a0c29399c2131331c95433b Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Fri, 18 Mar 2022 18:41:01 -0400 Subject: [PATCH 4/7] Finish functionality --- .../plugins/session_view/common/constants.ts | 19 +++++++ .../public/components/process_tree/helpers.ts | 14 +++++ .../public/components/process_tree/hooks.ts | 25 ++++++++- .../public/components/process_tree/index.tsx | 10 +++- .../components/process_tree_alert/index.tsx | 9 +-- .../components/process_tree_alerts/index.tsx | 4 +- .../process_tree_node/index.test.tsx | 1 + .../components/process_tree_node/index.tsx | 4 +- .../public/components/session_view/hooks.ts | 46 ++++++++++++++- .../public/components/session_view/index.tsx | 38 +++++++++---- x-pack/plugins/session_view/public/types.ts | 15 ++++- .../server/routes/alert_status_route.ts | 56 +++++++++++++++++++ .../session_view/server/routes/index.ts | 2 + 13 files changed, 215 insertions(+), 28 deletions(-) create mode 100644 x-pack/plugins/session_view/server/routes/alert_status_route.ts diff --git a/x-pack/plugins/session_view/common/constants.ts b/x-pack/plugins/session_view/common/constants.ts index 4ca130c6af7b4..dac2ee85a1f39 100644 --- a/x-pack/plugins/session_view/common/constants.ts +++ b/x-pack/plugins/session_view/common/constants.ts @@ -6,10 +6,12 @@ */ export const PROCESS_EVENTS_ROUTE = '/internal/session_view/process_events_route'; +export const ALERT_STATUS_ROUTE = '/internal/session_view/alert_status_route'; export const SESSION_ENTRY_LEADERS_ROUTE = '/internal/session_view/session_entry_leaders_route'; export const PROCESS_EVENTS_INDEX = 'logs-endpoint.events.process-default'; export const ALERTS_INDEX = '.siem-signals-default'; export const ENTRY_SESSION_ENTITY_ID_PROPERTY = 'process.entry_leader.entity_id'; +export const ALERT_UUID_PROPERTY = 'kibana.alert.uuid'; export const KIBANA_DATE_FORMAT = 'MMM DD, YYYY @ hh:mm:ss.SSS'; // We fetch a large number of events per page to mitigate a few design caveats in session viewer @@ -26,6 +28,23 @@ export const KIBANA_DATE_FORMAT = 'MMM DD, YYYY @ hh:mm:ss.SSS'; // search functionality will instead use a separate ES backend search to avoid this. // 3. Fewer round trips to the backend! export const PROCESS_EVENTS_PER_PAGE = 1000; + +// As an initial approach, we won't be implementing pagination for alerts. +// Instead we will load this fixed amount of alerts as a maximum for a session. +// This could cause an edge case, where a noisy rule that alerts on every process event +// causes a session to only list and highlight up to 1000 alerts, even though there could +// be far greater than this amount. UX should be added to let the end user know this is +// happening and to revise their rule to be more specific. +export const ALERTS_PER_PAGE = 1000; + +// when showing the count of alerts in details panel tab, if the number +// exceeds ALERT_COUNT_THRESHOLD we put a + next to it, e.g 999+ +export const ALERT_COUNT_THRESHOLD = 999; + +// react-query caching keys +export const QUERY_KEY_PROCESS_EVENTS = 'sessionViewProcessEvents'; +export const QUERY_KEY_ALERTS = 'sessionViewAlerts'; + export const MOUSE_EVENT_PLACEHOLDER = { stopPropagation: () => undefined } as React.MouseEvent; export const DEBOUNCE_TIMEOUT = 500; diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts index d3d7af1c62eda..d8ec67569c206 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts @@ -5,8 +5,22 @@ * 2.0. */ import { Process, ProcessEvent, ProcessMap } from '../../../common/types/process_tree'; +import { UpdateAlertStatus } from '../../types'; import { ProcessImpl } from './hooks'; +// if given event is an alert, and it exist in updatedAlertsStatus, update the alert's status +// with the updated status value in updatedAlertsStatus Map +export const updateAlertEventStatus = ( + events: ProcessEvent[], + updatedAlertsStatus: UpdateAlertStatus +) => { + events.forEach((event) => { + if (event.kibana?.alert.uuid && updatedAlertsStatus[event.kibana.alert.uuid]) { + event.kibana.alert.workflow_status = updatedAlertsStatus[event.kibana.alert.uuid].status; + } + }); +}; + // given a page of new events, add these events to the appropriate process class model // create a new process if none are created and return the mutated processMap export const updateProcessMap = (processMap: ProcessMap, events: ProcessEvent[]) => { diff --git a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts index dfd34a5d10094..77cb3aa7d1be4 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts @@ -15,13 +15,20 @@ import { ProcessMap, ProcessEventsPage, } from '../../../common/types/process_tree'; -import { processNewEvents, searchProcessTree, autoExpandProcessTree } from './helpers'; +import { + updateAlertEventStatus, + processNewEvents, + searchProcessTree, + autoExpandProcessTree, +} from './helpers'; import { sortProcesses } from '../../../common/utils/sort_processes'; +import { UpdateAlertStatus } from '../../types'; interface UseProcessTreeDeps { sessionEntityId: string; data: ProcessEventsPage[]; searchQuery?: string; + updatedAlertsStatus: UpdateAlertStatus; } export class ProcessImpl implements Process { @@ -129,6 +136,7 @@ export class ProcessImpl implements Process { // only used to auto expand parts of the tree that could be of interest. isUserEntered() { const event = this.getDetails(); + const { pid, tty, @@ -181,7 +189,12 @@ export class ProcessImpl implements Process { }); } -export const useProcessTree = ({ sessionEntityId, data, searchQuery }: UseProcessTreeDeps) => { +export const useProcessTree = ({ + sessionEntityId, + data, + searchQuery, + updatedAlertsStatus, +}: 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 @@ -250,5 +263,13 @@ export const useProcessTree = ({ sessionEntityId, data, searchQuery }: UseProces sessionLeader.orphans = orphans; + // update alert status in processMap for alerts in updatedAlertsStatus + Object.keys(updatedAlertsStatus).forEach((alertUuid) => { + const process = processMap[updatedAlertsStatus[alertUuid].processEntityId]; + if (process) { + updateAlertEventStatus(process.getAlerts(), updatedAlertsStatus); + } + }); + return { sessionLeader: processMap[sessionEntityId], processMap, searchResults }; }; diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.tsx index 434d9f8fcf7a4..e476150c9efd6 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.tsx @@ -11,6 +11,7 @@ import { ProcessTreeNode } from '../process_tree_node'; import { BackToInvestigatedAlert } from '../back_to_investigated_alert'; import { useProcessTree } from './hooks'; import { Process, ProcessEventsPage, ProcessEvent } from '../../../common/types/process_tree'; +import { UpdateAlertStatus } from '../../types'; import { useScroll } from '../../hooks/use_scroll'; import { useStyles } from './styles'; @@ -36,8 +37,11 @@ interface ProcessTreeDeps { selectedProcess?: Process | null; onProcessSelected: (process: Process | null) => void; setSearchResults?: (results: Process[]) => void; - loadAlertDetails?: (alertUuid: string) => void; - handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; + + // a map for alerts with updated status and process.entity_id + updatedAlertsStatus: UpdateAlertStatus; + loadAlertDetails?: (alertUuid: string, handleOnAlertDetailsClosed: () => void) => void; + handleOnAlertDetailsClosed: (alertUuid: string) => void; timeStampOn?: boolean; verboseModeOn?: boolean; } @@ -55,6 +59,7 @@ export const ProcessTree = ({ selectedProcess, onProcessSelected, setSearchResults, + updatedAlertsStatus, loadAlertDetails, handleOnAlertDetailsClosed, timeStampOn, @@ -68,6 +73,7 @@ export const ProcessTree = ({ sessionEntityId, data, searchQuery, + updatedAlertsStatus, }); const scrollerRef = useRef(null); diff --git a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx index d35b2ac62be81..4e425920570de 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx @@ -17,8 +17,8 @@ interface ProcessTreeAlertDeps { isSelected: boolean; onClick: (alert: ProcessEventAlert | null) => void; selectAlert: (alertUuid: string) => void; - loadAlertDetails?: (alertUuid: string) => void; - handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; + loadAlertDetails?: (alertUuid: string, handleOnAlertDetailsClosed: () => void) => void; + handleOnAlertDetailsClosed: (alertUuid: string, status?: string) => void; } export const ProcessTreeAlert = ({ @@ -28,6 +28,7 @@ export const ProcessTreeAlert = ({ onClick, selectAlert, loadAlertDetails, + handleOnAlertDetailsClosed, }: ProcessTreeAlertDeps) => { const styles = useStyles({ isInvestigated, isSelected }); @@ -41,9 +42,9 @@ export const ProcessTreeAlert = ({ const handleExpandClick = useCallback(() => { if (loadAlertDetails && uuid) { - loadAlertDetails(uuid); + loadAlertDetails(uuid, () => handleOnAlertDetailsClosed(uuid)); } - }, [loadAlertDetails, uuid]); + }, [handleOnAlertDetailsClosed, loadAlertDetails, uuid]); const handleClick = useCallback(() => { if (alert.kibana?.alert) { diff --git a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx index bbf181e7af349..b24fb54e29280 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx @@ -16,8 +16,8 @@ interface ProcessTreeAlertsDeps { jumpToAlertID?: string; isProcessSelected?: boolean; onAlertSelected: (e: MouseEvent) => void; - loadAlertDetails?: (alertUuid: string) => void; - handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; + loadAlertDetails?: (alertUuid: string, handleOnAlertDetailsClosed: () => void) => void; + handleOnAlertDetailsClosed: (alertUuid: string) => void; } export function ProcessTreeAlerts({ diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx index 0791f21e81846..2e82e822f0c82 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.test.tsx @@ -36,6 +36,7 @@ describe('ProcessTreeNode component', () => { }, } as unknown as RefObject, onChangeJumpToEventVisibility: jest.fn(), + handleOnAlertDetailsClosed: (_alertUuid: string) => {}, }; beforeEach(() => { diff --git a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx index cae1be6583a22..b1c42dd95efb9 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_node/index.tsx @@ -43,8 +43,8 @@ export interface ProcessDeps { verboseModeOn?: boolean; scrollerRef: RefObject; onChangeJumpToEventVisibility: (isVisible: boolean, isAbove: boolean) => void; - loadAlertDetails?: (alertUuid: string) => void; - handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; + loadAlertDetails?: (alertUuid: string, handleOnAlertDetailsClosed: () => void) => void; + handleOnAlertDetailsClosed: (alertUuid: string) => void; } /** diff --git a/x-pack/plugins/session_view/public/components/session_view/hooks.ts b/x-pack/plugins/session_view/public/components/session_view/hooks.ts index 17574cfd28074..3295ff81d0ee0 100644 --- a/x-pack/plugins/session_view/public/components/session_view/hooks.ts +++ b/x-pack/plugins/session_view/public/components/session_view/hooks.ts @@ -5,12 +5,18 @@ * 2.0. */ import { useEffect, useState } from 'react'; -import { useInfiniteQuery } from 'react-query'; +import { useQuery, useInfiniteQuery } from 'react-query'; import { EuiSearchBarOnChangeArgs } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { ProcessEvent, ProcessEventResults } from '../../../common/types/process_tree'; -import { PROCESS_EVENTS_ROUTE, PROCESS_EVENTS_PER_PAGE } from '../../../common/constants'; +import { UpdateAlertStatus } from '../../types'; +import { + PROCESS_EVENTS_ROUTE, + PROCESS_EVENTS_PER_PAGE, + ALERT_STATUS_ROUTE, + QUERY_KEY_ALERTS, +} from '../../../common/constants'; export const useFetchSessionViewProcessEvents = ( sessionEntityId: string, @@ -75,6 +81,42 @@ export const useFetchSessionViewProcessEvents = ( return query; }; +export const useFetchAlertStatus = (updatedAlertsStatus: UpdateAlertStatus, alertUuid: string) => { + const { http } = useKibana().services; + const cachingKeys = [QUERY_KEY_ALERTS, alertUuid]; + const query = useQuery( + cachingKeys, + async () => { + if (!alertUuid) { + return updatedAlertsStatus; + } + + const res = await http.get(ALERT_STATUS_ROUTE, { + query: { + alertUuid, + }, + }); + + const events = res.events.map((event: any) => event._source as ProcessEvent); + + return { + ...updatedAlertsStatus, + [alertUuid]: { + status: events[0].kibana?.alert.workflow_status ?? '', + processEntityId: events[0].process.entity_id, + }, + }; + }, + { + refetchOnWindowFocus: false, + refetchOnMount: false, + refetchOnReconnect: false, + } + ); + + return query; +}; + export const useSearchQuery = () => { const [searchQuery, setSearchQuery] = useState(''); const onSearch = ({ query }: EuiSearchBarOnChangeArgs) => { diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index abb7623083a0a..65a12ca3c3e61 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useState, useCallback } from 'react'; +import React, { useState, useCallback, useEffect } from 'react'; import { EuiEmptyPrompt, EuiButton, @@ -18,12 +18,12 @@ import { SectionLoading } from '../../shared_imports'; import { ProcessTree } from '../process_tree'; import { Process } from '../../../common/types/process_tree'; import { DisplayOptionsState } from '../../../common/types/session_view'; -import { SessionViewDeps } from '../../types'; +import { SessionViewDeps, UpdateAlertStatus } from '../../types'; import { SessionViewDetailPanel } from '../session_view_detail_panel'; import { SessionViewSearchBar } from '../session_view_search_bar'; import { SessionViewDisplayOptions } from '../session_view_display_options'; import { useStyles } from './styles'; -import { useFetchSessionViewProcessEvents } from './hooks'; +import { useFetchAlertStatus, useFetchSessionViewProcessEvents } from './hooks'; /** * The main wrapper component for the session view. @@ -33,23 +33,23 @@ export const SessionView = ({ height, jumpToEvent, loadAlertDetails, - handleOnAlertDetailsClosed, }: SessionViewDeps) => { const [isDetailOpen, setIsDetailOpen] = useState(false); const [selectedProcess, setSelectedProcess] = useState(null); - - const styles = useStyles({ height }); - - const onProcessSelected = useCallback((process: Process | null) => { - setSelectedProcess(process); - }, []); - const [searchQuery, setSearchQuery] = useState(''); const [searchResults, setSearchResults] = useState(null); const [displayOptions, setDisplayOptions] = useState({ timestamp: true, verboseMode: true, }); + const [fetchAlertStatus, setFetchAlertStatus] = useState([]); + const [updatedAlertsStatus, setUpdatedAlertsStatus] = useState({}); + + const styles = useStyles({ height }); + + const onProcessSelected = useCallback((process: Process | null) => { + setSelectedProcess(process); + }, []); const { data, @@ -64,10 +64,25 @@ export const SessionView = ({ const hasData = data && data.pages.length > 0 && data.pages[0].events.length > 0; const renderIsLoading = isFetching && !data; const renderDetails = isDetailOpen && selectedProcess; + const { data: newUpdatedAlertsStatus } = useFetchAlertStatus( + updatedAlertsStatus, + fetchAlertStatus[0] ?? '' + ); + + useEffect(() => { + if (fetchAlertStatus) { + setUpdatedAlertsStatus({ ...newUpdatedAlertsStatus }); + } + }, [fetchAlertStatus, newUpdatedAlertsStatus]); + + const handleOnAlertDetailsClosed = useCallback((alertUuid: string) => { + setFetchAlertStatus([alertUuid]); + }, []); const toggleDetailPanel = useCallback(() => { setIsDetailOpen(!isDetailOpen); }, [isDetailOpen]); + const handleOptionChange = useCallback((checkedOptions: DisplayOptionsState) => { setDisplayOptions(checkedOptions); }, []); @@ -188,6 +203,7 @@ export const SessionView = ({ fetchNextPage={fetchNextPage} fetchPreviousPage={fetchPreviousPage} setSearchResults={setSearchResults} + updatedAlertsStatus={updatedAlertsStatus} loadAlertDetails={loadAlertDetails} handleOnAlertDetailsClosed={handleOnAlertDetailsClosed} timeStampOn={displayOptions.timestamp} diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 4a282d0dc3cf8..28c61d6744b7a 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -21,9 +21,18 @@ export interface SessionViewDeps { // session view will fetch a page worth of events starting from jumpToEvent as well as a page backwards. jumpToEvent?: ProcessEvent; // Callback to open the alerts flyout - loadAlertDetails?: (alertUuid: string) => void; - // Callback used when alert flyout panel is closed - handleOnAlertDetailsClosed?: (alertUuid: string, status?: string) => void; + loadAlertDetails?: ( + alertUuid: string, + // Callback used when alert flyout panel is closed + handleOnAlertDetailsClosed: () => void + ) => void; +} + +export interface UpdateAlertStatus { + [key: string]: { + status: string; + processEntityId: string; + }; } export interface EuiTabProps { diff --git a/x-pack/plugins/session_view/server/routes/alert_status_route.ts b/x-pack/plugins/session_view/server/routes/alert_status_route.ts new file mode 100644 index 0000000000000..70ce32ee72020 --- /dev/null +++ b/x-pack/plugins/session_view/server/routes/alert_status_route.ts @@ -0,0 +1,56 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import type { ElasticsearchClient } from 'kibana/server'; +import { IRouter } from '../../../../../src/core/server'; +import { ALERT_STATUS_ROUTE, ALERTS_INDEX, ALERT_UUID_PROPERTY } from '../../common/constants'; +import { expandDottedObject } from '../../common/utils/expand_dotted_object'; + +export const registerAlertStatusRoute = (router: IRouter) => { + router.get( + { + path: ALERT_STATUS_ROUTE, + validate: { + query: schema.object({ + alertUuid: schema.string(), + }), + }, + }, + async (context, request, response) => { + const client = context.core.elasticsearch.client.asCurrentUser; + const { alertUuid } = request.query; + const body = await searchAlertByUuid(client, alertUuid); + + return response.ok({ body }); + } + ); +}; + +export const searchAlertByUuid = async (client: ElasticsearchClient, alertUuid: string) => { + const search = await client.search({ + index: [ALERTS_INDEX], + ignore_unavailable: true, // on a new installation the .siem-signals-default index might not be created yet. + body: { + query: { + match: { + [ALERT_UUID_PROPERTY]: alertUuid, + }, + }, + size: 1, + }, + }); + + const events = search.hits.hits.map((hit: any) => { + // TODO: re-eval if this is needed after updated ECS mappings are applied. + // the .siem-signals-default index flattens many properties. this util unflattens them. + hit._source = expandDottedObject(hit._source); + + return hit; + }); + + return { events }; +}; diff --git a/x-pack/plugins/session_view/server/routes/index.ts b/x-pack/plugins/session_view/server/routes/index.ts index 7b9cfb45f580b..b8cb80dc1d1d4 100644 --- a/x-pack/plugins/session_view/server/routes/index.ts +++ b/x-pack/plugins/session_view/server/routes/index.ts @@ -6,9 +6,11 @@ */ import { IRouter } from '../../../../../src/core/server'; import { registerProcessEventsRoute } from './process_events_route'; +import { registerAlertStatusRoute } from './alert_status_route'; import { sessionEntryLeadersRoute } from './session_entry_leaders_route'; export const registerRoutes = (router: IRouter) => { registerProcessEventsRoute(router); + registerAlertStatusRoute(router); sessionEntryLeadersRoute(router); }; From f1c437e1aa3b212e4b4e8ded49d44a4f708242a3 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 21 Mar 2022 11:10:38 -0400 Subject: [PATCH 5/7] Fix jest tests --- .../components/process_tree/index.test.tsx | 107 ++++-------------- .../public/components/process_tree/index.tsx | 2 +- .../process_tree_alert/index.test.tsx | 56 +++++---- .../components/process_tree_alert/index.tsx | 3 +- .../process_tree_alerts/index.test.tsx | 17 +-- .../components/process_tree_alerts/index.tsx | 2 +- 6 files changed, 68 insertions(+), 119 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx index bdaeb0cdce2b4..9fa7900d04b0d 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.test.tsx @@ -10,7 +10,7 @@ import { mockData } from '../../../common/mocks/constants/session_view_process.m import { Process } from '../../../common/types/process_tree'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; import { ProcessImpl } from './hooks'; -import { ProcessTree } from './index'; +import { ProcessTreeDeps, ProcessTree } from './index'; describe('ProcessTree component', () => { let render: () => ReturnType; @@ -18,6 +18,18 @@ describe('ProcessTree component', () => { let mockedContext: AppContextTestRender; const sessionLeader = mockData[0].events[0]; const sessionLeaderVerboseTest = mockData[0].events[3]; + const props: ProcessTreeDeps = { + sessionEntityId: sessionLeader.process.entity_id, + data: mockData, + isFetching: false, + fetchNextPage: jest.fn(), + hasNextPage: false, + fetchPreviousPage: jest.fn(), + hasPreviousPage: false, + onProcessSelected: jest.fn(), + updatedAlertsStatus: {}, + handleOnAlertDetailsClosed: jest.fn(), + }; beforeEach(() => { mockedContext = createAppRootMockRenderer(); @@ -25,18 +37,7 @@ describe('ProcessTree component', () => { describe('When ProcessTree is mounted', () => { it('should render given a valid sessionEntityId and data', () => { - renderResult = mockedContext.render( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - onProcessSelected={jest.fn()} - /> - ); + renderResult = mockedContext.render(); expect(renderResult.queryByTestId('sessionView:sessionViewProcessTree')).toBeTruthy(); expect(renderResult.queryAllByTestId('sessionView:processTreeNode')).toBeTruthy(); }); @@ -47,17 +48,7 @@ describe('ProcessTree component', () => { expect(process?.id).toBe(jumpToEvent.process.entity_id); }); renderResult = mockedContext.render( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - jumpToEvent={jumpToEvent} - onProcessSelected={onProcessSelected} - /> + ); expect(renderResult.queryByTestId('sessionView:sessionViewProcessTree')).toBeTruthy(); expect(renderResult.queryAllByTestId('sessionView:processTreeNode')).toBeTruthy(); @@ -70,16 +61,7 @@ describe('ProcessTree component', () => { expect(process?.id).toBe(sessionLeader.process.entity_id); }); renderResult = mockedContext.render( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - onProcessSelected={onProcessSelected} - /> + ); expect(renderResult.queryByTestId('sessionView:sessionViewProcessTree')).toBeTruthy(); expect(renderResult.queryAllByTestId('sessionView:processTreeNode')).toBeTruthy(); @@ -88,20 +70,7 @@ describe('ProcessTree component', () => { }); it('When Verbose mode is OFF, it should not show all childrens', () => { - renderResult = mockedContext.render( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - onProcessSelected={jest.fn()} - timeStampOn={true} - verboseModeOn={false} - /> - ); + renderResult = mockedContext.render(); expect(renderResult.queryByText('cat')).toBeFalsy(); const selectionArea = renderResult.queryAllByTestId('sessionView:processTreeNode'); @@ -112,20 +81,7 @@ describe('ProcessTree component', () => { }); it('When Verbose mode is ON, it should show all childrens', () => { - renderResult = mockedContext.render( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - onProcessSelected={jest.fn()} - timeStampOn={true} - verboseModeOn={true} - /> - ); + renderResult = mockedContext.render(); expect(renderResult.queryByText('cat')).toBeTruthy(); const selectionArea = renderResult.queryAllByTestId('sessionView:processTreeNode'); @@ -139,18 +95,7 @@ describe('ProcessTree component', () => { const mockSelectedProcess = new ProcessImpl(mockData[0].events[0].process.entity_id); renderResult = mockedContext.render( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - selectedProcess={mockSelectedProcess} - onProcessSelected={jest.fn()} - verboseModeOn={true} - /> + ); expect( @@ -162,19 +107,7 @@ describe('ProcessTree component', () => { // change the selected process const mockSelectedProcess2 = new ProcessImpl(mockData[0].events[1].process.entity_id); - renderResult.rerender( - true} - hasNextPage={false} - fetchPreviousPage={() => true} - hasPreviousPage={false} - selectedProcess={mockSelectedProcess2} - onProcessSelected={jest.fn()} - /> - ); + renderResult.rerender(); expect( renderResult diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.tsx index e476150c9efd6..69449d6761b2b 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.tsx @@ -17,7 +17,7 @@ import { useStyles } from './styles'; type FetchFunction = () => void; -interface ProcessTreeDeps { +export interface ProcessTreeDeps { // process.entity_id to act as root node (typically a session (or entry session) leader). sessionEntityId: string; diff --git a/x-pack/plugins/session_view/public/components/process_tree_alert/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_alert/index.test.tsx index 635ac09682eae..2a56a0ae2be67 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alert/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alert/index.test.tsx @@ -8,17 +8,26 @@ import React from 'react'; import { mockAlerts } from '../../../common/mocks/constants/session_view_process.mock'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; -import { ProcessTreeAlert } from './index'; +import { ProcessTreeAlertDeps, ProcessTreeAlert } from './index'; const mockAlert = mockAlerts[0]; const TEST_ID = `sessionView:sessionViewAlertDetail-${mockAlert.kibana?.alert.uuid}`; const ALERT_RULE_NAME = mockAlert.kibana?.alert.rule.name; const ALERT_STATUS = mockAlert.kibana?.alert.workflow_status; +const EXPAND_BUTTON_TEST_ID = `sessionView:sessionViewAlertDetailExpand-${mockAlert.kibana?.alert.uuid}`; describe('ProcessTreeAlerts component', () => { let render: () => ReturnType; let renderResult: ReturnType; let mockedContext: AppContextTestRender; + const props: ProcessTreeAlertDeps = { + alert: mockAlert, + isInvestigated: false, + isSelected: false, + onClick: jest.fn(), + selectAlert: jest.fn(), + handleOnAlertDetailsClosed: jest.fn(), + }; beforeEach(() => { mockedContext = createAppRootMockRenderer(); @@ -26,15 +35,7 @@ describe('ProcessTreeAlerts component', () => { describe('When ProcessTreeAlert is mounted', () => { it('should render alert row correctly', async () => { - renderResult = mockedContext.render( - - ); + renderResult = mockedContext.render(); expect(renderResult.queryByTestId(TEST_ID)).toBeTruthy(); expect(renderResult.queryByText(ALERT_RULE_NAME!)).toBeTruthy(); @@ -42,21 +43,34 @@ describe('ProcessTreeAlerts component', () => { }); it('should execute onClick callback', async () => { - const mockFn = jest.fn(); - renderResult = mockedContext.render( - - ); + const onClick = jest.fn(); + renderResult = mockedContext.render(); const alertRow = renderResult.queryByTestId(TEST_ID); expect(alertRow).toBeTruthy(); alertRow?.click(); - expect(mockFn).toHaveBeenCalledTimes(1); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it('should automatically call selectAlert when isInvestigated is true', async () => { + const selectAlert = jest.fn(); + renderResult = mockedContext.render( + + ); + + expect(selectAlert).toHaveBeenCalledTimes(1); + }); + + it('should execute loadAlertDetails callback when clicking on expand button', async () => { + const loadAlertDetails = jest.fn(); + renderResult = mockedContext.render( + + ); + + const expandButton = renderResult.queryByTestId(EXPAND_BUTTON_TEST_ID); + expect(expandButton).toBeTruthy(); + expandButton?.click(); + expect(loadAlertDetails).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx index 4e425920570de..5ec1c4a7693c3 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alert/index.tsx @@ -11,7 +11,7 @@ import { ProcessEvent, ProcessEventAlert } from '../../../common/types/process_t import { getBadgeColorFromAlertStatus } from './helpers'; import { useStyles } from './styles'; -interface ProcessTreeAlertDeps { +export interface ProcessTreeAlertDeps { alert: ProcessEvent; isInvestigated: boolean; isSelected: boolean; @@ -71,6 +71,7 @@ export const ProcessTreeAlert = ({ iconType="expand" aria-label="expand" css={styles.alertRowItem} + data-test-subj={`sessionView:sessionViewAlertDetailExpand-${uuid}`} onClick={handleExpandClick} /> diff --git a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.test.tsx b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.test.tsx index c4dbaf817cff2..2333c71d36a51 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.test.tsx @@ -8,12 +8,17 @@ import React from 'react'; import { mockAlerts } from '../../../common/mocks/constants/session_view_process.mock'; import { AppContextTestRender, createAppRootMockRenderer } from '../../test'; -import { ProcessTreeAlerts } from './index'; +import { ProcessTreeAlertsDeps, ProcessTreeAlerts } from './index'; describe('ProcessTreeAlerts component', () => { let render: () => ReturnType; let renderResult: ReturnType; let mockedContext: AppContextTestRender; + const props: ProcessTreeAlertsDeps = { + alerts: mockAlerts, + onAlertSelected: jest.fn(), + handleOnAlertDetailsClosed: jest.fn(), + }; beforeEach(() => { mockedContext = createAppRootMockRenderer(); @@ -21,17 +26,13 @@ describe('ProcessTreeAlerts component', () => { describe('When ProcessTreeAlerts is mounted', () => { it('should return null if no alerts', async () => { - renderResult = mockedContext.render( - - ); + renderResult = mockedContext.render(); expect(renderResult.queryByTestId('sessionView:sessionViewAlertDetails')).toBeNull(); }); it('should return an array of alert details', async () => { - renderResult = mockedContext.render( - - ); + renderResult = mockedContext.render(); expect(renderResult.queryByTestId('sessionView:sessionViewAlertDetails')).toBeTruthy(); mockAlerts.forEach((alert) => { @@ -49,7 +50,7 @@ describe('ProcessTreeAlerts component', () => { it('should execute onAlertSelected when clicking on an alert', async () => { const mockFn = jest.fn(); renderResult = mockedContext.render( - + ); expect(renderResult.queryByTestId('sessionView:sessionViewAlertDetails')).toBeTruthy(); diff --git a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx index b24fb54e29280..c97ccfe253605 100644 --- a/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree_alerts/index.tsx @@ -11,7 +11,7 @@ import { ProcessEvent, ProcessEventAlert } from '../../../common/types/process_t import { ProcessTreeAlert } from '../process_tree_alert'; import { MOUSE_EVENT_PLACEHOLDER } from '../../../common/constants'; -interface ProcessTreeAlertsDeps { +export interface ProcessTreeAlertsDeps { alerts: ProcessEvent[]; jumpToAlertID?: string; isProcessSelected?: boolean; From a61c73e52d4f659c115aa831c1475a7001f19e97 Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Mon, 21 Mar 2022 11:49:59 -0400 Subject: [PATCH 6/7] Add tests for updateAlertEventStatus --- .../plugins/session_view/common/constants.ts | 5 ++ .../components/process_tree/helpers.test.ts | 52 +++++++++++++++++-- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/session_view/common/constants.ts b/x-pack/plugins/session_view/common/constants.ts index dac2ee85a1f39..42e1d33ab6dba 100644 --- a/x-pack/plugins/session_view/common/constants.ts +++ b/x-pack/plugins/session_view/common/constants.ts @@ -13,6 +13,11 @@ export const ALERTS_INDEX = '.siem-signals-default'; export const ENTRY_SESSION_ENTITY_ID_PROPERTY = 'process.entry_leader.entity_id'; export const ALERT_UUID_PROPERTY = 'kibana.alert.uuid'; export const KIBANA_DATE_FORMAT = 'MMM DD, YYYY @ hh:mm:ss.SSS'; +export const ALERT_STATUS = { + OPEN: 'open', + ACKNOWLEDGED: 'acknowledged', + CLOSED: 'closed', +}; // We fetch a large number of events per page to mitigate a few design caveats in session viewer // 1. Due to the hierarchical nature of the data (e.g we are rendering a time ordered pid tree) there are common scenarios where there diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts index 9092009a7d291..3a4d7dd13ebdb 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts @@ -5,11 +5,15 @@ * 2.0. */ import { - mockData, + mockEvents, + mockAlerts, mockProcessMap, } from '../../../common/mocks/constants/session_view_process.mock'; -import { Process, ProcessMap } from '../../../common/types/process_tree'; +import { Process, ProcessMap, ProcessEvent } from '../../../common/types/process_tree'; +import { ALERT_STATUS } from '../../../common/constants'; +import { UpdateAlertStatus } from '../../types'; import { + updateAlertEventStatus, updateProcessMap, buildProcessTree, searchProcessTree, @@ -20,8 +24,6 @@ const SESSION_ENTITY_ID = '3d0192c6-7c54-5ee6-a110-3539a7cf42bc'; const SEARCH_QUERY = 'vi'; const SEARCH_RESULT_PROCESS_ID = '8e4daeb2-4a4e-56c4-980e-f0dcfdbc3727'; -const mockEvents = mockData[0].events; - describe('process tree hook helpers tests', () => { let processMap: ProcessMap; @@ -73,4 +75,46 @@ describe('process tree hook helpers tests', () => { // session leader should have autoExpand to be true expect(processMap[SESSION_ENTITY_ID].autoExpand).toBeTruthy(); }); + + it('updateAlertEventStatus works', () => { + const events: ProcessEvent[] = JSON.parse(JSON.stringify([...mockEvents, ...mockAlerts])); + const updatedAlertsStatus: UpdateAlertStatus = { + [mockAlerts[0].kibana?.alert.uuid!]: { + status: ALERT_STATUS.CLOSED, + processEntityId: mockAlerts[0].process.entity_id, + }, + [mockAlerts[1].kibana?.alert.uuid!]: { + status: ALERT_STATUS.ACKNOWLEDGED, + processEntityId: mockAlerts[1].process.entity_id, + }, + }; + + expect( + events.find( + (event) => + event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[0].kibana?.alert.uuid + )?.kibana?.alert.workflow_status + ).toEqual(ALERT_STATUS.OPEN); + expect( + events.find( + (event) => + event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[1].kibana?.alert.uuid + )?.kibana?.alert.workflow_status + ).toEqual(ALERT_STATUS.OPEN); + + updateAlertEventStatus(events, updatedAlertsStatus); + + expect( + events.find( + (event) => + event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[0].kibana?.alert.uuid + )?.kibana?.alert.workflow_status + ).toEqual(ALERT_STATUS.CLOSED); + expect( + events.find( + (event) => + event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[1].kibana?.alert.uuid + )?.kibana?.alert.workflow_status + ).toEqual(ALERT_STATUS.ACKNOWLEDGED); + }); }); From 07e5811ce301979d2c0668a5b0e3ed178891e96e Mon Sep 17 00:00:00 2001 From: Zizhou Wang Date: Tue, 22 Mar 2022 12:10:43 -0400 Subject: [PATCH 7/7] Fix PR comments --- .../constants/session_view_process.mock.ts | 3 ++ .../common/types/process_tree/index.ts | 8 +++++ .../components/process_tree/helpers.test.ts | 21 ++++++------ .../public/components/process_tree/helpers.ts | 33 ++++++++++++++----- .../public/components/process_tree/hooks.ts | 10 ++++-- .../public/components/process_tree/index.tsx | 10 ++++-- .../public/components/session_view/hooks.ts | 19 +++++++---- .../public/components/session_view/index.tsx | 6 ++-- x-pack/plugins/session_view/public/types.ts | 7 ---- 9 files changed, 77 insertions(+), 40 deletions(-) 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 83cd250d45691..f9ace9fee7a75 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 @@ -920,6 +920,7 @@ export const childProcessMock: Process = { hasOutput: () => false, hasAlerts: () => false, getAlerts: () => [], + updateAlertsStatus: (_) => undefined, hasExec: () => false, getOutput: () => '', getDetails: () => @@ -998,6 +999,7 @@ export const processMock: Process = { hasOutput: () => false, hasAlerts: () => false, getAlerts: () => [], + updateAlertsStatus: (_) => undefined, hasExec: () => false, getOutput: () => '', getDetails: () => @@ -1173,6 +1175,7 @@ export const mockProcessMap = mockEvents.reduce( hasOutput: () => false, hasAlerts: () => false, getAlerts: () => [], + updateAlertsStatus: (_) => undefined, hasExec: () => false, getOutput: () => '', getDetails: () => event, 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 746c1b2093661..3475e8d425908 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 @@ -5,6 +5,13 @@ * 2.0. */ +export interface AlertStatusEventEntityIdMap { + [alertUuid: string]: { + status: string; + processEntityId: string; + }; +} + export const enum EventKind { event = 'event', signal = 'signal', @@ -150,6 +157,7 @@ export interface Process { hasOutput(): boolean; hasAlerts(): boolean; getAlerts(): ProcessEvent[]; + updateAlertsStatus(updatedAlertsStatus: AlertStatusEventEntityIdMap): void; hasExec(): boolean; getOutput(): string; getDetails(): ProcessEvent; diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts index 3a4d7dd13ebdb..39947da471499 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.test.ts @@ -4,14 +4,19 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { cloneDeep } from 'lodash'; import { mockEvents, mockAlerts, mockProcessMap, } from '../../../common/mocks/constants/session_view_process.mock'; -import { Process, ProcessMap, ProcessEvent } from '../../../common/types/process_tree'; +import { + AlertStatusEventEntityIdMap, + Process, + ProcessMap, + ProcessEvent, +} from '../../../common/types/process_tree'; import { ALERT_STATUS } from '../../../common/constants'; -import { UpdateAlertStatus } from '../../types'; import { updateAlertEventStatus, updateProcessMap, @@ -77,16 +82,12 @@ describe('process tree hook helpers tests', () => { }); it('updateAlertEventStatus works', () => { - const events: ProcessEvent[] = JSON.parse(JSON.stringify([...mockEvents, ...mockAlerts])); - const updatedAlertsStatus: UpdateAlertStatus = { + let events: ProcessEvent[] = cloneDeep([...mockEvents, ...mockAlerts]); + const updatedAlertsStatus: AlertStatusEventEntityIdMap = { [mockAlerts[0].kibana?.alert.uuid!]: { status: ALERT_STATUS.CLOSED, processEntityId: mockAlerts[0].process.entity_id, }, - [mockAlerts[1].kibana?.alert.uuid!]: { - status: ALERT_STATUS.ACKNOWLEDGED, - processEntityId: mockAlerts[1].process.entity_id, - }, }; expect( @@ -102,7 +103,7 @@ describe('process tree hook helpers tests', () => { )?.kibana?.alert.workflow_status ).toEqual(ALERT_STATUS.OPEN); - updateAlertEventStatus(events, updatedAlertsStatus); + events = updateAlertEventStatus(events, updatedAlertsStatus); expect( events.find( @@ -115,6 +116,6 @@ describe('process tree hook helpers tests', () => { (event) => event.kibana?.alert.uuid && event.kibana?.alert.uuid === mockAlerts[1].kibana?.alert.uuid )?.kibana?.alert.workflow_status - ).toEqual(ALERT_STATUS.ACKNOWLEDGED); + ).toEqual(ALERT_STATUS.OPEN); }); }); diff --git a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts index d8ec67569c206..df4a6cf70abec 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/helpers.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/helpers.ts @@ -4,22 +4,39 @@ * 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 { UpdateAlertStatus } from '../../types'; +import { + AlertStatusEventEntityIdMap, + Process, + ProcessEvent, + ProcessMap, +} from '../../../common/types/process_tree'; import { ProcessImpl } from './hooks'; // if given event is an alert, and it exist in updatedAlertsStatus, update the alert's status // with the updated status value in updatedAlertsStatus Map export const updateAlertEventStatus = ( events: ProcessEvent[], - updatedAlertsStatus: UpdateAlertStatus -) => { - events.forEach((event) => { - if (event.kibana?.alert.uuid && updatedAlertsStatus[event.kibana.alert.uuid]) { - event.kibana.alert.workflow_status = updatedAlertsStatus[event.kibana.alert.uuid].status; + updatedAlertsStatus: AlertStatusEventEntityIdMap +) => + events.map((event) => { + // do nothing if event is not an alert + if (!event.kibana) { + return event; } + + return { + ...event, + kibana: { + ...event.kibana, + alert: { + ...event.kibana.alert, + workflow_status: + updatedAlertsStatus[event.kibana.alert?.uuid]?.status ?? + event.kibana.alert?.workflow_status, + }, + }, + }; }); -}; // given a page of new events, add these events to the appropriate process class model // create a new process if none are created and return the mutated processMap diff --git a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts index 77cb3aa7d1be4..fb00344d5e280 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/hooks.ts +++ b/x-pack/plugins/session_view/public/components/process_tree/hooks.ts @@ -8,6 +8,7 @@ import _ from 'lodash'; import memoizeOne from 'memoize-one'; import { useState, useEffect } from 'react'; import { + AlertStatusEventEntityIdMap, EventAction, EventKind, Process, @@ -22,13 +23,12 @@ import { autoExpandProcessTree, } from './helpers'; import { sortProcesses } from '../../../common/utils/sort_processes'; -import { UpdateAlertStatus } from '../../types'; interface UseProcessTreeDeps { sessionEntityId: string; data: ProcessEventsPage[]; searchQuery?: string; - updatedAlertsStatus: UpdateAlertStatus; + updatedAlertsStatus: AlertStatusEventEntityIdMap; } export class ProcessImpl implements Process { @@ -110,6 +110,10 @@ export class ProcessImpl implements Process { return this.filterEventsByKind(this.events, EventKind.signal); } + updateAlertsStatus(updatedAlertsStatus: AlertStatusEventEntityIdMap) { + this.events = updateAlertEventStatus(this.events, updatedAlertsStatus); + } + hasExec() { return !!this.findEventByAction(this.events, EventAction.exec); } @@ -267,7 +271,7 @@ export const useProcessTree = ({ Object.keys(updatedAlertsStatus).forEach((alertUuid) => { const process = processMap[updatedAlertsStatus[alertUuid].processEntityId]; if (process) { - updateAlertEventStatus(process.getAlerts(), updatedAlertsStatus); + process.updateAlertsStatus(updatedAlertsStatus); } }); diff --git a/x-pack/plugins/session_view/public/components/process_tree/index.tsx b/x-pack/plugins/session_view/public/components/process_tree/index.tsx index 69449d6761b2b..4b489797c7e26 100644 --- a/x-pack/plugins/session_view/public/components/process_tree/index.tsx +++ b/x-pack/plugins/session_view/public/components/process_tree/index.tsx @@ -10,8 +10,12 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { ProcessTreeNode } from '../process_tree_node'; import { BackToInvestigatedAlert } from '../back_to_investigated_alert'; import { useProcessTree } from './hooks'; -import { Process, ProcessEventsPage, ProcessEvent } from '../../../common/types/process_tree'; -import { UpdateAlertStatus } from '../../types'; +import { + AlertStatusEventEntityIdMap, + Process, + ProcessEventsPage, + ProcessEvent, +} from '../../../common/types/process_tree'; import { useScroll } from '../../hooks/use_scroll'; import { useStyles } from './styles'; @@ -39,7 +43,7 @@ export interface ProcessTreeDeps { setSearchResults?: (results: Process[]) => void; // a map for alerts with updated status and process.entity_id - updatedAlertsStatus: UpdateAlertStatus; + updatedAlertsStatus: AlertStatusEventEntityIdMap; loadAlertDetails?: (alertUuid: string, handleOnAlertDetailsClosed: () => void) => void; handleOnAlertDetailsClosed: (alertUuid: string) => void; timeStampOn?: boolean; diff --git a/x-pack/plugins/session_view/public/components/session_view/hooks.ts b/x-pack/plugins/session_view/public/components/session_view/hooks.ts index 3295ff81d0ee0..a134a366c4168 100644 --- a/x-pack/plugins/session_view/public/components/session_view/hooks.ts +++ b/x-pack/plugins/session_view/public/components/session_view/hooks.ts @@ -9,8 +9,11 @@ import { useQuery, useInfiniteQuery } from 'react-query'; import { EuiSearchBarOnChangeArgs } from '@elastic/eui'; import { CoreStart } from 'kibana/public'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { ProcessEvent, ProcessEventResults } from '../../../common/types/process_tree'; -import { UpdateAlertStatus } from '../../types'; +import { + AlertStatusEventEntityIdMap, + ProcessEvent, + ProcessEventResults, +} from '../../../common/types/process_tree'; import { PROCESS_EVENTS_ROUTE, PROCESS_EVENTS_PER_PAGE, @@ -81,10 +84,13 @@ export const useFetchSessionViewProcessEvents = ( return query; }; -export const useFetchAlertStatus = (updatedAlertsStatus: UpdateAlertStatus, alertUuid: string) => { +export const useFetchAlertStatus = ( + updatedAlertsStatus: AlertStatusEventEntityIdMap, + alertUuid: string +) => { const { http } = useKibana().services; const cachingKeys = [QUERY_KEY_ALERTS, alertUuid]; - const query = useQuery( + const query = useQuery( cachingKeys, async () => { if (!alertUuid) { @@ -97,13 +103,14 @@ export const useFetchAlertStatus = (updatedAlertsStatus: UpdateAlertStatus, aler }, }); + // TODO: add error handling const events = res.events.map((event: any) => event._source as ProcessEvent); return { ...updatedAlertsStatus, [alertUuid]: { - status: events[0].kibana?.alert.workflow_status ?? '', - processEntityId: events[0].process.entity_id, + status: events[0]?.kibana?.alert.workflow_status ?? '', + processEntityId: events[0]?.process?.entity_id ?? '', }, }; }, diff --git a/x-pack/plugins/session_view/public/components/session_view/index.tsx b/x-pack/plugins/session_view/public/components/session_view/index.tsx index 65a12ca3c3e61..af4eb6114a0a2 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.tsx @@ -16,9 +16,9 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { SectionLoading } from '../../shared_imports'; import { ProcessTree } from '../process_tree'; -import { Process } from '../../../common/types/process_tree'; +import { AlertStatusEventEntityIdMap, Process } from '../../../common/types/process_tree'; import { DisplayOptionsState } from '../../../common/types/session_view'; -import { SessionViewDeps, UpdateAlertStatus } from '../../types'; +import { SessionViewDeps } from '../../types'; import { SessionViewDetailPanel } from '../session_view_detail_panel'; import { SessionViewSearchBar } from '../session_view_search_bar'; import { SessionViewDisplayOptions } from '../session_view_display_options'; @@ -43,7 +43,7 @@ export const SessionView = ({ verboseMode: true, }); const [fetchAlertStatus, setFetchAlertStatus] = useState([]); - const [updatedAlertsStatus, setUpdatedAlertsStatus] = useState({}); + const [updatedAlertsStatus, setUpdatedAlertsStatus] = useState({}); const styles = useStyles({ height }); diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts index 28c61d6744b7a..3a7ef376bd426 100644 --- a/x-pack/plugins/session_view/public/types.ts +++ b/x-pack/plugins/session_view/public/types.ts @@ -28,13 +28,6 @@ export interface SessionViewDeps { ) => void; } -export interface UpdateAlertStatus { - [key: string]: { - status: string; - processEntityId: string; - }; -} - export interface EuiTabProps { id: string; name: string;