Skip to content

Commit 0ef7bb8

Browse files
authored
PR: Provide limit warnings to user when API limits are reached. (#69590)
* Provide facilties to raise limit warnings for user when API limits are reached.
1 parent 6556ccf commit 0ef7bb8

File tree

8 files changed

+111
-14
lines changed

8 files changed

+111
-14
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ import {
1212

1313
interface ServerReturnedResolverData {
1414
readonly type: 'serverReturnedResolverData';
15-
readonly events: ResolverEvent[];
16-
readonly stats: Map<string, ResolverNodeStats>;
15+
readonly payload: {
16+
readonly events: Readonly<ResolverEvent[]>;
17+
readonly stats: Readonly<Map<string, ResolverNodeStats>>;
18+
readonly lineageLimits: { readonly children: string | null; readonly ancestors: string | null };
19+
};
1720
}
1821

1922
interface ServerFailedToReturnResolverData {

x-pack/plugins/security_solution/public/resolver/store/data/graphing.test.ts

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import { DataAction } from './action';
99
import { dataReducer } from './reducer';
1010
import { DataState } from '../../types';
1111
import { LegacyEndpointEvent, ResolverEvent } from '../../../../common/endpoint/types';
12-
import { graphableProcesses, processNodePositionsAndEdgeLineSegments } from './selectors';
12+
import {
13+
graphableProcesses,
14+
processNodePositionsAndEdgeLineSegments,
15+
limitsReached,
16+
} from './selectors';
1317
import { mockProcessEvent } from '../../models/process_event_test_helpers';
18+
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
1419

1520
describe('resolver graph layout', () => {
1621
let processA: LegacyEndpointEvent;
@@ -114,7 +119,10 @@ describe('resolver graph layout', () => {
114119
describe('when rendering no nodes', () => {
115120
beforeEach(() => {
116121
const events: ResolverEvent[] = [];
117-
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
122+
const action: DataAction = {
123+
type: 'serverReturnedResolverData',
124+
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
125+
};
118126
store.dispatch(action);
119127
});
120128
it('the graphableProcesses list should only include nothing', () => {
@@ -128,7 +136,10 @@ describe('resolver graph layout', () => {
128136
describe('when rendering one node', () => {
129137
beforeEach(() => {
130138
const events = [processA];
131-
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
139+
const action: DataAction = {
140+
type: 'serverReturnedResolverData',
141+
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
142+
};
132143
store.dispatch(action);
133144
});
134145
it('the graphableProcesses list should only include nothing', () => {
@@ -142,7 +153,10 @@ describe('resolver graph layout', () => {
142153
describe('when rendering two nodes, one being the parent of the other', () => {
143154
beforeEach(() => {
144155
const events = [processA, processB];
145-
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
156+
const action: DataAction = {
157+
type: 'serverReturnedResolverData',
158+
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
159+
};
146160
store.dispatch(action);
147161
});
148162
it('the graphableProcesses list should only include nothing', () => {
@@ -166,7 +180,10 @@ describe('resolver graph layout', () => {
166180
processH,
167181
processI,
168182
];
169-
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
183+
const action: DataAction = {
184+
type: 'serverReturnedResolverData',
185+
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
186+
};
170187
store.dispatch(action);
171188
});
172189
it("the graphableProcesses list should only include events with 'processCreated' an 'processRan' eventType", () => {
@@ -187,3 +204,48 @@ describe('resolver graph layout', () => {
187204
});
188205
});
189206
});
207+
208+
describe('resolver graph with too much lineage', () => {
209+
let generator: EndpointDocGenerator;
210+
let store: Store<DataState, DataAction>;
211+
let allEvents: ResolverEvent[];
212+
let childrenCursor: string;
213+
let ancestorCursor: string;
214+
215+
beforeEach(() => {
216+
generator = new EndpointDocGenerator('seed');
217+
allEvents = generator.generateTree({ ancestors: 1, generations: 2, children: 2 }).allEvents;
218+
childrenCursor = 'aValidChildursor';
219+
ancestorCursor = 'aValidAncestorCursor';
220+
store = createStore(dataReducer, undefined);
221+
});
222+
223+
describe('should select from state properly', () => {
224+
it('should indicate there are too many ancestors', () => {
225+
const action: DataAction = {
226+
type: 'serverReturnedResolverData',
227+
payload: {
228+
events: allEvents,
229+
stats: new Map(),
230+
lineageLimits: { children: childrenCursor, ancestors: ancestorCursor },
231+
},
232+
};
233+
store.dispatch(action);
234+
const { ancestors } = limitsReached(store.getState());
235+
expect(ancestors).toEqual(true);
236+
});
237+
it('should indicate there are too many children', () => {
238+
const action: DataAction = {
239+
type: 'serverReturnedResolverData',
240+
payload: {
241+
events: allEvents,
242+
stats: new Map(),
243+
lineageLimits: { children: childrenCursor, ancestors: ancestorCursor },
244+
},
245+
};
246+
store.dispatch(action);
247+
const { children } = limitsReached(store.getState());
248+
expect(children).toEqual(true);
249+
});
250+
});
251+
});

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ function initialState(): DataState {
1313
relatedEventsStats: new Map(),
1414
relatedEvents: new Map(),
1515
relatedEventsReady: new Map(),
16+
lineageLimits: { children: null, ancestors: null },
1617
isLoading: false,
1718
hasError: false,
1819
};
@@ -22,8 +23,9 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
2223
if (action.type === 'serverReturnedResolverData') {
2324
return {
2425
...state,
25-
results: action.events,
26-
relatedEventsStats: action.stats,
26+
results: action.payload.events,
27+
relatedEventsStats: action.payload.stats,
28+
lineageLimits: action.payload.lineageLimits,
2729
isLoading: false,
2830
hasError: false,
2931
};

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,15 @@ export const processNodePositionsAndEdgeLineSegments = createSelector(
529529
};
530530
}
531531
);
532+
533+
/**
534+
* Returns the `children` and `ancestors` limits for the current graph, if any.
535+
*
536+
* @param state {DataState} the DataState from the reducer
537+
*/
538+
export const limitsReached = (state: DataState): { children: boolean; ancestors: boolean } => {
539+
return {
540+
children: state.lineageLimits.children !== null,
541+
ancestors: state.lineageLimits.ancestors !== null,
542+
};
543+
};

x-pack/plugins/security_solution/public/resolver/store/middleware.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,20 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => {
7777
}
7878
const nodeStats: Map<string, ResolverNodeStats> = new Map();
7979
nodeStats.set(entityId, stats);
80+
const lineageLimits = { children: children.nextChild, ancestors: ancestry.nextAncestor };
81+
8082
const events = [
8183
...lifecycle,
8284
...getLifecycleEventsAndStats(children.childNodes, nodeStats),
8385
...getLifecycleEventsAndStats(ancestry.ancestors, nodeStats),
8486
];
8587
api.dispatch({
8688
type: 'serverReturnedResolverData',
87-
events,
88-
stats: nodeStats,
89+
payload: {
90+
events,
91+
stats: nodeStats,
92+
lineageLimits,
93+
},
8994
});
9095
} catch (error) {
9196
api.dispatch({

x-pack/plugins/security_solution/public/resolver/store/selectors.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ export const graphableProcesses = composeSelectors(
152152
dataSelectors.graphableProcesses
153153
);
154154

155+
/**
156+
* Select the `ancestors` and `children` limits that were reached or exceeded
157+
* during the request for the current tree.
158+
*/
159+
export const lineageLimitsReached = composeSelectors(
160+
dataStateSelector,
161+
dataSelectors.limitsReached
162+
);
163+
155164
/**
156165
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
157166
* concern-specific selector. `selector` should return the concern-specific state.

x-pack/plugins/security_solution/public/resolver/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ export type CameraState = {
147147
*/
148148
export interface DataState {
149149
readonly results: readonly ResolverEvent[];
150-
readonly relatedEventsStats: Map<string, ResolverNodeStats>;
150+
readonly relatedEventsStats: Readonly<Map<string, ResolverNodeStats>>;
151151
readonly relatedEvents: Map<string, ResolverRelatedEvents>;
152152
readonly relatedEventsReady: Map<string, boolean>;
153+
readonly lineageLimits: Readonly<{ children: string | null; ancestors: string | null }>;
153154
isLoading: boolean;
154155
hasError: boolean;
155156
}

x-pack/plugins/security_solution/public/resolver/view/use_camera.test.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,11 @@ describe('useCamera on an unpainted element', () => {
176176
}
177177
const serverResponseAction: ResolverAction = {
178178
type: 'serverReturnedResolverData',
179-
events,
180-
stats: new Map(),
179+
payload: {
180+
events,
181+
stats: new Map(),
182+
lineageLimits: { children: null, ancestors: null },
183+
},
181184
};
182185
act(() => {
183186
store.dispatch(serverResponseAction);

0 commit comments

Comments
 (0)