Skip to content

Commit 7fbc57a

Browse files
Merge branch 'master' into fix/determine-correct-merge-base
2 parents 047e560 + 1b1a0f4 commit 7fbc57a

File tree

26 files changed

+990
-81
lines changed

26 files changed

+990
-81
lines changed

x-pack/plugins/apm/common/agent_name.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,16 @@ export function isJavaAgentName(
4141
): agentName is 'java' {
4242
return agentName === 'java';
4343
}
44+
45+
/**
46+
* "Normalizes" and agent name by:
47+
*
48+
* * Converting to lowercase
49+
* * Converting "rum-js" to "js-base"
50+
*
51+
* This helps dealing with some older agent versions
52+
*/
53+
export function getNormalizedAgentName(agentName?: string) {
54+
const lowercased = agentName && agentName.toLowerCase();
55+
return isRumAgentName(lowercased) ? 'js-base' : lowercased;
56+
}

x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ storiesOf('app/ServiceMap/Cytoscape', module)
186186
'agent.name': 'dotnet'
187187
}
188188
},
189+
{
190+
data: {
191+
id: 'dotNet',
192+
'service.name': 'dotNet service',
193+
'agent.name': 'dotNet'
194+
}
195+
},
189196
{
190197
data: {
191198
id: 'go',

x-pack/plugins/apm/public/components/app/ServiceMap/icons.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import cytoscape from 'cytoscape';
8-
import { isRumAgentName } from '../../../../common/agent_name';
8+
import { getNormalizedAgentName } from '../../../../common/agent_name';
99
import {
1010
AGENT_NAME,
1111
SPAN_SUBTYPE,
@@ -87,8 +87,7 @@ const agentIcons: { [key: string]: string } = {
8787
};
8888

8989
function getAgentIcon(agentName?: string) {
90-
// RUM can have multiple names. Normalize it
91-
const normalizedAgentName = isRumAgentName(agentName) ? 'js-base' : agentName;
90+
const normalizedAgentName = getNormalizedAgentName(agentName);
9291
return normalizedAgentName && agentIcons[normalizedAgentName];
9392
}
9493

x-pack/plugins/endpoint/common/models/event.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* or more contributor license agreements. Licensed under the Elastic License;
44
* you may not use this file except in compliance with the Elastic License.
55
*/
6-
76
import { LegacyEndpointEvent, ResolverEvent } from '../types';
87

98
export function isLegacyEvent(event: ResolverEvent): event is LegacyEndpointEvent {
@@ -46,3 +45,23 @@ export function parentEntityId(event: ResolverEvent): string | undefined {
4645
}
4746
return event.process.parent?.entity_id;
4847
}
48+
49+
export function eventType(event: ResolverEvent): string {
50+
// Returning "Process" as a catch-all here because it seems pretty general
51+
let eventCategoryToReturn: string = 'Process';
52+
if (isLegacyEvent(event)) {
53+
const legacyFullType = event.endgame.event_type_full;
54+
if (legacyFullType) {
55+
return legacyFullType;
56+
}
57+
} else {
58+
const eventCategories = event.event.category;
59+
const eventCategory =
60+
typeof eventCategories === 'string' ? eventCategories : eventCategories[0] || '';
61+
62+
if (eventCategory) {
63+
eventCategoryToReturn = eventCategory;
64+
}
65+
}
66+
return eventCategoryToReturn;
67+
}

x-pack/plugins/endpoint/public/embeddables/resolver/store/actions.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ interface AppRequestedResolverData {
4444
readonly type: 'appRequestedResolverData';
4545
}
4646

47+
/**
48+
* The action dispatched when the app requests related event data for one or more
49+
* subjects (whose ids should be included as an array @ `payload`)
50+
*/
51+
interface UserRequestedRelatedEventData {
52+
readonly type: 'userRequestedRelatedEventData';
53+
readonly payload: ResolverEvent;
54+
}
55+
4756
/**
4857
* When the user switches the "active descendant" of the Resolver.
4958
* The "active descendant" (from the point of view of the parent element)
@@ -77,11 +86,36 @@ interface UserSelectedResolverNode {
7786
};
7887
}
7988

89+
/**
90+
* This action should dispatch to indicate that the user chose to
91+
* focus on examining the related events of a particular ResolverEvent.
92+
* Optionally, this can be bound by a category of related events (e.g. 'file' or 'dns')
93+
*/
94+
interface UserSelectedRelatedEventCategory {
95+
readonly type: 'userSelectedRelatedEventCategory';
96+
readonly payload: {
97+
subject: ResolverEvent;
98+
category?: string;
99+
};
100+
}
101+
102+
/**
103+
* This action should dispatch to indicate that the user chose to focus
104+
* on examining alerts related to a particular ResolverEvent
105+
*/
106+
interface UserSelectedRelatedAlerts {
107+
readonly type: 'userSelectedRelatedAlerts';
108+
readonly payload: ResolverEvent;
109+
}
110+
80111
export type ResolverAction =
81112
| CameraAction
82113
| DataAction
83114
| UserBroughtProcessIntoView
84115
| UserChangedSelectedEvent
85116
| AppRequestedResolverData
86117
| UserFocusedOnResolverNode
87-
| UserSelectedResolverNode;
118+
| UserSelectedResolverNode
119+
| UserRequestedRelatedEventData
120+
| UserSelectedRelatedEventCategory
121+
| UserSelectedRelatedAlerts;

x-pack/plugins/endpoint/public/embeddables/resolver/store/data/action.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import { ResolverEvent } from '../../../../../common/types';
8+
import { RelatedEventDataEntry } from '../../types';
89

910
interface ServerReturnedResolverData {
1011
readonly type: 'serverReturnedResolverData';
@@ -15,4 +16,24 @@ interface ServerFailedToReturnResolverData {
1516
readonly type: 'serverFailedToReturnResolverData';
1617
}
1718

18-
export type DataAction = ServerReturnedResolverData | ServerFailedToReturnResolverData;
19+
/**
20+
* Will occur when a request for related event data is fulfilled by the API.
21+
*/
22+
interface ServerReturnedRelatedEventData {
23+
readonly type: 'serverReturnedRelatedEventData';
24+
readonly payload: Map<ResolverEvent, RelatedEventDataEntry>;
25+
}
26+
27+
/**
28+
* Will occur when a request for related event data is unsuccessful.
29+
*/
30+
interface ServerFailedToReturnRelatedEventData {
31+
readonly type: 'serverFailedToReturnRelatedEventData';
32+
readonly payload: ResolverEvent;
33+
}
34+
35+
export type DataAction =
36+
| ServerReturnedResolverData
37+
| ServerFailedToReturnResolverData
38+
| ServerReturnedRelatedEventData
39+
| ServerFailedToReturnRelatedEventData;

x-pack/plugins/endpoint/public/embeddables/resolver/store/data/reducer.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ function initialState(): DataState {
1212
results: [],
1313
isLoading: false,
1414
hasError: false,
15+
resultsEnrichedWithRelatedEventInfo: new Map(),
1516
};
1617
}
1718

@@ -23,6 +24,26 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
2324
isLoading: false,
2425
hasError: false,
2526
};
27+
} else if (action.type === 'userRequestedRelatedEventData') {
28+
const resolverEvent = action.payload;
29+
const currentStatsMap = new Map(state.resultsEnrichedWithRelatedEventInfo);
30+
/**
31+
* Set the waiting indicator for this event to indicate that related event results are pending.
32+
* It will be replaced by the actual results from the API when they are returned.
33+
*/
34+
currentStatsMap.set(resolverEvent, 'waitingForRelatedEventData');
35+
return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap };
36+
} else if (action.type === 'serverFailedToReturnRelatedEventData') {
37+
const currentStatsMap = new Map(state.resultsEnrichedWithRelatedEventInfo);
38+
const resolverEvent = action.payload;
39+
currentStatsMap.set(resolverEvent, 'error');
40+
return { ...state, resultsEnrichedWithRelatedEventInfo: currentStatsMap };
41+
} else if (action.type === 'serverReturnedRelatedEventData') {
42+
const relatedDataEntries = new Map([
43+
...state.resultsEnrichedWithRelatedEventInfo,
44+
...action.payload,
45+
]);
46+
return { ...state, resultsEnrichedWithRelatedEventInfo: relatedDataEntries };
2647
} else if (action.type === 'appRequestedResolverData') {
2748
return {
2849
...state,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { Store, createStore } from 'redux';
8+
import { DataAction } from './action';
9+
import { dataReducer } from './reducer';
10+
import {
11+
DataState,
12+
RelatedEventDataEntry,
13+
RelatedEventDataEntryWithStats,
14+
RelatedEventData,
15+
} from '../../types';
16+
import { ResolverEvent } from '../../../../../common/types';
17+
import { relatedEventStats, relatedEvents } from './selectors';
18+
19+
describe('resolver data selectors', () => {
20+
const store: Store<DataState, DataAction> = createStore(dataReducer, undefined);
21+
describe('when related event data is reduced into state with no results', () => {
22+
let relatedEventInfoBeforeAction: RelatedEventData;
23+
beforeEach(() => {
24+
relatedEventInfoBeforeAction = new Map(relatedEvents(store.getState()) || []);
25+
const payload: Map<ResolverEvent, RelatedEventDataEntry> = new Map();
26+
const action: DataAction = { type: 'serverReturnedRelatedEventData', payload };
27+
store.dispatch(action);
28+
});
29+
it('should have the same related info as before the action', () => {
30+
const relatedInfoAfterAction = relatedEvents(store.getState());
31+
expect(relatedInfoAfterAction).toEqual(relatedEventInfoBeforeAction);
32+
});
33+
});
34+
describe('when related event data is reduced into state with 2 dns results', () => {
35+
let mockBaseEvent: ResolverEvent;
36+
beforeEach(() => {
37+
mockBaseEvent = {} as ResolverEvent;
38+
function dnsRelatedEventEntry() {
39+
const fakeEvent = {} as ResolverEvent;
40+
return { relatedEvent: fakeEvent, relatedEventType: 'dns' };
41+
}
42+
const payload: Map<ResolverEvent, RelatedEventDataEntry> = new Map([
43+
[
44+
mockBaseEvent,
45+
{
46+
relatedEvents: [dnsRelatedEventEntry(), dnsRelatedEventEntry()],
47+
},
48+
],
49+
]);
50+
const action: DataAction = { type: 'serverReturnedRelatedEventData', payload };
51+
store.dispatch(action);
52+
});
53+
it('should compile stats reflecting a count of 2 for dns', () => {
54+
const actualStats = relatedEventStats(store.getState());
55+
const statsForFakeEvent = actualStats.get(mockBaseEvent)! as RelatedEventDataEntryWithStats;
56+
expect(statsForFakeEvent.stats).toEqual({ dns: 2 });
57+
});
58+
});
59+
});

x-pack/plugins/endpoint/public/embeddables/resolver/store/data/selectors.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
ProcessWithWidthMetadata,
1515
Matrix3,
1616
AdjacentProcessMap,
17+
RelatedEventData,
18+
RelatedEventDataEntryWithStats,
1719
} from '../../types';
1820
import { ResolverEvent } from '../../../../../common/types';
1921
import { Vector2 } from '../../types';
@@ -405,6 +407,86 @@ export const indexedProcessTree = createSelector(graphableProcesses, function in
405407
return indexedProcessTreeFactory(graphableProcesses);
406408
});
407409

