From 1601fa407d87083c5ea87c1052cef34434f7fbdd Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Mon, 8 Jun 2020 17:49:45 -0400 Subject: [PATCH 1/6] Removing a lot of the code to request related events and create the maps --- .../common/endpoint/models/event.ts | 20 ----- .../public/resolver/store/data/action.ts | 15 +--- .../public/resolver/store/data/reducer.ts | 25 +------ .../public/resolver/store/data/selectors.ts | 72 +++--------------- .../public/resolver/store/middleware.ts | 74 ++++++------------- .../public/resolver/store/selectors.ts | 7 +- .../public/resolver/types.ts | 51 +------------ .../public/resolver/view/index.tsx | 5 +- .../resolver/view/process_event_dot.tsx | 49 ++++-------- 9 files changed, 65 insertions(+), 253 deletions(-) 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/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.ts b/x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts index 792afe7642e0e..abd1d55daa74a 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,77 +408,25 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in }); /** - * Process events that will be graphed. + * Statistics for each node in the resolver tree. */ -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; -}); +/* export const relatedEventsStats = function (data: DataState) { + return data.relatedEventsStats; +};*/ /** * This selects `RelatedEventData` maps specifically for graphable processes + * TODO does this HAVE to be for only graphable processes? it's just the stats does it matter? */ -export const relatedEvents = createSelector( +/* 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); @@ -488,6 +434,12 @@ export const relatedEvents = createSelector( return relatedEvents; }, eventsRelatedByProcess); } +);*/ + +// Heeeallp +export const relatedEventsStats = createSelector( + (data: DataState) => data.relatedEventsStats, + (stats) => stats ); export const processAdjacencies = createSelector( 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..b7ba868d9bfe8 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware.ts @@ -8,13 +8,14 @@ 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,17 +25,25 @@ 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); } + if (currentNode.stats) { + stats.set(currentNode.entityID, currentNode.stats); + } + return flattenedEvents; - }, events); + }, []); } type RelatedEventAPIResponse = 'error' | ResolverRelatedEvents; +// TODO heaaaalppp we probably want to keep this since Brent will need it??? /** * 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. @@ -74,17 +83,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; const legacyEndpointID = action.payload.selectedEvent?.agent?.id; - [{ lifecycle, children, ancestry }] = await Promise.all([ + [{ lifecycle, children, ancestry, entityID, 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([ + [{ lifecycle, children, ancestry, entityID, stats }] = await Promise.all([ context.services.http.get(`/api/endpoint/resolver/${entityId}`, { query: { children: 5, @@ -93,14 +104,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 +123,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..c6f8b94367781 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,32 @@ 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).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 - 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; - } + // TODO make the drop down visible immediately if there are stats + // Heeeallp TODO return relatedEventOptions; })(); From c4a3d310e92bb6b9647ebaea8eed541df6ac6110 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Fri, 12 Jun 2020 11:20:36 -0400 Subject: [PATCH 2/6] Correctly displaying stats --- .../public/resolver/store/data/selectors.ts | 29 ------------------- .../resolver/view/process_event_dot.tsx | 6 +++- 2 files changed, 5 insertions(+), 30 deletions(-) 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 abd1d55daa74a..5c327df633e2c 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 @@ -407,35 +407,6 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in return indexedProcessTreeFactory(graphableProcesses); }); -/** - * Statistics for each node in the resolver tree. - */ -/* export const relatedEventsStats = function (data: DataState) { - return data.relatedEventsStats; -};*/ - -/** - * This selects `RelatedEventData` maps specifically for graphable processes - * TODO does this HAVE to be for only graphable processes? it's just the stats does it matter? - */ -/* export const relatedEvents = createSelector( - graphableProcesses, - relatedEventStats, - function getRelatedEvents( - graphableProcesses, - relatedEventStats - ) { - const eventsRelatedByProcess: RelatedEventData = new Map(); - return graphableProcesses.reduce((relatedEvents, graphableProcess) => { - const relatedEventDataEntry = relatedEventStats?.get(graphableProcess); - if (relatedEventDataEntry) { - relatedEvents.set(graphableProcess, relatedEventDataEntry); - } - return relatedEvents; - }, eventsRelatedByProcess); - } -);*/ - // Heeeallp export const relatedEventsStats = createSelector( (data: DataState) => data.relatedEventsStats, 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 c6f8b94367781..14db3286d7d8a 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 @@ -393,7 +393,7 @@ const ProcessEventDotComponents = React.memo( return []; } // If we have entries to show, map them into options to display in the selectable list - return Object.entries(relatedEventsStats).map(([category, total]) => { + return Object.entries(relatedEventsStats.events.byCategory).map(([category, total]) => { const displayName = getDisplayName(category); return { prefix: , @@ -414,6 +414,10 @@ const ProcessEventDotComponents = React.memo( const relatedEventStatusOrOptions = (() => { // TODO make the drop down visible immediately if there are stats // Heeeallp TODO + if (!relatedEventsStats) { + return subMenuAssets.initialMenuStatus; + } + return relatedEventOptions; })(); From 6bd034dc81f48095c7a51e22580102ae111867d0 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Fri, 12 Jun 2020 11:27:20 -0400 Subject: [PATCH 3/6] Removing tests --- .../resolver/store/data/selectors.test.ts | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/resolver/store/data/selectors.test.ts 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 }); - }); - }); -}); From a06294fd9141db09faf0faee493ae2ad1fd25d22 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Fri, 12 Jun 2020 14:01:48 -0400 Subject: [PATCH 4/6] Addressing todos --- .../public/resolver/store/data/selectors.ts | 11 ++--- .../public/resolver/store/middleware.ts | 42 +++---------------- .../resolver/view/process_event_dot.tsx | 2 - 3 files changed, 12 insertions(+), 43 deletions(-) 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 5c327df633e2c..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 @@ -407,11 +407,12 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in return indexedProcessTreeFactory(graphableProcesses); }); -// Heeeallp -export const relatedEventsStats = createSelector( - (data: DataState) => data.relatedEventsStats, - (stats) => stats -); +/** + * This returns a map of entity_ids to stats about the related events and alerts. + */ +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 b7ba868d9bfe8..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,7 +5,6 @@ */ 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 } from '../types'; @@ -14,7 +13,6 @@ import { ResolverChildren, ResolverAncestry, LifecycleNode, - ResolverRelatedEvents, ResolverNodeStats, } from '../../../common/endpoint/types'; import * as event from '../../../common/endpoint/models/event'; @@ -42,34 +40,6 @@ function getLifecycleEventsAndStats( }, []); } -type RelatedEventAPIResponse = 'error' | ResolverRelatedEvents; -// TODO heaaaalppp we probably want to keep this since Brent will need it??? -/** - * 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'; - } - yield [eventToQueryForRelateds, result]; - } -} - export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { return (api) => (next) => async (action: ResolverAction) => { next(action); @@ -83,19 +53,19 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { let lifecycle: ResolverEvent[]; let children: ResolverChildren; let ancestry: ResolverAncestry; - let entityID: string; + 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, entityID, stats }] = 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, entityID, stats }] = 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, @@ -105,7 +75,7 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => { ]); } const nodeStats: Map = new Map(); - nodeStats.set(entityID, stats); + nodeStats.set(entityId, stats); const events = [ ...lifecycle, ...getLifecycleEventsAndStats(children.childNodes, nodeStats), 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 14db3286d7d8a..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 @@ -412,8 +412,6 @@ const ProcessEventDotComponents = React.memo( }, [relatedEventsStats, dispatch, event]); const relatedEventStatusOrOptions = (() => { - // TODO make the drop down visible immediately if there are stats - // Heeeallp TODO if (!relatedEventsStats) { return subMenuAssets.initialMenuStatus; } From 46b4c48c8fe0c0fc8470b0b2c64f1ee41c8fbadb Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Fri, 12 Jun 2020 15:07:36 -0400 Subject: [PATCH 5/6] Fixing test --- .../public/resolver/store/data/graphing.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) 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", () => { From 2ad5fbd3fa0e13c9d26c77e947cea8fb0012c8cc Mon Sep 17 00:00:00 2001 From: Jonathan Buttner Date: Fri, 12 Jun 2020 15:11:26 -0400 Subject: [PATCH 6/6] Fixing camera test --- .../security_solution/public/resolver/view/use_camera.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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);