Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display node 75% view submenus #64121

Merged
merged 81 commits into from
May 18, 2020
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
c4ab6e2
cleanup: move submenus into a general component
Apr 21, 2020
cfd95e8
remove unnecessary useState import
Apr 21, 2020
2c7b279
referral for help
Apr 22, 2020
5aef930
Use EUISelectable for options list
Apr 23, 2020
8ea107d
adding EUISelectable
Apr 23, 2020
ab58823
user-select off to fix bug Jonny found
Apr 27, 2020
42c9a3b
Revert "user-select off to fix bug Jonny found"
Apr 27, 2020
f166d6d
straighten corners on menu open to match mocks
Apr 27, 2020
6cb887b
adding type information for related events
Apr 27, 2020
36fe414
refine type information for related events
Apr 27, 2020
2429b4b
Node option submenus take waiting prop
Apr 28, 2020
41f4a79
Merge remote-tracking branch 'upstream/master' into resolver/lay-in-s…
Apr 28, 2020
8c454d4
merge upstream
Apr 28, 2020
747cd6e
stub selector for related events
Apr 29, 2020
72665e0
provision component with selected relatedEvents
Apr 29, 2020
a98cd7a
shape reducder
Apr 29, 2020
6e75074
adjust reducer for consistency
Apr 29, 2020
5529b7e
adding request logic to middleware
Apr 30, 2020
29e0018
investigating problems with /events and CLI
Apr 30, 2020
c7c22bf
pulling through selector
May 6, 2020
5631113
test rendering in component
May 6, 2020
e8d48db
cleanup unused
May 6, 2020
01ea4a1
more cleanup
May 6, 2020
c64d990
cleanup action
May 6, 2020
c7edee7
fix submenu options typings
May 6, 2020
2d1b3f2
move utility function off and seal it
May 6, 2020
4ad185d
fix middleware types
May 8, 2020
de039bf
adjust model to return Process
May 8, 2020
ffdb41f
K Qualters review: change action name
May 8, 2020
3a6314b
lint
May 8, 2020
12af60b
Add error handling
May 8, 2020
ecf85db
cleanup reducer
May 8, 2020
84ffd8b
document selector
May 8, 2020
7fc4d0b
adding comments to types
May 8, 2020
8c46844
linting
May 8, 2020
f1863c5
use built-in EUI loading indicator
May 8, 2020
0620a86
linting
May 8, 2020
ea3aa85
node cleanup
May 8, 2020
4842833
add related category select action
May 11, 2020
03237e6
Move submenu to import to clear up event_dot
May 11, 2020
1d09e38
18n format for number
May 11, 2020
7395f6d
add comments to submenu
May 11, 2020
ea0c436
add related alerts action
May 11, 2020
e1d5cc9
Merge branch 'master' into resolver/lay-in-submenu-options
elasticmachine May 11, 2020
211df54
lint selector
May 11, 2020
e2d87f4
D Plumlee review: remove unnecessary bindings to type
May 11, 2020
fe22f98
D Plumlee review: remove comments
May 11, 2020
c0bcc25
add comments to relatedEventOptions breakouts
May 11, 2020
e375fa7
R Austin review: move logic from reducer to selector
May 12, 2020
eaea02e
linting
May 12, 2020
7bb0c30
D Plumlee / R Austin review: i18n for event categories
May 12, 2020
ae14c1e
lint resolver node
May 12, 2020
5a4819c
R Austin Review: adjust middleware, rewrites for clarity, fixing types
May 12, 2020
7b8aa36
R Austin review: Move category display names to view
May 13, 2020
e019815
R Austin review: remove all symbols
May 13, 2020
2d7523a
R Austin review: refactor memos for clarity
May 13, 2020
17dfe3c
add comment
May 13, 2020
2413c93
K Qualters review: destructure arguments
May 13, 2020
d08f414
K Qualters review: add more ECS event types to Map
May 13, 2020
2f90697
add comments for clarification
May 13, 2020
848f659
Merge branch 'master' into resolver/lay-in-submenu-options
elasticmachine May 13, 2020
70fa593
Merge branch 'master' into resolver/lay-in-submenu-options
elasticmachine May 13, 2020
66fc653
D Plumlee review: Display unknown for unmatched categories
May 14, 2020
235ebd5
R Austin review: Call i18n with static values
May 14, 2020
080e112
D Plumlee review: remove reselect call from selector where it's not n…
May 14, 2020
7e03a25
R Austin / J Brown review: Add test coverage for selectors
May 14, 2020
f39f4cd
Merge branch 'master' into resolver/lay-in-submenu-options
elasticmachine May 14, 2020
78b0531
Merge branch 'master' into resolver/lay-in-submenu-options
elasticmachine May 18, 2020
f601477
R Austin review: undo Memos, add comments
May 18, 2020
3d52b8c
Merge remote-tracking branch 'origin/resolver/lay-in-submenu-options'…
May 18, 2020
1985a31
R Austin review: adjust type on submenu for readability
May 18, 2020
91f2bd6
R Austin review: move selectable options to state hook
May 18, 2020
c05de64
R Austin review: simplify type, remove memo, remove inert return
May 18, 2020
5bb0411
R Austin review: add comments, move testing instantiation
May 18, 2020
7e85044
R Austin review: remove unnecessary type check
May 18, 2020
c06da13
R Austin review: direct return
May 18, 2020
e742cce
R Austin review: Add comments to actions
May 18, 2020
fe978f6
R Austin review: change payload for failed action
May 18, 2020
7750c97
R Austin review: More specific related type?
May 18, 2020
9e07c11
R Austin review: Remove Error construction
May 18, 2020
002d631
R Austin review: mark relatedEventInfo non-optional
May 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions x-pack/plugins/endpoint/common/models/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { LegacyEndpointEvent, ResolverEvent } from '../types';
import { EventCategory } from '../../public/embeddables/resolver/types';

