Skip to content

Commit 05b0789

Browse files
author
Robert Austin
authored
[Resolver] aria-level and aria-flowto support enhancements (#71887)
* `IndexedProcessTree` now owns the concern of defining the order of siblings * `IsometricTaxiLayout` now owns the concept of `ariaLevels` * added `datetime` method to `process_event` model which returns a time in ms since unix epoch for the event * renamed some resolver selectors * added resolver selector: `ariaLevel` * added 'data' selector: `followingSibling` (used for aria-flowto) * added resolver selector `ariaFlowtoNodeID` which takes a nodeID, and returns its following sibling's node id (if that sibling is visible.) By only returning visible siblings, we ensure that `aria-flowto` will point to an html ID that is in the dom.
1 parent a44cc08 commit 05b0789

File tree

20 files changed

+894
-263
lines changed

20 files changed

+894
-263
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ export function eventName(event: ResolverEvent): string {
3232
}
3333
}
3434

35-
export function eventId(event: ResolverEvent): string {
35+
export function eventId(event: ResolverEvent): number | undefined | string {
3636
if (isLegacyEvent(event)) {
37-
return event.endgame.serial_event_id ? String(event.endgame.serial_event_id) : '';
37+
return event.endgame.serial_event_id;
3838
}
3939
return event.event.id;
4040
}

x-pack/plugins/security_solution/public/resolver/models/indexed_process_tree/__snapshots__/isometric_taxi_layout.test.ts.snap

Lines changed: 175 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/security_solution/public/resolver/models/indexed_process_tree/index.ts

Lines changed: 53 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,99 +4,46 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import { uniquePidForProcess, uniqueParentPidForProcess } from '../process_event';
8-
import { IndexedProcessTree, AdjacentProcessMap } from '../../types';
7+
import { uniquePidForProcess, uniqueParentPidForProcess, orderByTime } from '../process_event';
8+
import { IndexedProcessTree } from '../../types';
99
import { ResolverEvent } from '../../../../common/endpoint/types';
1010
import { levelOrder as baseLevelOrder } from '../../lib/tree_sequencers';
1111

1212
/**
13-
* Create a new IndexedProcessTree from an array of ProcessEvents
13+
* Create a new IndexedProcessTree from an array of ProcessEvents.
14+
* siblings will be ordered by timestamp
1415
*/
15-
export function factory(processes: ResolverEvent[]): IndexedProcessTree {
16+
export function factory(
17+
// Array of processes to index as a tree
18+
processes: ResolverEvent[]
19+
): IndexedProcessTree {
1620
const idToChildren = new Map<string | undefined, ResolverEvent[]>();
1721
const idToValue = new Map<string, ResolverEvent>();
18-
const idToAdjacent = new Map<string, AdjacentProcessMap>();
19-
20-
function emptyAdjacencyMap(id: string): AdjacentProcessMap {
21-
return {
22-
self: id,
23-
parent: null,
24-
firstChild: null,
25-
previousSibling: null,
26-
nextSibling: null,
27-
level: 1,
28-
};
29-
}
30-
31-
const roots: ResolverEvent[] = [];
3222

3323
for (const process of processes) {
3424
const uniqueProcessPid = uniquePidForProcess(process);
3525
idToValue.set(uniqueProcessPid, process);
3626

37-
const currentProcessAdjacencyMap: AdjacentProcessMap =
38-
idToAdjacent.get(uniqueProcessPid) || emptyAdjacencyMap(uniqueProcessPid);
39-
idToAdjacent.set(uniqueProcessPid, currentProcessAdjacencyMap);
40-
4127
const uniqueParentPid = uniqueParentPidForProcess(process);
42-
const currentProcessSiblings = idToChildren.get(uniqueParentPid);
43-
44-
if (currentProcessSiblings) {
45-
const previousProcessId = uniquePidForProcess(
46-
currentProcessSiblings[currentProcessSiblings.length - 1]
47-
);
48-
currentProcessSiblings.push(process);
49-
/**
50-
* Update adjacency maps for current and previous entries
51-
*/
52-
idToAdjacent.get(previousProcessId)!.nextSibling = uniqueProcessPid;
53-
currentProcessAdjacencyMap.previousSibling = previousProcessId;
54-
if (uniqueParentPid) {
55-
currentProcessAdjacencyMap.parent = uniqueParentPid;
28+
// if its defined and not ''
29+
if (uniqueParentPid) {
30+
let siblings = idToChildren.get(uniqueParentPid);
31+
if (!siblings) {
32+
siblings = [];
33+
idToChildren.set(uniqueParentPid, siblings);
5634
}
57-
} else {
58-
if (uniqueParentPid) {
59-
idToChildren.set(uniqueParentPid, [process]);
60-
/**
61-
* Get the parent's map, otherwise set an empty one
62-
*/
63-
const parentAdjacencyMap =
64-
idToAdjacent.get(uniqueParentPid) ||
65-
(idToAdjacent.set(uniqueParentPid, emptyAdjacencyMap(uniqueParentPid)),
66-
idToAdjacent.get(uniqueParentPid))!;
67-
// set firstChild for parent
68-
parentAdjacencyMap.firstChild = uniqueProcessPid;
69-
// set parent for current
70-
currentProcessAdjacencyMap.parent = uniqueParentPid || null;
71-
} else {
72-
// In this case (no unique parent id), it must be a root
73-
roots.push(process);
74-
}
75-
}
76-
}
77-
78-
/**
79-
* Scan adjacency maps from the top down and assign levels
80-
*/
81-
function traverseLevels(currentProcessMap: AdjacentProcessMap, level: number = 1): void {
82-
const nextLevel = level + 1;
83-
if (currentProcessMap.nextSibling) {
84-
traverseLevels(idToAdjacent.get(currentProcessMap.nextSibling)!, level);
85-
}
86-
if (currentProcessMap.firstChild) {
87-
traverseLevels(idToAdjacent.get(currentProcessMap.firstChild)!, nextLevel);
35+
siblings.push(process);
8836
}
89-
currentProcessMap.level = level;
9037
}
9138

92-
for (const treeRoot of roots) {
93-
traverseLevels(idToAdjacent.get(uniquePidForProcess(treeRoot))!);
39+
// sort the children of each node
40+
for (const siblings of idToChildren.values()) {
41+
siblings.sort(orderByTime);
9442
}
9543

9644
return {
9745
idToChildren,
9846
idToProcess: idToValue,
99-
idToAdjacent,
10047
};
10148
}
10249

@@ -109,6 +56,13 @@ export function children(tree: IndexedProcessTree, process: ResolverEvent): Reso
10956
return currentProcessSiblings === undefined ? [] : currentProcessSiblings;
11057
}
11158

59+
/**
60+
* Get the indexed process event for the ID
61+
*/
62+
export function processEvent(tree: IndexedProcessTree, entityID: string): ResolverEvent | null {
63+
return tree.idToProcess.get(entityID) ?? null;
64+
}
65+
11266
/**
11367
* Returns the parent ProcessEvent, if any, for the passed in `childProcess`
11468
*/
@@ -124,6 +78,31 @@ export function parent(
12478
}
12579
}
12680

81+
/**
82+
* Returns the following sibling
83+
*/
84+
export function nextSibling(
85+
tree: IndexedProcessTree,
86+
sibling: ResolverEvent
87+
): ResolverEvent | undefined {
88+
const parentNode = parent(tree, sibling);
89+
if (parentNode) {
90+
// The siblings of `sibling` are the children of its parent.
91+
const siblings = children(tree, parentNode);
92+
93+
// Find the sibling
94+
const index = siblings.indexOf(sibling);
95+
96+
// if the sibling wasn't found, or if it was the last element in the array, return undefined
97+
if (index === -1 || index === siblings.length - 1) {
98+
return undefined;
99+
}
100+
101+
// return the next sibling
102+
return siblings[index + 1];
103+
}
104+
}
105+
127106
/**
128107
* Number of processes in the tree
129108
*/
@@ -138,7 +117,10 @@ export function root(tree: IndexedProcessTree) {
138117
if (size(tree) === 0) {
139118
return null;
140119
}
120+
// any node will do
141121
let current: ResolverEvent = tree.idToProcess.values().next().value;
122+
123+
// iteratively swap current w/ its parent
142124
while (parent(tree, current) !== undefined) {
143125
current = parent(tree, current)!;
144126
}

0 commit comments

Comments
 (0)