diff --git a/x-pack/plugins/security_solution/common/endpoint/models/event.ts b/x-pack/plugins/security_solution/common/endpoint/models/event.ts index 2c325d64f8515..9eea6bf320db8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/event.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/event.ts @@ -52,23 +52,3 @@ export function parentEntityId(event: ResolverEvent): string | undefined { } return event.process.parent?.entity_id; } - -export function eventType(event: ResolverEvent): string { - // Returning "Process" as a catch-all here because it seems pretty general - let eventCategoryToReturn: string = 'Process'; - if (isLegacyEvent(event)) { - const legacyFullType = event.endgame.event_type_full; - if (legacyFullType) { - return legacyFullType; - } - } else { - const eventCategories = event.event.category; - const eventCategory = - typeof eventCategories === 'string' ? eventCategories : eventCategories[0] || ''; - - if (eventCategory) { - eventCategoryToReturn = eventCategory; - } - } - return eventCategoryToReturn; -} diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts index f6ef7b16ae682..96552fbed6207 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/action.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/action.ts @@ -4,26 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ResolverEvent } from '../../../../common/endpoint/types'; -import { RelatedEventDataEntry } from '../../types'; +import { ResolverEvent, ResolverNodeStats } from '../../../../common/endpoint/types'; interface ServerReturnedResolverData { readonly type: 'serverReturnedResolverData'; - readonly payload: ResolverEvent[]; + readonly events: ResolverEvent[]; + readonly stats: Map; } interface ServerFailedToReturnResolverData { readonly type: 'serverFailedToReturnResolverData'; } -/** - * Will occur when a request for related event data is fulfilled by the API. - */ -interface ServerReturnedRelatedEventData { - readonly type: 'serverReturnedRelatedEventData'; - readonly payload: Map; -} - /** * Will occur when a request for related event data is unsuccessful. */ @@ -35,5 +27,4 @@ interface ServerFailedToReturnRelatedEventData { export type DataAction = | ServerReturnedResolverData | ServerFailedToReturnResolverData - | ServerReturnedRelatedEventData | ServerFailedToReturnRelatedEventData; diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/graphing.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/graphing.test.ts index 69edf3b4c134d..d120adb72cd81 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/graphing.test.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/graphing.test.ts @@ -113,8 +113,8 @@ describe('resolver graph layout', () => { }); describe('when rendering no nodes', () => { beforeEach(() => { - const payload: ResolverEvent[] = []; - const action: DataAction = { type: 'serverReturnedResolverData', payload }; + const events: ResolverEvent[] = []; + const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() }; store.dispatch(action); }); it('the graphableProcesses list should only include nothing', () => { @@ -127,8 +127,8 @@ describe('resolver graph layout', () => { }); describe('when rendering one node', () => { beforeEach(() => { - const payload = [processA]; - const action: DataAction = { type: 'serverReturnedResolverData', payload }; + const events = [processA]; + const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() }; store.dispatch(action); }); it('the graphableProcesses list should only include nothing', () => { @@ -141,8 +141,8 @@ describe('resolver graph layout', () => { }); describe('when rendering two nodes, one being the parent of the other', () => { beforeEach(() => { - const payload = [processA, processB]; - const action: DataAction = { type: 'serverReturnedResolverData', payload }; + const events = [processA, processB]; + const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() }; store.dispatch(action); }); it('the graphableProcesses list should only include nothing', () => { @@ -155,7 +155,7 @@ describe('resolver graph layout', () => { }); describe('when rendering two forks, and one fork has an extra long tine', () => { beforeEach(() => { - const payload = [ + const events = [ processA, processB, processC, @@ -166,7 +166,7 @@ describe('resolver graph layout', () => { processH, processI, ]; - const action: DataAction = { type: 'serverReturnedResolverData', payload }; + const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() }; store.dispatch(action); }); it("the graphableProcesses list should only include events with 'processCreated' an 'processRan' eventType", () => { diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts index 9dd6bcdf385ae..0c29411c316d4 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/reducer.ts @@ -10,9 +10,9 @@ import { DataState, ResolverAction } from '../../types'; function initialState(): DataState { return { results: [], + relatedEventsStats: new Map(), isLoading: false, hasError: false, - resultsEnrichedWithRelatedEventInfo: new Map(), }; } @@ -20,30 +20,11 @@ export const dataReducer: Reducer = (state = initialS if (action.type === 'serverReturnedResolverData') { return { ...state, - results: action.payload, + results: action.events, + relatedEventsStats: action.stats, isLoading: false, hasError: false, }; - } else if (action.type === 'userRequestedRelatedEventData') { - const resolverEvent = action.payload; - const currentStatsMap = new Map(state.resultsEnrichedWithRelatedEventInfo); - /** - * Set the waiting indicator for this event to indicate that related event results are pending. - * It will be replaced by the actual results from the API when they are returned. - */ - currentStatsMap.set(resolverEvent, 'waitingForRelatedEventData'); - return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; - } else if (action.type === 'serverFailedToReturnRelatedEventData') { - const currentStatsMap = new Map(state.resultsEnrichedWithRelatedEventInfo); - const resolverEvent = action.payload; - currentStatsMap.set(resolverEvent, 'error'); - return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap }; - } else if (action.type === 'serverReturnedRelatedEventData') { - const relatedDataEntries = new Map([ - ...state.resultsEnrichedWithRelatedEventInfo, - ...action.payload, - ]); - return { ...state, resultsEnrichedWithRelatedEventInfo: relatedDataEntries }; } else if (action.type === 'appRequestedResolverData') { return { ...state, diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts deleted file mode 100644 index f6d2b978d6b4d..0000000000000 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts +++ /dev/null @@ -1,59 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Store, createStore } from 'redux'; -import { DataAction } from './action'; -import { dataReducer } from './reducer'; -import { - DataState, - RelatedEventDataEntry, - RelatedEventDataEntryWithStats, - RelatedEventData, -} from '../../types'; -import { ResolverEvent } from '../../../../common/endpoint/types'; -import { relatedEventStats, relatedEvents } from './selectors'; - -describe('resolver data selectors', () => { - const store: Store = createStore(dataReducer, undefined); - describe('when related event data is reduced into state with no results', () => { - let relatedEventInfoBeforeAction: RelatedEventData; - beforeEach(() => { - relatedEventInfoBeforeAction = new Map(relatedEvents(store.getState()) || []); - const payload: Map = new Map(); - const action: DataAction = { type: 'serverReturnedRelatedEventData', payload }; - store.dispatch(action); - }); - it('should have the same related info as before the action', () => { - const relatedInfoAfterAction = relatedEvents(store.getState()); - expect(relatedInfoAfterAction).toEqual(relatedEventInfoBeforeAction); - }); - }); - describe('when related event data is reduced into state with 2 dns results', () => { - let mockBaseEvent: ResolverEvent; - beforeEach(() => { - mockBaseEvent = {} as ResolverEvent; - function dnsRelatedEventEntry() { - const fakeEvent = {} as ResolverEvent; - return { relatedEvent: fakeEvent, relatedEventType: 'dns' }; - } - const payload: Map = new Map([ - [ - mockBaseEvent, - { - relatedEvents: [dnsRelatedEventEntry(), dnsRelatedEventEntry()], - }, - ], - ]); - const action: DataAction = { type: 'serverReturnedRelatedEventData', payload }; - store.dispatch(action); - }); - it('should compile stats reflecting a count of 2 for dns', () => { - const actualStats = relatedEventStats(store.getState()); - const statsForFakeEvent = actualStats.get(mockBaseEvent)! as RelatedEventDataEntryWithStats; - expect(statsForFakeEvent.stats).toEqual({ dns: 2 }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 792afe7642e0e..6904107eeebb5 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts @@ -15,8 +15,6 @@ import { Matrix3, AdjacentProcessMap, Vector2, - RelatedEventData, - RelatedEventDataEntryWithStats, } from '../../types'; import { ResolverEvent } from '../../../../common/endpoint/types'; @@ -410,85 +408,11 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in }); /** - * Process events that will be graphed. + * This returns a map of entity_ids to stats about the related events and alerts. */ -export const relatedEventResults = function (data: DataState) { - return data.resultsEnrichedWithRelatedEventInfo; -}; - -/** - * This selector compiles the related event data attached in `relatedEventResults` - * into a `RelatedEventData` map of ResolverEvents to statistics about their related events - */ -export const relatedEventStats = createSelector(relatedEventResults, function getRelatedEvents( - /* eslint-disable no-shadow */ - relatedEventResults - /* eslint-enable no-shadow */ -) { - /* eslint-disable no-shadow */ - const relatedEventStats: RelatedEventData = new Map(); - /* eslint-enable no-shadow */ - if (!relatedEventResults) { - return relatedEventStats; - } - - for (const updatedEvent of relatedEventResults.keys()) { - const newStatsEntry = relatedEventResults.get(updatedEvent); - if (newStatsEntry === 'error') { - // If the entry is an error, return it as is - relatedEventStats.set(updatedEvent, newStatsEntry); - // eslint-disable-next-line no-continue - continue; - } - if (typeof newStatsEntry === 'object') { - /** - * Otherwise, it should be a valid stats entry. - * Do the work to compile the stats. - * Folowing reduction, this will be a record like - * {DNS: 10, File: 2} etc. - */ - const statsForEntry = newStatsEntry?.relatedEvents.reduce( - (compiledStats: Record, relatedEvent: { relatedEventType: string }) => { - compiledStats[relatedEvent.relatedEventType] = - (compiledStats[relatedEvent.relatedEventType] || 0) + 1; - return compiledStats; - }, - {} - ); - - const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign(newStatsEntry, { - stats: statsForEntry, - }); - relatedEventStats.set(updatedEvent, newRelatedEventStats); - } - } - return relatedEventStats; -}); - -/** - * This selects `RelatedEventData` maps specifically for graphable processes - */ -export const relatedEvents = createSelector( - graphableProcesses, - relatedEventStats, - function getRelatedEvents( - /* eslint-disable no-shadow */ - graphableProcesses, - relatedEventStats - /* eslint-enable no-shadow */ - ) { - const eventsRelatedByProcess: RelatedEventData = new Map(); - /* eslint-disable no-shadow */ - return graphableProcesses.reduce((relatedEvents, graphableProcess) => { - /* eslint-enable no-shadow */ - const relatedEventDataEntry = relatedEventStats?.get(graphableProcess); - if (relatedEventDataEntry) { - relatedEvents.set(graphableProcess, relatedEventDataEntry); - } - return relatedEvents; - }, eventsRelatedByProcess); - } -); +export function relatedEventsStats(data: DataState) { + return data.relatedEventsStats; +} export const processAdjacencies = createSelector( indexedProcessTree, diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts index 038163a164c6c..079eecf0315a5 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts @@ -5,16 +5,15 @@ */ import { Dispatch, MiddlewareAPI } from 'redux'; -import { HttpHandler } from 'kibana/public'; import { KibanaReactContextValue } from '../../../../../../src/plugins/kibana_react/public'; import { StartServices } from '../../types'; -import { ResolverState, ResolverAction, RelatedEventDataEntry } from '../types'; +import { ResolverState, ResolverAction } from '../types'; import { ResolverEvent, ResolverChildren, ResolverAncestry, LifecycleNode, - ResolverRelatedEvents, + ResolverNodeStats, } from '../../../common/endpoint/types'; import * as event from '../../../common/endpoint/models/event'; @@ -24,41 +23,21 @@ type MiddlewareFactory = ( api: MiddlewareAPI, S> ) => (next: Dispatch) => (action: ResolverAction) => unknown; -function getLifecycleEvents(nodes: LifecycleNode[], events: ResolverEvent[] = []): ResolverEvent[] { - return nodes.reduce((flattenedEvents, currentNode) => { +function getLifecycleEventsAndStats( + nodes: LifecycleNode[], + stats: Map +): ResolverEvent[] { + return nodes.reduce((flattenedEvents: ResolverEvent[], currentNode: LifecycleNode) => { if (currentNode.lifecycle && currentNode.lifecycle.length > 0) { flattenedEvents.push(...currentNode.lifecycle); } - return flattenedEvents; - }, events); -} - -type RelatedEventAPIResponse = 'error' | ResolverRelatedEvents; -/** - * As the design goal of this stopgap was to prevent saturating the server with /events - * requests, this generator intentionally processes events in serial rather than in parallel. - * @param eventsToFetch - * events to run against the /id/events API - * @param httpGetter - * the HttpHandler to use - */ -async function* getEachRelatedEventsResult( - eventsToFetch: ResolverEvent[], - httpGetter: HttpHandler -): AsyncGenerator<[ResolverEvent, RelatedEventAPIResponse]> { - for (const eventToQueryForRelateds of eventsToFetch) { - const id = event.entityId(eventToQueryForRelateds); - let result: RelatedEventAPIResponse; - try { - result = await httpGetter(`/api/endpoint/resolver/${id}/events`, { - query: { events: 100 }, - }); - } catch (e) { - result = 'error'; + if (currentNode.stats) { + stats.set(currentNode.entityID, currentNode.stats); } - yield [eventToQueryForRelateds, result]; - } + + return flattenedEvents; + }, []); } export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { @@ -74,17 +53,19 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { let lifecycle: ResolverEvent[]; let children: ResolverChildren; let ancestry: ResolverAncestry; + let entityId: string; + let stats: ResolverNodeStats; if (event.isLegacyEvent(action.payload.selectedEvent)) { - const entityId = action.payload.selectedEvent?.endgame?.unique_pid; + entityId = action.payload.selectedEvent?.endgame?.unique_pid.toString(); const legacyEndpointID = action.payload.selectedEvent?.agent?.id; - [{ lifecycle, children, ancestry }] = await Promise.all([ + [{ lifecycle, children, ancestry, stats }] = await Promise.all([ context.services.http.get(`/api/endpoint/resolver/${entityId}`, { query: { legacyEndpointID, children: 5, ancestors: 5 }, }), ]); } else { - const entityId = action.payload.selectedEvent.process.entity_id; - [{ lifecycle, children, ancestry }] = await Promise.all([ + entityId = action.payload.selectedEvent.process.entity_id; + [{ lifecycle, children, ancestry, stats }] = await Promise.all([ context.services.http.get(`/api/endpoint/resolver/${entityId}`, { query: { children: 5, @@ -93,14 +74,17 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { }), ]); } - const response: ResolverEvent[] = [ + const nodeStats: Map = new Map(); + nodeStats.set(entityId, stats); + const events = [ ...lifecycle, - ...getLifecycleEvents(children.childNodes), - ...getLifecycleEvents(ancestry.ancestors), + ...getLifecycleEventsAndStats(children.childNodes, nodeStats), + ...getLifecycleEventsAndStats(ancestry.ancestors, nodeStats), ]; api.dispatch({ type: 'serverReturnedResolverData', - payload: response, + events, + stats: nodeStats, }); } catch (error) { api.dispatch({ @@ -109,45 +93,5 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { } } } - - if (action.type === 'userRequestedRelatedEventData') { - if (typeof context !== 'undefined') { - const response: Map = new Map(); - for await (const results of getEachRelatedEventsResult( - [action.payload], - context.services.http.get - )) { - /** - * results here will take the shape of - * [event requested , response of event against the /related api] - */ - const [baseEvent, apiResults] = results; - if (apiResults === 'error') { - api.dispatch({ - type: 'serverFailedToReturnRelatedEventData', - payload: results[0], - }); - // eslint-disable-next-line no-continue - continue; - } - - const fetchedResults = apiResults.events; - // pack up the results into response - const relatedEventEntry = fetchedResults.map((relatedEvent) => { - return { - relatedEvent, - relatedEventType: event.eventType(relatedEvent), - }; - }); - - response.set(baseEvent, { relatedEvents: relatedEventEntry }); - } - - api.dispatch({ - type: 'serverReturnedRelatedEventData', - payload: response, - }); - } - } }; }; diff --git a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts index 82b722bb986ef..270a84d24a991 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/selectors.ts @@ -61,9 +61,12 @@ export const processAdjacencies = composeSelectors( ); /** - * Returns a map of `ResolverEvent`s to their related `ResolverEvent`s + * Returns a map of `ResolverEvent` entity_id to their related event and alert statistics */ -export const relatedEvents = composeSelectors(dataStateSelector, dataSelectors.relatedEvents); +export const relatedEventsStats = composeSelectors( + dataStateSelector, + dataSelectors.relatedEventsStats +); /** * Returns the id of the "current" tree node (fake-focused) diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts index e93a7856557cc..ecdd032a6c1d7 100644 --- a/x-pack/plugins/security_solution/public/resolver/types.ts +++ b/x-pack/plugins/security_solution/public/resolver/types.ts @@ -8,8 +8,7 @@ import { Store } from 'redux'; import { ResolverAction } from './store/actions'; export { ResolverAction } from './store/actions'; -import { ResolverEvent } from '../../common/endpoint/types'; -import { eventType } from '../../common/endpoint/models/event'; +import { ResolverEvent, ResolverNodeStats } from '../../common/endpoint/types'; /** * Redux state for the Resolver feature. Properties on this interface are populated via multiple reducers using redux's `combineReducers`. @@ -131,60 +130,14 @@ export type CameraState = { } ); -/** - * This represents all the raw data (sans statistics, metadata, etc.) - * about a particular subject's related events - */ -export interface RelatedEventDataEntry { - relatedEvents: Array<{ - relatedEvent: ResolverEvent; - relatedEventType: ReturnType; - }>; -} - -/** - * Represents the status of the request for related event data, which will be either the data, - * a value indicating that it's still waiting for the data or an Error indicating the data can't be retrieved as expected - */ -export type RelatedEventDataResults = - | RelatedEventDataEntry - | 'waitingForRelatedEventData' - | 'error'; - -/** - * This represents the raw related events data enhanced with statistics - * (e.g. counts of items grouped by their related event types) - */ -export type RelatedEventDataEntryWithStats = RelatedEventDataEntry & { - stats: Record; -}; - -/** - * The status or value of any particular event's related events w.r.t. their valence to the current view. - * One of: - * `RelatedEventDataEntryWithStats` when results have been received and processed and are ready to display - * `waitingForRelatedEventData` when related events have been requested but have not yet matriculated - * `Error` when the request for any event encounters an error during service - */ -export type RelatedEventEntryWithStatsOrWaiting = - | RelatedEventDataEntryWithStats - | `waitingForRelatedEventData` - | 'error'; - -/** - * This represents a Map that will return either a `RelatedEventDataEntryWithStats` - * or a `waitingForRelatedEventData` symbol when referenced with a unique event. - */ -export type RelatedEventData = Map; - /** * State for `data` reducer which handles receiving Resolver data from the backend. */ export interface DataState { readonly results: readonly ResolverEvent[]; + readonly relatedEventsStats: Map; isLoading: boolean; hasError: boolean; - resultsEnrichedWithRelatedEventInfo: Map; } export type Vector2 = readonly [number, number]; diff --git a/x-pack/plugins/security_solution/public/resolver/view/index.tsx b/x-pack/plugins/security_solution/public/resolver/view/index.tsx index cae5ba97f413c..9b336d6916bdf 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/index.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/index.tsx @@ -18,6 +18,7 @@ import { useCamera } from './use_camera'; import { SymbolDefinitions, NamedColors } from './defs'; import { ResolverAction } from '../types'; import { ResolverEvent } from '../../../common/endpoint/types'; +import * as eventModel from '../../../common/endpoint/models/event'; const StyledPanel = styled(Panel)` position: absolute; @@ -51,7 +52,7 @@ export const Resolver = styled( const dispatch: (action: ResolverAction) => unknown = useDispatch(); const { processToAdjacencyMap } = useSelector(selectors.processAdjacencies); - const relatedEvents = useSelector(selectors.relatedEvents); + const relatedEventsStats = useSelector(selectors.relatedEventsStats); const { projectionMatrix, ref, onMouseDown } = useCamera(); const isLoading = useSelector(selectors.isLoading); const hasError = useSelector(selectors.hasError); @@ -110,7 +111,7 @@ export const Resolver = styled( projectionMatrix={projectionMatrix} event={processEvent} adjacentNodeMap={adjacentNodeMap} - relatedEvents={relatedEvents.get(processEvent)} + relatedEventsStats={relatedEventsStats.get(eventModel.entityId(processEvent))} /> ); })} diff --git a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx index 0d86cf45405a9..03903b722427a 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx @@ -17,15 +17,9 @@ import { import { useSelector } from 'react-redux'; import { NodeSubMenu, subMenuAssets } from './submenu'; import { applyMatrix3 } from '../lib/vector2'; -import { - Vector2, - Matrix3, - AdjacentProcessMap, - ResolverProcessType, - RelatedEventEntryWithStatsOrWaiting, -} from '../types'; +import { Vector2, Matrix3, AdjacentProcessMap, ResolverProcessType } from '../types'; import { SymbolIds, NamedColors } from './defs'; -import { ResolverEvent } from '../../../common/endpoint/types'; +import { ResolverEvent, ResolverNodeStats } from '../../../common/endpoint/types'; import { useResolverDispatch } from './use_resolver_dispatch'; import * as eventModel from '../../../common/endpoint/models/event'; import * as processModel from '../models/process_event'; @@ -248,7 +242,7 @@ const ProcessEventDotComponents = React.memo( event, projectionMatrix, adjacentNodeMap, - relatedEvents, + relatedEventsStats, }: { /** * A `className` string provided by `styled` @@ -271,10 +265,9 @@ const ProcessEventDotComponents = React.memo( */ adjacentNodeMap: AdjacentProcessMap; /** - * A collection of events related to the current node and statistics (e.g. counts indexed by event type) - * to provide the user some visibility regarding the contents thereof. + * Statistics for the number of related events and alerts for this process node */ - relatedEvents?: RelatedEventEntryWithStatsOrWaiting; + relatedEventsStats?: ResolverNodeStats; }) => { /** * Convert the position, which is in 'world' coordinates, to screen coordinates. @@ -395,48 +388,34 @@ const ProcessEventDotComponents = React.memo( * e.g. "10 DNS", "230 File" */ const relatedEventOptions = useMemo(() => { - if (relatedEvents === 'error') { - // Return an empty set of options if there was an error requesting them - return []; - } - const relatedStats = typeof relatedEvents === 'object' && relatedEvents.stats; - if (!relatedStats) { + if (!relatedEventsStats) { // Return an empty set of options if there are no stats to report return []; } // If we have entries to show, map them into options to display in the selectable list - return Object.entries(relatedStats).map((statsEntry) => { - const displayName = getDisplayName(statsEntry[0]); + return Object.entries(relatedEventsStats.events.byCategory).map(([category, total]) => { + const displayName = getDisplayName(category); return { - prefix: , + prefix: , optionTitle: `${displayName}`, action: () => { dispatch({ type: 'userSelectedRelatedEventCategory', payload: { subject: event, - category: statsEntry[0], + category, }, }); }, }; }); - }, [relatedEvents, dispatch, event]); + }, [relatedEventsStats, dispatch, event]); const relatedEventStatusOrOptions = (() => { - if (!relatedEvents) { - // If related events have not yet been requested + if (!relatedEventsStats) { return subMenuAssets.initialMenuStatus; } - if (relatedEvents === 'error') { - // If there was an error when we tried to request the events - return subMenuAssets.menuError; - } - if (relatedEvents === 'waitingForRelatedEventData') { - // If we're waiting for events to be returned - // Pass on the waiting symbol - return relatedEvents; - } + return relatedEventOptions; })(); diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx b/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx index f8aa4fbca3291..4f66322a247a6 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx @@ -176,7 +176,8 @@ describe('useCamera on an unpainted element', () => { } const serverResponseAction: ResolverAction = { type: 'serverReturnedResolverData', - payload: events, + events, + stats: new Map(), }; act(() => { store.dispatch(serverResponseAction);