export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEvent {
return (event as LegacyEndpointEvent).endgame !== undefined;
Expand Down Expand Up @@ -46,3 +47,42 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
}
return event.process.parent?.entity_id;
}

export function eventCategoryDisplayName(event: ResolverEvent): EventCategory {
const eventTypeToNameMap = new Map<string, EventCategory | undefined>([
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
['process', 'Process'],
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
['alert', 'Alert'],
['security', 'Security'],
['file', 'File'],
['network', 'Network'],
['registry', 'Registry'],
['dns', 'DNS'],
['clr', 'CLR'],
['image_load', 'Image Load'],
['powershell', 'Powershell'],
['wmi', 'WMI'],
['api', 'API'],
['user', 'User'],
]);

// Returning "Process" as a catch-all here because it seems pretty general
let eventCategoryToReturn: EventCategory = 'Process';
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
if (isLegacyEvent(event)) {
const legacyFullType = event.endgame.event_type_full;
if (legacyFullType) {
const mappedLegacyCategory = eventTypeToNameMap.get(legacyFullType);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎗️ 0% chance this is correct. Ask @kqualters-elastic how to fix it.

if (mappedLegacyCategory) {
eventCategoryToReturn = mappedLegacyCategory;
}
}
} else {
const eventCategories = event.event.category;
const eventCategory =
typeof eventCategories === 'string' ? eventCategories : eventCategories[0] || '';
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
const mappedCategoryValue = eventTypeToNameMap.get(eventCategory);
if (mappedCategoryValue) {
eventCategoryToReturn = mappedCategoryValue;
}
}
return eventCategoryToReturn;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ interface AppRequestedResolverData {
readonly type: 'appRequestedResolverData';
}

/**
* The action dispatched when the app requests related event data for one or more
* subjects (whose ids should be included as an array @ `payload`)
*/
interface UserRequestedRelatedEventData {
readonly type: 'userRequestedRelatedEventData';
readonly payload: ResolverEvent;
}

/**
* When the user switches the "active descendant" of the Resolver.
* The "active descendant" (from the point of view of the parent element)
Expand Down Expand Up @@ -77,11 +86,27 @@ interface UserSelectedResolverNode {
};
}

interface UserSelectedRelatedEventCategory {
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
readonly type: 'userSelectedRelatedEventCategory';
readonly payload: {
subject: ResolverEvent;
category: string;
};
}

interface UserSelectedRelatedAlerts {
readonly type: 'userSelectedRelatedAlerts';
readonly payload: ResolverEvent;
}

export type ResolverAction =
| CameraAction
| DataAction
| UserBroughtProcessIntoView
| UserChangedSelectedEvent
| AppRequestedResolverData
| UserFocusedOnResolverNode
| UserSelectedResolverNode;
| UserSelectedResolverNode
| UserRequestedRelatedEventData
| UserSelectedRelatedEventCategory
| UserSelectedRelatedAlerts;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { ResolverEvent } from '../../../../../common/types';
import { RelatedEventDataEntry } from '../../types';

interface ServerReturnedResolverData {
readonly type: 'serverReturnedResolverData';
Expand All @@ -15,4 +16,24 @@ interface ServerFailedToReturnResolverData {
readonly type: 'serverFailedToReturnResolverData';
}

export type DataAction = ServerReturnedResolverData | ServerFailedToReturnResolverData;
/**
* Will occur when a request for related event data is fulfilled by the API.
*/
interface ServerReturnedRelatedEventData {
readonly type: 'serverReturnedRelatedEventData';
readonly payload: Map<ResolverEvent, RelatedEventDataEntry>;
}

/**
* Will occur when a request for related event data is unsuccessful.
*/
interface ServerFailedToReturnRelatedEventData {
readonly type: 'serverFailedToReturnRelatedEventData';
readonly payload: [ResolverEvent, Error];
}

export type DataAction =
| ServerReturnedResolverData
| ServerFailedToReturnResolverData
| ServerReturnedRelatedEventData
| ServerFailedToReturnRelatedEventData;
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
*/

import { Reducer } from 'redux';
import { DataState, ResolverAction } from '../../types';
import {
DataState,
ResolverAction,
resultsEnrichedWithRelatedEventInfo,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎗️ importing so we keep the key in sync in the different places we use it. Per earlier review, it was changed from a Symbol to a string.

RelatedEventDataEntryWithStats,
RelatedEventType,
RelatedEventData,
waitingForRelatedEventData,
} from '../../types';

function initialState(): DataState {
return {
results: [],
isLoading: false,
hasError: false,
[resultsEnrichedWithRelatedEventInfo]: new Map(),
};
}

Expand All @@ -23,6 +32,63 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
isLoading: false,
hasError: false,
};
} else if (action.type === 'userRequestedRelatedEventData') {
const resolverEvent = action.payload;
const statsMap = state[resultsEnrichedWithRelatedEventInfo];
if (statsMap) {
const currentStatsMap = new Map(statsMap);
/**
* 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 };
}
return state;
} else if (action.type === 'serverFailedToReturnRelatedEventData') {
const statsMap = state[resultsEnrichedWithRelatedEventInfo];
if (statsMap) {
const currentStatsMap = new Map(statsMap);
const [resolverEvent, apiError] = action.payload;
currentStatsMap.set(resolverEvent, apiError);
return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap };
}
return state;
} else if (action.type === 'serverReturnedRelatedEventData') {
/**
* REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379
* When this data is inlined with results, there won't be a need for this.
*/
const statsMap = state[resultsEnrichedWithRelatedEventInfo];

if (statsMap && typeof statsMap?.set === 'function') {
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
const currentStatsMap: RelatedEventData = new Map(statsMap);
for (const updatedEvent of action.payload.keys()) {
const newStatsEntry = action.payload.get(updatedEvent);

if (newStatsEntry) {
// do stats
const statsForEntry = newStatsEntry?.relatedEvents.reduce(
(
compiledStats: Partial<Record<RelatedEventType, number>>,
relatedEvent: { relatedEventType: RelatedEventType }
) => {
compiledStats[relatedEvent.relatedEventType] =
(compiledStats[relatedEvent.relatedEventType] || 0) + 1;
return compiledStats;
},
{}
);
const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign(
newStatsEntry,
{ stats: statsForEntry }
);
currentStatsMap.set(updatedEvent, newRelatedEventStats);
}
}
return { ...state, [resultsEnrichedWithRelatedEventInfo]: currentStatsMap };
}
return state;
} else if (action.type === 'appRequestedResolverData') {
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
ProcessWithWidthMetadata,
Matrix3,
AdjacentProcessMap,
RelatedEventData,
resultsEnrichedWithRelatedEventInfo,
} from '../../types';
import { ResolverEvent } from '../../../../../common/types';
import { Vector2 } from '../../types';
Expand Down Expand Up @@ -405,6 +407,38 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in
return indexedProcessTreeFactory(graphableProcesses);
});