410+
/**
411+
* Process events that will be graphed.
412+
*/
413+
export const relatedEventResults = function(data: DataState) {
414+
return data.resultsEnrichedWithRelatedEventInfo;
415+
};
416+
417+
/**
418+
* This selector compiles the related event data attached in `relatedEventResults`
419+
* into a `RelatedEventData` map of ResolverEvents to statistics about their related events
420+
*/
421+
export const relatedEventStats = createSelector(relatedEventResults, function getRelatedEvents(
422+
/* eslint-disable no-shadow */
423+
relatedEventResults
424+
/* eslint-enable no-shadow */
425+
) {
426+
/* eslint-disable no-shadow */
427+
const relatedEventStats: RelatedEventData = new Map();
428+
/* eslint-enable no-shadow */
429+
if (!relatedEventResults) {
430+
return relatedEventStats;
431+
}
432+
433+
for (const updatedEvent of relatedEventResults.keys()) {
434+
const newStatsEntry = relatedEventResults.get(updatedEvent);
435+
if (newStatsEntry === 'error') {
436+
// If the entry is an error, return it as is
437+
relatedEventStats.set(updatedEvent, newStatsEntry);
438+
continue;
439+
}
440+
if (typeof newStatsEntry === 'object') {
441+
/**
442+
* Otherwise, it should be a valid stats entry.
443+
* Do the work to compile the stats.
444+
* Folowing reduction, this will be a record like
445+
* {DNS: 10, File: 2} etc.
446+
*/
447+
const statsForEntry = newStatsEntry?.relatedEvents.reduce(
448+
(compiledStats: Record<string, number>, relatedEvent: { relatedEventType: string }) => {
449+
compiledStats[relatedEvent.relatedEventType] =
450+
(compiledStats[relatedEvent.relatedEventType] || 0) + 1;
451+
return compiledStats;
452+
},
453+
{}
454+
);
455+
456+
const newRelatedEventStats: RelatedEventDataEntryWithStats = Object.assign(newStatsEntry, {
457+
stats: statsForEntry,
458+
});
459+
relatedEventStats.set(updatedEvent, newRelatedEventStats);
460+
}
461+
}
462+
return relatedEventStats;
463+
});
464+
465+
/**
466+
* This selects `RelatedEventData` maps specifically for graphable processes
467+
*/
468+
export const relatedEvents = createSelector(
469+
graphableProcesses,
470+
relatedEventStats,
471+
function getRelatedEvents(
472+
/* eslint-disable no-shadow */
473+
graphableProcesses,
474+
relatedEventStats
475+
/* eslint-enable no-shadow */
476+
) {
477+
const eventsRelatedByProcess: RelatedEventData = new Map();
478+
/* eslint-disable no-shadow */
479+
return graphableProcesses.reduce((relatedEvents, graphableProcess) => {
480+
/* eslint-enable no-shadow */
481+
const relatedEventDataEntry = relatedEventStats?.get(graphableProcess);
482+
if (relatedEventDataEntry) {
483+
relatedEvents.set(graphableProcess, relatedEventDataEntry);
484+
}
485+
return relatedEvents;
486+
}, eventsRelatedByProcess);
487+
}
488+
);
489+
408490
export const processAdjacencies = createSelector(
409491
indexedProcessTree,
410492
graphableProcesses,

0 commit comments

Comments
 (0)