Skip to content

Commit

Permalink
PR: Provide limit warnings to user when API limits are reached. (#69590
Browse files Browse the repository at this point in the history
…) (#69944)

* Provide facilties to raise limit warnings for user when API limits are reached.
  • Loading branch information
bkimmel authored Jun 25, 2020
1 parent 08ced76 commit b833d6b
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import {

interface ServerReturnedResolverData {
readonly type: 'serverReturnedResolverData';
readonly events: ResolverEvent[];
readonly stats: Map<string, ResolverNodeStats>;
readonly payload: {
readonly events: Readonly<ResolverEvent[]>;
readonly stats: Readonly<Map<string, ResolverNodeStats>>;
readonly lineageLimits: { readonly children: string | null; readonly ancestors: string | null };
};
}

interface ServerFailedToReturnResolverData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import { DataAction } from './action';
import { dataReducer } from './reducer';
import { DataState } from '../../types';
import { LegacyEndpointEvent, ResolverEvent } from '../../../../common/endpoint/types';
import { graphableProcesses, processNodePositionsAndEdgeLineSegments } from './selectors';
import {
graphableProcesses,
processNodePositionsAndEdgeLineSegments,
limitsReached,
} from './selectors';
import { mockProcessEvent } from '../../models/process_event_test_helpers';
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';

describe('resolver graph layout', () => {
let processA: LegacyEndpointEvent;
Expand Down Expand Up @@ -114,7 +119,10 @@ describe('resolver graph layout', () => {
describe('when rendering no nodes', () => {
beforeEach(() => {
const events: ResolverEvent[] = [];
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
const action: DataAction = {
type: 'serverReturnedResolverData',
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
};
store.dispatch(action);
});
it('the graphableProcesses list should only include nothing', () => {
Expand All @@ -128,7 +136,10 @@ describe('resolver graph layout', () => {
describe('when rendering one node', () => {
beforeEach(() => {
const events = [processA];
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
const action: DataAction = {
type: 'serverReturnedResolverData',
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
};
store.dispatch(action);
});
it('the graphableProcesses list should only include nothing', () => {
Expand All @@ -142,7 +153,10 @@ describe('resolver graph layout', () => {
describe('when rendering two nodes, one being the parent of the other', () => {
beforeEach(() => {
const events = [processA, processB];
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
const action: DataAction = {
type: 'serverReturnedResolverData',
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
};
store.dispatch(action);
});
it('the graphableProcesses list should only include nothing', () => {
Expand All @@ -166,7 +180,10 @@ describe('resolver graph layout', () => {
processH,
processI,
];
const action: DataAction = { type: 'serverReturnedResolverData', events, stats: new Map() };
const action: DataAction = {
type: 'serverReturnedResolverData',
payload: { events, stats: new Map(), lineageLimits: { children: null, ancestors: null } },
};
store.dispatch(action);
});
it("the graphableProcesses list should only include events with 'processCreated' an 'processRan' eventType", () => {
Expand All @@ -187,3 +204,48 @@ describe('resolver graph layout', () => {
});
});
});

describe('resolver graph with too much lineage', () => {
let generator: EndpointDocGenerator;
let store: Store<DataState, DataAction>;
let allEvents: ResolverEvent[];
let childrenCursor: string;
let ancestorCursor: string;

beforeEach(() => {
generator = new EndpointDocGenerator('seed');
allEvents = generator.generateTree({ ancestors: 1, generations: 2, children: 2 }).allEvents;
childrenCursor = 'aValidChildursor';
ancestorCursor = 'aValidAncestorCursor';
store = createStore(dataReducer, undefined);
});

describe('should select from state properly', () => {
it('should indicate there are too many ancestors', () => {
const action: DataAction = {
type: 'serverReturnedResolverData',
payload: {
events: allEvents,
stats: new Map(),
lineageLimits: { children: childrenCursor, ancestors: ancestorCursor },
},
};
store.dispatch(action);
const { ancestors } = limitsReached(store.getState());
expect(ancestors).toEqual(true);
});
it('should indicate there are too many children', () => {
const action: DataAction = {
type: 'serverReturnedResolverData',
payload: {
events: allEvents,
stats: new Map(),
lineageLimits: { children: childrenCursor, ancestors: ancestorCursor },
},
};
store.dispatch(action);
const { children } = limitsReached(store.getState());
expect(children).toEqual(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function initialState(): DataState {
relatedEventsStats: new Map(),
relatedEvents: new Map(),
relatedEventsReady: new Map(),
lineageLimits: { children: null, ancestors: null },
isLoading: false,
hasError: false,
};
Expand All @@ -22,8 +23,9 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
if (action.type === 'serverReturnedResolverData') {
return {
...state,
results: action.events,
relatedEventsStats: action.stats,
results: action.payload.events,
relatedEventsStats: action.payload.stats,
lineageLimits: action.payload.lineageLimits,
isLoading: false,
hasError: false,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,15 @@ export const processNodePositionsAndEdgeLineSegments = createSelector(
};
}
);

/**
* Returns the `children` and `ancestors` limits for the current graph, if any.
*
* @param state {DataState} the DataState from the reducer
*/
export const limitsReached = (state: DataState): { children: boolean; ancestors: boolean } => {
return {
children: state.lineageLimits.children !== null,
ancestors: state.lineageLimits.ancestors !== null,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,20 @@ export const resolverMiddlewareFactory: MiddlewareFactory = (context) => {
}
const nodeStats: Map<string, ResolverNodeStats> = new Map();
nodeStats.set(entityId, stats);
const lineageLimits = { children: children.nextChild, ancestors: ancestry.nextAncestor };

const events = [
...lifecycle,
...getLifecycleEventsAndStats(children.childNodes, nodeStats),
...getLifecycleEventsAndStats(ancestry.ancestors, nodeStats),
];
api.dispatch({
type: 'serverReturnedResolverData',
events,
stats: nodeStats,
payload: {
events,
stats: nodeStats,
lineageLimits,
},
});
} catch (error) {
api.dispatch({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ export const graphableProcesses = composeSelectors(
dataSelectors.graphableProcesses
);

/**
* Select the `ancestors` and `children` limits that were reached or exceeded
* during the request for the current tree.
*/
export const lineageLimitsReached = composeSelectors(
dataStateSelector,
dataSelectors.limitsReached
);

/**
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
* concern-specific selector. `selector` should return the concern-specific state.
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/security_solution/public/resolver/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ export type CameraState = {
*/
export interface DataState {
readonly results: readonly ResolverEvent[];
readonly relatedEventsStats: Map<string, ResolverNodeStats>;
readonly relatedEventsStats: Readonly<Map<string, ResolverNodeStats>>;
readonly relatedEvents: Map<string, ResolverRelatedEvents>;
readonly relatedEventsReady: Map<string, boolean>;
readonly lineageLimits: Readonly<{ children: string | null; ancestors: string | null }>;
isLoading: boolean;
hasError: boolean;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,11 @@ describe('useCamera on an unpainted element', () => {
}
const serverResponseAction: ResolverAction = {
type: 'serverReturnedResolverData',
events,
stats: new Map(),
payload: {
events,
stats: new Map(),
lineageLimits: { children: null, ancestors: null },
},
};
act(() => {
store.dispatch(serverResponseAction);
Expand Down

0 comments on commit b833d6b

Please sign in to comment.