/**
* Process events that will be graphed.
*/
export const relatedEventStats = createSelector(
(data: DataState) => data,
function(data) {
return data[resultsEnrichedWithRelatedEventInfo];
}
);

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 const processAdjacencies = createSelector(
indexedProcessTree,
graphableProcesses,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
*/

import { Dispatch, MiddlewareAPI } from 'redux';
import { HttpHandler } from 'kibana/public';
import { KibanaReactContextValue } from '../../../../../../../src/plugins/kibana_react/public';
import { EndpointPluginServices } from '../../../plugin';
import { ResolverState, ResolverAction } from '../types';
import { ResolverState, ResolverAction, RelatedEventDataEntry, RelatedEventType } from '../types';
import { ResolverEvent, ResolverNode } from '../../../../common/types';
import * as event from '../../../../common/models/event';

Expand All @@ -30,6 +31,26 @@ function flattenEvents(children: ResolverNode[], events: ResolverEvent[] = []):
}, events);
}

type RelatedEventAPIResponse = Error | { events: ResolverEvent[] };
async function* getEachRelatedEventsResult(
eventsToFetch: ResolverEvent[],
httpGetter: HttpHandler
): AsyncGenerator<[ResolverEvent, RelatedEventAPIResponse], any, any> {
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
for (const eventToQueryForRelateds of eventsToFetch) {
const id = event.entityId(eventToQueryForRelateds);
const relatedEventError = new Error(`Error fetching related events for entity=${id}`);
let result: RelatedEventAPIResponse = relatedEventError;
try {
result = await httpGetter(`/api/endpoint/resolver/${id}/events`, {
query: { events: 100 },
});
} catch (e) {
result = relatedEventError;
}
yield [eventToQueryForRelateds, result];
}
}

export const resolverMiddlewareFactory: MiddlewareFactory = context => {
return api => next => async (action: ResolverAction) => {
next(action);
Expand Down Expand Up @@ -78,5 +99,42 @@ export const resolverMiddlewareFactory: MiddlewareFactory = context => {
}
}
}
/**
* REMOVE: pending resolution of https://github.com/elastic/endpoint-app-team/issues/379
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
* When this data is inlined with results, there won't be a need for this.
*/
if (action.type === 'userRequestedRelatedEventData') {
if (typeof context !== 'undefined') {
for await (const results of getEachRelatedEventsResult(
[action.payload],
context.services.http.get
)) {
const apiResults = results[1];
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
if (apiResults instanceof Error) {
api.dispatch({
type: 'serverFailedToReturnRelatedEventData',
payload: [results[0], apiResults],
});
}
const response: Map<ResolverEvent, RelatedEventDataEntry> = new Map();
const baseEvent = results[0];
bkimmel marked this conversation as resolved.
Show resolved Hide resolved
const fetchedResults = (results[1] as { events: ResolverEvent[] }).events;
// pack up the results into response
const relatedEventEntry = fetchedResults.map(relatedEvent => {
return {
relatedEvent,
relatedEventType: event.eventCategoryDisplayName(relatedEvent) as RelatedEventType,
};
});

response.set(baseEvent, { relatedEvents: relatedEventEntry });

api.dispatch({
type: 'serverReturnedRelatedEventData',
payload: response,
});
}
}
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ export const processAdjacencies = composeSelectors(
dataSelectors.processAdjacencies
);

/**
* Returns a map of `ResolverEvent`s to their related `ResolverEvent`s
*/
export const relatedEvents = composeSelectors(dataStateSelector, dataSelectors.relatedEvents);

/**
* Returns the id of the "current" tree node (fake-focused)
*/
Expand Down
Loading