[Security Solution] Pass filters from SIEM to resolver, update resolver when refresh is clicked (#85812)
* Pass filters from SIEM to resolver * Fix test type errors * Revert loading state change, update snapshots * Make correct check in nodeData selector * Fix inverted logic in nodeData selector * Revert nodeData invalidation logic Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ee37f6dd91
commit
47444e77c2
|
@ -43,8 +43,8 @@ export function dataAccessLayerFactory(
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
timeRange: {
|
timeRange: {
|
||||||
from: timeRange.from.toISOString(),
|
from: timeRange.from,
|
||||||
to: timeRange.to.toISOString(),
|
to: timeRange.to,
|
||||||
},
|
},
|
||||||
filter: JSON.stringify({
|
filter: JSON.stringify({
|
||||||
bool: {
|
bool: {
|
||||||
|
@ -82,8 +82,8 @@ export function dataAccessLayerFactory(
|
||||||
query: { afterEvent: after, limit: 25 },
|
query: { afterEvent: after, limit: 25 },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
timeRange: {
|
timeRange: {
|
||||||
from: timeRange.from.toISOString(),
|
from: timeRange.from,
|
||||||
to: timeRange.to.toISOString(),
|
to: timeRange.to,
|
||||||
},
|
},
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
filter: JSON.stringify({
|
filter: JSON.stringify({
|
||||||
|
@ -119,8 +119,8 @@ export function dataAccessLayerFactory(
|
||||||
query: { limit },
|
query: { limit },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
timeRange: {
|
timeRange: {
|
||||||
from: timeRange.from.toISOString(),
|
from: timeRange.from,
|
||||||
to: timeRange.to.toISOString(),
|
to: timeRange.to,
|
||||||
},
|
},
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
filter: JSON.stringify({
|
filter: JSON.stringify({
|
||||||
|
@ -182,8 +182,8 @@ export function dataAccessLayerFactory(
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
indexPatterns,
|
indexPatterns,
|
||||||
timeRange: {
|
timeRange: {
|
||||||
from: timeRange.from.toISOString(),
|
from: timeRange.from,
|
||||||
to: timeRange.to.toISOString(),
|
to: timeRange.to,
|
||||||
},
|
},
|
||||||
filter: JSON.stringify(filter),
|
filter: JSON.stringify(filter),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -13,5 +13,7 @@ export function mockTreeFetcherParameters(): TreeFetcherParameters {
|
||||||
return {
|
return {
|
||||||
databaseDocumentID: '',
|
databaseDocumentID: '',
|
||||||
indices: [],
|
indices: [],
|
||||||
|
dataRequestID: 0,
|
||||||
|
filters: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,13 @@ import { maxDate, createRange } from './time_range';
|
||||||
describe('range', () => {
|
describe('range', () => {
|
||||||
it('creates a range starting from 1970-01-01T00:00:00.000Z to +275760-09-13T00:00:00.000Z by default', () => {
|
it('creates a range starting from 1970-01-01T00:00:00.000Z to +275760-09-13T00:00:00.000Z by default', () => {
|
||||||
const { from, to } = createRange();
|
const { from, to } = createRange();
|
||||||
expect(from.toISOString()).toBe('1970-01-01T00:00:00.000Z');
|
expect(from).toBe('1970-01-01T00:00:00.000Z');
|
||||||
expect(to.toISOString()).toBe('+275760-09-13T00:00:00.000Z');
|
expect(to).toBe('+275760-09-13T00:00:00.000Z');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates an invalid to date using a number greater than 8640000000000000', () => {
|
it('creates an invalid to date using a number greater than 8640000000000000', () => {
|
||||||
const { to } = createRange({ to: new Date(maxDate + 1) });
|
|
||||||
expect(() => {
|
expect(() => {
|
||||||
to.toISOString();
|
createRange({ to: new Date(maxDate + 1) });
|
||||||
}).toThrow(RangeError);
|
}).toThrow(RangeError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,7 @@ export function createRange({
|
||||||
to?: Date;
|
to?: Date;
|
||||||
} = {}): TimeRange {
|
} = {}): TimeRange {
|
||||||
return {
|
return {
|
||||||
from,
|
from: from.toISOString(),
|
||||||
to,
|
to: to.toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,25 +10,47 @@ import { equal } from './tree_fetcher_parameters';
|
||||||
describe('TreeFetcherParameters#equal:', () => {
|
describe('TreeFetcherParameters#equal:', () => {
|
||||||
const cases: Array<[TreeFetcherParameters, TreeFetcherParameters, boolean]> = [
|
const cases: Array<[TreeFetcherParameters, TreeFetcherParameters, boolean]> = [
|
||||||
// different databaseDocumentID
|
// different databaseDocumentID
|
||||||
[{ databaseDocumentID: 'a', indices: [] }, { databaseDocumentID: 'b', indices: [] }, false],
|
[
|
||||||
|
{ databaseDocumentID: 'a', indices: [], filters: {} },
|
||||||
|
{ databaseDocumentID: 'b', indices: [], filters: {} },
|
||||||
|
false,
|
||||||
|
],
|
||||||
// different indices length
|
// different indices length
|
||||||
[{ databaseDocumentID: 'a', indices: [''] }, { databaseDocumentID: 'a', indices: [] }, false],
|
[
|
||||||
|
{ databaseDocumentID: 'a', indices: [''], filters: {} },
|
||||||
|
{ databaseDocumentID: 'a', indices: [], filters: {} },
|
||||||
|
false,
|
||||||
|
],
|
||||||
// same indices length, different databaseDocumentID
|
// same indices length, different databaseDocumentID
|
||||||
[{ databaseDocumentID: 'a', indices: [''] }, { databaseDocumentID: 'b', indices: [''] }, false],
|
[
|
||||||
|
{ databaseDocumentID: 'a', indices: [''], filters: {} },
|
||||||
|
{ databaseDocumentID: 'b', indices: [''], filters: {} },
|
||||||
|
false,
|
||||||
|
],
|
||||||
// 1 item in `indices`
|
// 1 item in `indices`
|
||||||
[{ databaseDocumentID: 'b', indices: [''] }, { databaseDocumentID: 'b', indices: [''] }, true],
|
[
|
||||||
|
{ databaseDocumentID: 'b', indices: [''], filters: {} },
|
||||||
|
{ databaseDocumentID: 'b', indices: [''], filters: {} },
|
||||||
|
true,
|
||||||
|
],
|
||||||
// 2 item in `indices`
|
// 2 item in `indices`
|
||||||
[
|
[
|
||||||
{ databaseDocumentID: 'b', indices: ['1', '2'] },
|
{ databaseDocumentID: 'b', indices: ['1', '2'], filters: {} },
|
||||||
{ databaseDocumentID: 'b', indices: ['1', '2'] },
|
{ databaseDocumentID: 'b', indices: ['1', '2'], filters: {} },
|
||||||
true,
|
true,
|
||||||
],
|
],
|
||||||
// 2 item in `indices`, but order inversed
|
// 2 item in `indices`, but order inversed
|
||||||
[
|
[
|
||||||
{ databaseDocumentID: 'b', indices: ['2', '1'] },
|
{ databaseDocumentID: 'b', indices: ['2', '1'], filters: {} },
|
||||||
{ databaseDocumentID: 'b', indices: ['1', '2'] },
|
{ databaseDocumentID: 'b', indices: ['1', '2'], filters: {} },
|
||||||
true,
|
true,
|
||||||
],
|
],
|
||||||
|
// all parameters the same, except for the request id
|
||||||
|
[
|
||||||
|
{ databaseDocumentID: 'b', indices: [], dataRequestID: 0, filters: {} },
|
||||||
|
{ databaseDocumentID: 'b', indices: [], dataRequestID: 1, filters: {} },
|
||||||
|
false,
|
||||||
|
],
|
||||||
];
|
];
|
||||||
describe.each(cases)('%p when compared to %p', (first, second, expected) => {
|
describe.each(cases)('%p when compared to %p', (first, second, expected) => {
|
||||||
it(`should ${expected ? '' : 'not'}be equal`, () => {
|
it(`should ${expected ? '' : 'not'}be equal`, () => {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { TreeFetcherParameters } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if two instances of `TreeFetcherParameters` are equivalent. Use this to determine if
|
* Determine if two instances of `TreeFetcherParameters` are equivalent. Use this to determine if
|
||||||
* a change to a `TreeFetcherParameters` warrants invaliding a request or response.
|
* a change to a `TreeFetcherParameters` warrants invalidating a request or response.
|
||||||
*/
|
*/
|
||||||
export function equal(param1: TreeFetcherParameters, param2?: TreeFetcherParameters): boolean {
|
export function equal(param1: TreeFetcherParameters, param2?: TreeFetcherParameters): boolean {
|
||||||
if (!param2) {
|
if (!param2) {
|
||||||
|
@ -17,7 +17,10 @@ export function equal(param1: TreeFetcherParameters, param2?: TreeFetcherParamet
|
||||||
if (param1 === param2) {
|
if (param1 === param2) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (param1.databaseDocumentID !== param2.databaseDocumentID) {
|
if (
|
||||||
|
param1.databaseDocumentID !== param2.databaseDocumentID ||
|
||||||
|
param1.dataRequestID !== param2.dataRequestID
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return arraysContainTheSameElements(param1.indices, param2.indices);
|
return arraysContainTheSameElements(param1.indices, param2.indices);
|
||||||
|
|
|
@ -98,6 +98,12 @@ interface AppReceivedNewExternalProperties {
|
||||||
* Indices that the backend will use to find the document.
|
* Indices that the backend will use to find the document.
|
||||||
*/
|
*/
|
||||||
indices: string[];
|
indices: string[];
|
||||||
|
|
||||||
|
shouldUpdate: boolean;
|
||||||
|
filters: {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ResolverRelatedEvents,
|
|
||||||
NewResolverTree,
|
NewResolverTree,
|
||||||
SafeEndpointEvent,
|
SafeEndpointEvent,
|
||||||
SafeResolverEvent,
|
SafeResolverEvent,
|
||||||
ResolverSchema,
|
ResolverSchema,
|
||||||
} from '../../../../common/endpoint/types';
|
} from '../../../../common/endpoint/types';
|
||||||
import { TreeFetcherParameters } from '../../types';
|
import { TreeFetcherParameters, PanelViewAndParameters } from '../../types';
|
||||||
|
|
||||||
interface ServerReturnedResolverData {
|
interface ServerReturnedResolverData {
|
||||||
readonly type: 'serverReturnedResolverData';
|
readonly type: 'serverReturnedResolverData';
|
||||||
|
@ -35,6 +34,13 @@ interface ServerReturnedResolverData {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AppRequestedNodeEventsInCategory {
|
||||||
|
readonly type: 'appRequestedNodeEventsInCategory';
|
||||||
|
readonly payload: {
|
||||||
|
parameters: PanelViewAndParameters;
|
||||||
|
dataRequestID: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
interface AppRequestedResolverData {
|
interface AppRequestedResolverData {
|
||||||
readonly type: 'appRequestedResolverData';
|
readonly type: 'appRequestedResolverData';
|
||||||
/**
|
/**
|
||||||
|
@ -81,14 +87,6 @@ interface AppAbortedResolverDataRequest {
|
||||||
readonly payload: TreeFetcherParameters;
|
readonly payload: TreeFetcherParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* When related events are returned from the server
|
|
||||||
*/
|
|
||||||
interface ServerReturnedRelatedEventData {
|
|
||||||
readonly type: 'serverReturnedRelatedEventData';
|
|
||||||
readonly payload: ResolverRelatedEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ServerReturnedNodeEventsInCategory {
|
interface ServerReturnedNodeEventsInCategory {
|
||||||
readonly type: 'serverReturnedNodeEventsInCategory';
|
readonly type: 'serverReturnedNodeEventsInCategory';
|
||||||
readonly payload: {
|
readonly payload: {
|
||||||
|
@ -108,6 +106,8 @@ interface ServerReturnedNodeEventsInCategory {
|
||||||
* The category that `events` have in common.
|
* The category that `events` have in common.
|
||||||
*/
|
*/
|
||||||
eventCategory: string;
|
eventCategory: string;
|
||||||
|
|
||||||
|
dataRequestID: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,6 +135,8 @@ interface ServerReturnedNodeData {
|
||||||
* that we'll request their data in a subsequent request.
|
* that we'll request their data in a subsequent request.
|
||||||
*/
|
*/
|
||||||
numberOfRequestedEvents: number;
|
numberOfRequestedEvents: number;
|
||||||
|
|
||||||
|
dataRequestID: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +187,7 @@ interface ServerFailedToReturnCurrentRelatedEventData {
|
||||||
|
|
||||||
interface ServerReturnedCurrentRelatedEventData {
|
interface ServerReturnedCurrentRelatedEventData {
|
||||||
readonly type: 'serverReturnedCurrentRelatedEventData';
|
readonly type: 'serverReturnedCurrentRelatedEventData';
|
||||||
readonly payload: SafeResolverEvent;
|
readonly payload: { data: SafeResolverEvent; dataRequestID: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DataAction =
|
export type DataAction =
|
||||||
|
@ -194,7 +196,6 @@ export type DataAction =
|
||||||
| AppRequestedCurrentRelatedEventData
|
| AppRequestedCurrentRelatedEventData
|
||||||
| ServerReturnedCurrentRelatedEventData
|
| ServerReturnedCurrentRelatedEventData
|
||||||
| ServerFailedToReturnCurrentRelatedEventData
|
| ServerFailedToReturnCurrentRelatedEventData
|
||||||
| ServerReturnedRelatedEventData
|
|
||||||
| ServerReturnedNodeEventsInCategory
|
| ServerReturnedNodeEventsInCategory
|
||||||
| AppRequestedResolverData
|
| AppRequestedResolverData
|
||||||
| UserRequestedAdditionalRelatedEvents
|
| UserRequestedAdditionalRelatedEvents
|
||||||
|
@ -203,4 +204,5 @@ export type DataAction =
|
||||||
| ServerReturnedNodeData
|
| ServerReturnedNodeData
|
||||||
| ServerFailedToReturnNodeData
|
| ServerFailedToReturnNodeData
|
||||||
| AppRequestingNodeData
|
| AppRequestingNodeData
|
||||||
| UserReloadedResolverNode;
|
| UserReloadedResolverNode
|
||||||
|
| AppRequestedNodeEventsInCategory;
|
||||||
|
|
|
@ -40,6 +40,7 @@ export function updatedWith(
|
||||||
events: [...first.events, ...second.events],
|
events: [...first.events, ...second.events],
|
||||||
cursor: second.cursor,
|
cursor: second.cursor,
|
||||||
lastCursorRequested: null,
|
lastCursorRequested: null,
|
||||||
|
dataRequestID: second.dataRequestID,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|
|
@ -36,6 +36,7 @@ describe('Resolver Data Middleware', () => {
|
||||||
parameters: {
|
parameters: {
|
||||||
databaseDocumentID: '',
|
databaseDocumentID: '',
|
||||||
indices: [],
|
indices: [],
|
||||||
|
filters: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,19 +17,23 @@ const initialState: DataState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
data: null,
|
data: null,
|
||||||
},
|
},
|
||||||
relatedEvents: new Map(),
|
|
||||||
resolverComponentInstanceID: undefined,
|
resolverComponentInstanceID: undefined,
|
||||||
|
refreshCount: 0,
|
||||||
};
|
};
|
||||||
/* eslint-disable complexity */
|
/* eslint-disable complexity */
|
||||||
export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialState, action) => {
|
export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialState, action) => {
|
||||||
if (action.type === 'appReceivedNewExternalProperties') {
|
if (action.type === 'appReceivedNewExternalProperties') {
|
||||||
|
const refreshCount = state.refreshCount + (action.payload.shouldUpdate ? 1 : 0);
|
||||||
const nextState: DataState = {
|
const nextState: DataState = {
|
||||||
...state,
|
...state,
|
||||||
|
refreshCount,
|
||||||
tree: {
|
tree: {
|
||||||
...state.tree,
|
...state.tree,
|
||||||
currentParameters: {
|
currentParameters: {
|
||||||
databaseDocumentID: action.payload.databaseDocumentID,
|
databaseDocumentID: action.payload.databaseDocumentID,
|
||||||
indices: action.payload.indices,
|
indices: action.payload.indices,
|
||||||
|
filters: action.payload.filters,
|
||||||
|
dataRequestID: refreshCount,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
resolverComponentInstanceID: action.payload.resolverComponentInstanceID,
|
resolverComponentInstanceID: action.payload.resolverComponentInstanceID,
|
||||||
|
@ -57,6 +61,8 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
|
||||||
pendingRequestParameters: {
|
pendingRequestParameters: {
|
||||||
databaseDocumentID: action.payload.databaseDocumentID,
|
databaseDocumentID: action.payload.databaseDocumentID,
|
||||||
indices: action.payload.indices,
|
indices: action.payload.indices,
|
||||||
|
dataRequestID: action.payload.dataRequestID,
|
||||||
|
filters: action.payload.filters,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -117,12 +123,6 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
|
||||||
} else {
|
} else {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
} else if (action.type === 'serverReturnedRelatedEventData') {
|
|
||||||
const nextState: DataState = {
|
|
||||||
...state,
|
|
||||||
relatedEvents: new Map([...state.relatedEvents, [action.payload.entityID, action.payload]]),
|
|
||||||
};
|
|
||||||
return nextState;
|
|
||||||
} else if (action.type === 'serverReturnedNodeEventsInCategory') {
|
} else if (action.type === 'serverReturnedNodeEventsInCategory') {
|
||||||
// The data in the action could be irrelevant if the panel view or parameters have changed since the corresponding request was made. In that case, ignore this action.
|
// The data in the action could be irrelevant if the panel view or parameters have changed since the corresponding request was made. In that case, ignore this action.
|
||||||
if (
|
if (
|
||||||
|
@ -141,7 +141,9 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
|
||||||
if (updated) {
|
if (updated) {
|
||||||
const next: DataState = {
|
const next: DataState = {
|
||||||
...state,
|
...state,
|
||||||
nodeEventsInCategory: updated,
|
nodeEventsInCategory: {
|
||||||
|
...updated,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return next;
|
return next;
|
||||||
} else {
|
} else {
|
||||||
|
@ -235,7 +237,7 @@ export const dataReducer: Reducer<DataState, ResolverAction> = (state = initialS
|
||||||
...state,
|
...state,
|
||||||
currentRelatedEvent: {
|
currentRelatedEvent: {
|
||||||
loading: false,
|
loading: false,
|
||||||
data: action.payload,
|
...action.payload,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return nextState;
|
return nextState;
|
||||||
|
|
|
@ -139,6 +139,8 @@ describe('data state', () => {
|
||||||
// `locationSearch` doesn't matter for this test
|
// `locationSearch` doesn't matter for this test
|
||||||
locationSearch: '',
|
locationSearch: '',
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -152,7 +154,7 @@ describe('data state', () => {
|
||||||
has an error: false
|
has an error: false
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[]}
|
parameters to fetch: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":0}
|
||||||
requires a pending request to be aborted: null"
|
requires a pending request to be aborted: null"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@ -163,7 +165,7 @@ describe('data state', () => {
|
||||||
actions = [
|
actions = [
|
||||||
{
|
{
|
||||||
type: 'appRequestedResolverData',
|
type: 'appRequestedResolverData',
|
||||||
payload: { databaseDocumentID, indices: [] },
|
payload: { databaseDocumentID, indices: [], filters: {} },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
@ -182,7 +184,7 @@ describe('data state', () => {
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: null
|
parameters to fetch: null
|
||||||
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[]}"
|
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{}}"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -200,35 +202,34 @@ describe('data state', () => {
|
||||||
// `locationSearch` doesn't matter for this test
|
// `locationSearch` doesn't matter for this test
|
||||||
locationSearch: '',
|
locationSearch: '',
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'appRequestedResolverData',
|
type: 'appRequestedResolverData',
|
||||||
payload: { databaseDocumentID, indices: [] },
|
payload: { databaseDocumentID, indices: [], filters: {} },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
it('should be loading', () => {
|
it('should be loading', () => {
|
||||||
expect(selectors.isTreeLoading(state())).toBe(true);
|
expect(selectors.isTreeLoading(state())).toBe(true);
|
||||||
});
|
});
|
||||||
it('should not have a request to abort', () => {
|
|
||||||
expect(selectors.treeRequestParametersToAbort(state())).toBe(null);
|
|
||||||
});
|
|
||||||
it('should not have an error, more children, more ancestors, a request to make, or a pending request that should be aborted.', () => {
|
it('should not have an error, more children, more ancestors, a request to make, or a pending request that should be aborted.', () => {
|
||||||
expect(viewAsAString(state())).toMatchInlineSnapshot(`
|
expect(viewAsAString(state())).toMatchInlineSnapshot(`
|
||||||
"is loading: true
|
"is loading: true
|
||||||
has an error: false
|
has an error: false
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: null
|
parameters to fetch: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":0}
|
||||||
requires a pending request to be aborted: null"
|
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{}}"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
describe('when the pending request fails', () => {
|
describe('when the pending request fails', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
actions.push({
|
actions.push({
|
||||||
type: 'serverFailedToReturnResolverData',
|
type: 'serverFailedToReturnResolverData',
|
||||||
payload: { databaseDocumentID, indices: [] },
|
payload: { databaseDocumentID, indices: [], filters: {} },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should not be loading', () => {
|
it('should not be loading', () => {
|
||||||
|
@ -243,7 +244,7 @@ describe('data state', () => {
|
||||||
has an error: true
|
has an error: true
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: null
|
parameters to fetch: {\\"databaseDocumentID\\":\\"databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":0}
|
||||||
requires a pending request to be aborted: null"
|
requires a pending request to be aborted: null"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@ -265,12 +266,14 @@ describe('data state', () => {
|
||||||
// `locationSearch` doesn't matter for this test
|
// `locationSearch` doesn't matter for this test
|
||||||
locationSearch: '',
|
locationSearch: '',
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// this happens when the middleware starts the request
|
// this happens when the middleware starts the request
|
||||||
{
|
{
|
||||||
type: 'appRequestedResolverData',
|
type: 'appRequestedResolverData',
|
||||||
payload: { databaseDocumentID: firstDatabaseDocumentID, indices: [] },
|
payload: { databaseDocumentID: firstDatabaseDocumentID, indices: [], filters: {} },
|
||||||
},
|
},
|
||||||
// receive a different databaseDocumentID. this should cause the middleware to abort the existing request and start a new one
|
// receive a different databaseDocumentID. this should cause the middleware to abort the existing request and start a new one
|
||||||
{
|
{
|
||||||
|
@ -281,6 +284,8 @@ describe('data state', () => {
|
||||||
// `locationSearch` doesn't matter for this test
|
// `locationSearch` doesn't matter for this test
|
||||||
locationSearch: '',
|
locationSearch: '',
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -307,15 +312,103 @@ describe('data state', () => {
|
||||||
has an error: false
|
has an error: false
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: {\\"databaseDocumentID\\":\\"second databaseDocumentID\\",\\"indices\\":[]}
|
parameters to fetch: {\\"databaseDocumentID\\":\\"second databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":0}
|
||||||
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"first databaseDocumentID\\",\\"indices\\":[]}"
|
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"first databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{}}"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
describe('when after initial load resolver is told to refresh', () => {
|
||||||
|
const databaseDocumentID = 'doc id';
|
||||||
|
const resolverComponentInstanceID = 'instance';
|
||||||
|
const originID = 'origin';
|
||||||
|
const firstChildID = 'first';
|
||||||
|
const secondChildID = 'second';
|
||||||
|
const { resolverTree } = mockTreeWithNoAncestorsAnd2Children({
|
||||||
|
originID,
|
||||||
|
firstChildID,
|
||||||
|
secondChildID,
|
||||||
|
});
|
||||||
|
const { schema, dataSource } = endpointSourceSchema();
|
||||||
|
beforeEach(() => {
|
||||||
|
actions = [
|
||||||
|
// receive the document ID, this would cause the middleware to start the request
|
||||||
|
{
|
||||||
|
type: 'appReceivedNewExternalProperties',
|
||||||
|
payload: {
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
locationSearch: '',
|
||||||
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// this happens when the middleware starts the request
|
||||||
|
{
|
||||||
|
type: 'appRequestedResolverData',
|
||||||
|
payload: { databaseDocumentID, indices: [], dataRequestID: 99, filters: {} },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'serverReturnedResolverData',
|
||||||
|
payload: {
|
||||||
|
result: resolverTree,
|
||||||
|
dataSource,
|
||||||
|
schema,
|
||||||
|
parameters: { databaseDocumentID, indices: [], dataRequestID: 0, filters: {} },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// receive all the same parameters except shouldUpdate is true
|
||||||
|
{
|
||||||
|
type: 'appReceivedNewExternalProperties',
|
||||||
|
payload: {
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
locationSearch: '',
|
||||||
|
indices: [],
|
||||||
|
shouldUpdate: true,
|
||||||
|
filters: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'appReceivedNewExternalProperties',
|
||||||
|
payload: {
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
locationSearch: '',
|
||||||
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'appRequestedResolverData',
|
||||||
|
payload: { databaseDocumentID, indices: [], dataRequestID: 2, filters: {} },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
it('should need to request the tree using the same parameters as the first request', () => {
|
||||||
|
expect(selectors.treeParametersToFetch(state())?.databaseDocumentID).toBe(
|
||||||
|
databaseDocumentID
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should have a newer id', () => {
|
||||||
|
expect(selectors.treeParametersToFetch(state())?.dataRequestID).toBe(1);
|
||||||
|
});
|
||||||
|
it('should not have an error, more children, or more ancestors.', () => {
|
||||||
|
expect(viewAsAString(state())).toMatchInlineSnapshot(`
|
||||||
|
"is loading: true
|
||||||
|
has an error: false
|
||||||
|
has more children: false
|
||||||
|
has more ancestors: false
|
||||||
|
parameters to fetch: {\\"databaseDocumentID\\":\\"doc id\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":1}
|
||||||
|
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"doc id\\",\\"indices\\":[],\\"dataRequestID\\":2,\\"filters\\":{}}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
describe('and when the old request was aborted', () => {
|
describe('and when the old request was aborted', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
actions.push({
|
actions.push({
|
||||||
type: 'appAbortedResolverDataRequest',
|
type: 'appAbortedResolverDataRequest',
|
||||||
payload: { databaseDocumentID: firstDatabaseDocumentID, indices: [] },
|
payload: { databaseDocumentID: firstDatabaseDocumentID, indices: [], filters: {} },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should not require a pending request to be aborted', () => {
|
it('should not require a pending request to be aborted', () => {
|
||||||
|
@ -335,7 +428,7 @@ describe('data state', () => {
|
||||||
has an error: false
|
has an error: false
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: {\\"databaseDocumentID\\":\\"second databaseDocumentID\\",\\"indices\\":[]}
|
parameters to fetch: {\\"databaseDocumentID\\":\\"second databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":0}
|
||||||
requires a pending request to be aborted: null"
|
requires a pending request to be aborted: null"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@ -343,12 +436,9 @@ describe('data state', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
actions.push({
|
actions.push({
|
||||||
type: 'appRequestedResolverData',
|
type: 'appRequestedResolverData',
|
||||||
payload: { databaseDocumentID: secondDatabaseDocumentID, indices: [] },
|
payload: { databaseDocumentID: secondDatabaseDocumentID, indices: [], filters: {} },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should not have a document ID to fetch', () => {
|
|
||||||
expect(selectors.treeParametersToFetch(state())).toBe(null);
|
|
||||||
});
|
|
||||||
it('should be loading', () => {
|
it('should be loading', () => {
|
||||||
expect(selectors.isTreeLoading(state())).toBe(true);
|
expect(selectors.isTreeLoading(state())).toBe(true);
|
||||||
});
|
});
|
||||||
|
@ -358,8 +448,8 @@ describe('data state', () => {
|
||||||
has an error: false
|
has an error: false
|
||||||
has more children: false
|
has more children: false
|
||||||
has more ancestors: false
|
has more ancestors: false
|
||||||
parameters to fetch: null
|
parameters to fetch: {\\"databaseDocumentID\\":\\"second databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{},\\"dataRequestID\\":0}
|
||||||
requires a pending request to be aborted: null"
|
requires a pending request to be aborted: {\\"databaseDocumentID\\":\\"second databaseDocumentID\\",\\"indices\\":[],\\"filters\\":{}}"
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -414,6 +504,7 @@ describe('data state', () => {
|
||||||
// mock the requested size being larger than the returned number of events so we
|
// mock the requested size being larger than the returned number of events so we
|
||||||
// avoid the case where the limit was reached
|
// avoid the case where the limit was reached
|
||||||
numberOfRequestedEvents: nodeData.length + 1,
|
numberOfRequestedEvents: nodeData.length + 1,
|
||||||
|
dataRequestID: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,7 @@ import {
|
||||||
IsometricTaxiLayout,
|
IsometricTaxiLayout,
|
||||||
NodeData,
|
NodeData,
|
||||||
NodeDataStatus,
|
NodeDataStatus,
|
||||||
|
TimeRange,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import * as indexedProcessTreeModel from '../../models/indexed_process_tree';
|
import * as indexedProcessTreeModel from '../../models/indexed_process_tree';
|
||||||
import * as nodeModel from '../../../../common/endpoint/models/node';
|
import * as nodeModel from '../../../../common/endpoint/models/node';
|
||||||
|
@ -33,6 +34,7 @@ import {
|
||||||
import * as resolverTreeModel from '../../models/resolver_tree';
|
import * as resolverTreeModel from '../../models/resolver_tree';
|
||||||
import * as treeFetcherParametersModel from '../../models/tree_fetcher_parameters';
|
import * as treeFetcherParametersModel from '../../models/tree_fetcher_parameters';
|
||||||
import * as isometricTaxiLayoutModel from '../../models/indexed_process_tree/isometric_taxi_layout';
|
import * as isometricTaxiLayoutModel from '../../models/indexed_process_tree/isometric_taxi_layout';
|
||||||
|
import * as timeRangeModel from '../../models/time_range';
|
||||||
import * as aabbModel from '../../models/aabb';
|
import * as aabbModel from '../../models/aabb';
|
||||||
import * as vector2 from '../../models/vector2';
|
import * as vector2 from '../../models/vector2';
|
||||||
|
|
||||||
|
@ -93,6 +95,40 @@ export const originID: (state: DataState) => string | undefined = createSelector
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function currentRelatedEventRequestID(state: DataState): number | undefined {
|
||||||
|
if (state.currentRelatedEvent) {
|
||||||
|
return state.currentRelatedEvent?.dataRequestID;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentNodeEventsInCategoryRequestID(state: DataState): number | undefined {
|
||||||
|
if (state.nodeEventsInCategory?.pendingRequest) {
|
||||||
|
return state.nodeEventsInCategory.pendingRequest?.dataRequestID;
|
||||||
|
} else if (state.nodeEventsInCategory) {
|
||||||
|
return state.nodeEventsInCategory?.dataRequestID;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const eventsInCategoryResultIsStale = createSelector(
|
||||||
|
currentNodeEventsInCategoryRequestID,
|
||||||
|
refreshCount,
|
||||||
|
function eventsInCategoryResultIsStale(oldID, newID) {
|
||||||
|
return oldID !== undefined && oldID !== newID;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const currentRelatedEventIsStale = createSelector(
|
||||||
|
currentRelatedEventRequestID,
|
||||||
|
refreshCount,
|
||||||
|
function currentRelatedEventIsStale(oldID, newID) {
|
||||||
|
return oldID !== undefined && oldID !== newID;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a data structure for accessing events for specific nodes in a graph. For Endpoint graphs these nodes will be
|
* Returns a data structure for accessing events for specific nodes in a graph. For Endpoint graphs these nodes will be
|
||||||
* process lifecycle events.
|
* process lifecycle events.
|
||||||
|
@ -224,6 +260,10 @@ export const relatedEventCountByCategory: (
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export function refreshCount(state: DataState) {
|
||||||
|
return state.refreshCount;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if there might be more generations in the graph that we didn't get because we reached
|
* Returns true if there might be more generations in the graph that we didn't get because we reached
|
||||||
* the requested generations limit.
|
* the requested generations limit.
|
||||||
|
@ -305,6 +345,30 @@ export function treeParametersToFetch(state: DataState): TreeFetcherParameters |
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const timeRangeFilters = createSelector(
|
||||||
|
treeParametersToFetch,
|
||||||
|
function timeRangeFilters(treeParameters): TimeRange {
|
||||||
|
// Should always be provided from date picker, but provide valid defaults in any case.
|
||||||
|
const from = new Date(0);
|
||||||
|
const to = new Date(timeRangeModel.maxDate);
|
||||||
|
const timeRange = {
|
||||||
|
from: from.toISOString(),
|
||||||
|
to: to.toISOString(),
|
||||||
|
};
|
||||||
|
if (treeParameters !== null) {
|
||||||
|
if (treeParameters.filters.from) {
|
||||||
|
timeRange.from = treeParameters.filters.from;
|
||||||
|
}
|
||||||
|
if (treeParameters.filters.to) {
|
||||||
|
timeRange.to = treeParameters.filters.to;
|
||||||
|
}
|
||||||
|
return timeRange;
|
||||||
|
} else {
|
||||||
|
return timeRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The indices to use for the requests with the backend.
|
* The indices to use for the requests with the backend.
|
||||||
*/
|
*/
|
||||||
|
@ -682,7 +746,7 @@ export const isLoadingNodeEventsInCategory = createSelector(
|
||||||
(state: DataState) => state.nodeEventsInCategory,
|
(state: DataState) => state.nodeEventsInCategory,
|
||||||
panelViewAndParameters,
|
panelViewAndParameters,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
function (nodeEventsInCategory, panelViewAndParameters) {
|
function (nodeEventsInCategory, panelViewAndParameters): boolean {
|
||||||
const { panelView } = panelViewAndParameters;
|
const { panelView } = panelViewAndParameters;
|
||||||
return panelView === 'nodeEventsInCategory' && nodeEventsInCategory === undefined;
|
return panelView === 'nodeEventsInCategory' && nodeEventsInCategory === undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { SafeResolverEvent } from '../../../../common/endpoint/types';
|
||||||
|
|
||||||
import { ResolverState, DataAccessLayer, PanelViewAndParameters } from '../../types';
|
import { ResolverState, DataAccessLayer, PanelViewAndParameters } from '../../types';
|
||||||
import * as selectors from '../selectors';
|
import * as selectors from '../selectors';
|
||||||
import { createRange } from './../../models/time_range';
|
|
||||||
import { ResolverAction } from '../actions';
|
import { ResolverAction } from '../actions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,10 +34,14 @@ export function CurrentRelatedEventFetcher(
|
||||||
const indices = selectors.treeParameterIndices(state);
|
const indices = selectors.treeParameterIndices(state);
|
||||||
|
|
||||||
const oldParams = last;
|
const oldParams = last;
|
||||||
|
const newID = selectors.refreshCount(state);
|
||||||
last = newParams;
|
last = newParams;
|
||||||
|
|
||||||
// If the panel view params have changed and the current panel view is the `eventDetail`, then fetch the event details for that eventID.
|
// If the panel view params have changed and the current panel view is the `eventDetail`, then fetch the event details for that eventID.
|
||||||
if (!isEqual(newParams, oldParams) && newParams.panelView === 'eventDetail') {
|
if (
|
||||||
|
(!isEqual(newParams, oldParams) && newParams.panelView === 'eventDetail') ||
|
||||||
|
(selectors.currentRelatedEventIsStale(state) && newParams.panelView === 'eventDetail')
|
||||||
|
) {
|
||||||
const currentEventID = newParams.panelParameters.eventID;
|
const currentEventID = newParams.panelParameters.eventID;
|
||||||
const currentNodeID = newParams.panelParameters.nodeID;
|
const currentNodeID = newParams.panelParameters.nodeID;
|
||||||
const currentEventCategory = newParams.panelParameters.eventCategory;
|
const currentEventCategory = newParams.panelParameters.eventCategory;
|
||||||
|
@ -48,8 +51,10 @@ export function CurrentRelatedEventFetcher(
|
||||||
api.dispatch({
|
api.dispatch({
|
||||||
type: 'appRequestedCurrentRelatedEventData',
|
type: 'appRequestedCurrentRelatedEventData',
|
||||||
});
|
});
|
||||||
|
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||||
|
|
||||||
let result: SafeResolverEvent | null = null;
|
let result: SafeResolverEvent | null = null;
|
||||||
|
let payload: { data: SafeResolverEvent; dataRequestID: number } | null = null;
|
||||||
try {
|
try {
|
||||||
result = await dataAccessLayer.event({
|
result = await dataAccessLayer.event({
|
||||||
nodeID: currentNodeID,
|
nodeID: currentNodeID,
|
||||||
|
@ -58,7 +63,7 @@ export function CurrentRelatedEventFetcher(
|
||||||
eventID: currentEventID,
|
eventID: currentEventID,
|
||||||
winlogRecordID,
|
winlogRecordID,
|
||||||
indexPatterns: indices,
|
indexPatterns: indices,
|
||||||
timeRange: createRange(),
|
timeRange: timeRangeFilters,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
api.dispatch({
|
api.dispatch({
|
||||||
|
@ -67,9 +72,13 @@ export function CurrentRelatedEventFetcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
payload = {
|
||||||
|
data: result,
|
||||||
|
dataRequestID: newID,
|
||||||
|
};
|
||||||
api.dispatch({
|
api.dispatch({
|
||||||
type: 'serverReturnedCurrentRelatedEventData',
|
type: 'serverReturnedCurrentRelatedEventData',
|
||||||
payload: result,
|
payload,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
api.dispatch({
|
api.dispatch({
|
||||||
|
|
|
@ -10,7 +10,6 @@ import { SafeResolverEvent } from '../../../../common/endpoint/types';
|
||||||
import { ResolverState, DataAccessLayer } from '../../types';
|
import { ResolverState, DataAccessLayer } from '../../types';
|
||||||
import * as selectors from '../selectors';
|
import * as selectors from '../selectors';
|
||||||
import { ResolverAction } from '../actions';
|
import { ResolverAction } from '../actions';
|
||||||
import { createRange } from './../../models/time_range';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max number of nodes to request from the server
|
* Max number of nodes to request from the server
|
||||||
|
@ -59,10 +58,12 @@ export function NodeDataFetcher(
|
||||||
});
|
});
|
||||||
|
|
||||||
let results: SafeResolverEvent[] | undefined;
|
let results: SafeResolverEvent[] | undefined;
|
||||||
|
const newID = selectors.refreshCount(state);
|
||||||
try {
|
try {
|
||||||
|
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||||
results = await dataAccessLayer.nodeData({
|
results = await dataAccessLayer.nodeData({
|
||||||
ids: Array.from(newIDsToRequest),
|
ids: Array.from(newIDsToRequest),
|
||||||
timeRange: createRange(),
|
timeRange: timeRangeFilters,
|
||||||
indexPatterns: indices,
|
indexPatterns: indices,
|
||||||
limit: nodeDataLimit,
|
limit: nodeDataLimit,
|
||||||
});
|
});
|
||||||
|
@ -111,6 +112,7 @@ export function NodeDataFetcher(
|
||||||
* if that node is still in view we'll request its node data.
|
* if that node is still in view we'll request its node data.
|
||||||
*/
|
*/
|
||||||
numberOfRequestedEvents: nodeDataLimit,
|
numberOfRequestedEvents: nodeDataLimit,
|
||||||
|
dataRequestID: newID,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { ResolverPaginatedEvents } from '../../../../common/endpoint/types';
|
||||||
import { ResolverState, DataAccessLayer, PanelViewAndParameters } from '../../types';
|
import { ResolverState, DataAccessLayer, PanelViewAndParameters } from '../../types';
|
||||||
import * as selectors from '../selectors';
|
import * as selectors from '../selectors';
|
||||||
import { ResolverAction } from '../actions';
|
import { ResolverAction } from '../actions';
|
||||||
import { createRange } from './../../models/time_range';
|
|
||||||
|
|
||||||
export function RelatedEventsFetcher(
|
export function RelatedEventsFetcher(
|
||||||
dataAccessLayer: DataAccessLayer,
|
dataAccessLayer: DataAccessLayer,
|
||||||
|
@ -30,6 +29,9 @@ export function RelatedEventsFetcher(
|
||||||
const indices = selectors.treeParameterIndices(state);
|
const indices = selectors.treeParameterIndices(state);
|
||||||
|
|
||||||
const oldParams = last;
|
const oldParams = last;
|
||||||
|
const newID = selectors.refreshCount(state);
|
||||||
|
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||||
|
|
||||||
// Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info.
|
// Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info.
|
||||||
last = newParams;
|
last = newParams;
|
||||||
|
|
||||||
|
@ -37,12 +39,15 @@ export function RelatedEventsFetcher(
|
||||||
nodeID,
|
nodeID,
|
||||||
eventCategory,
|
eventCategory,
|
||||||
cursor,
|
cursor,
|
||||||
|
dataRequestID,
|
||||||
}: {
|
}: {
|
||||||
nodeID: string;
|
nodeID: string;
|
||||||
eventCategory: string;
|
eventCategory: string;
|
||||||
cursor: string | null;
|
cursor: string | null;
|
||||||
|
dataRequestID?: number;
|
||||||
}) {
|
}) {
|
||||||
let result: ResolverPaginatedEvents | null = null;
|
let result: ResolverPaginatedEvents | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
result = await dataAccessLayer.eventsWithEntityIDAndCategory({
|
result = await dataAccessLayer.eventsWithEntityIDAndCategory({
|
||||||
|
@ -50,14 +55,14 @@ export function RelatedEventsFetcher(
|
||||||
category: eventCategory,
|
category: eventCategory,
|
||||||
after: cursor,
|
after: cursor,
|
||||||
indexPatterns: indices,
|
indexPatterns: indices,
|
||||||
timeRange: createRange(),
|
timeRange: timeRangeFilters,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
result = await dataAccessLayer.eventsWithEntityIDAndCategory({
|
result = await dataAccessLayer.eventsWithEntityIDAndCategory({
|
||||||
entityID: nodeID,
|
entityID: nodeID,
|
||||||
category: eventCategory,
|
category: eventCategory,
|
||||||
indexPatterns: indices,
|
indexPatterns: indices,
|
||||||
timeRange: createRange(),
|
timeRange: timeRangeFilters,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -79,19 +84,29 @@ export function RelatedEventsFetcher(
|
||||||
eventCategory,
|
eventCategory,
|
||||||
cursor: result.nextEvent,
|
cursor: result.nextEvent,
|
||||||
nodeID,
|
nodeID,
|
||||||
|
dataRequestID: newID,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the panel view params have changed and the current panel view is either `nodeEventsInCategory` or `eventDetail`, then fetch the related events for that nodeID.
|
// If the panel view params have changed and the current panel view is either `nodeEventsInCategory` or `eventDetail`, then fetch the related events for that nodeID.
|
||||||
if (!isEqual(newParams, oldParams)) {
|
if (!isEqual(newParams, oldParams) || selectors.eventsInCategoryResultIsStale(state)) {
|
||||||
if (newParams.panelView === 'nodeEventsInCategory') {
|
if (newParams.panelView === 'nodeEventsInCategory') {
|
||||||
const nodeID = newParams.panelParameters.nodeID;
|
const nodeID = newParams.panelParameters.nodeID;
|
||||||
|
api.dispatch({
|
||||||
|
type: 'appRequestedNodeEventsInCategory',
|
||||||
|
payload: {
|
||||||
|
parameters: newParams,
|
||||||
|
dataRequestID: newID,
|
||||||
|
},
|
||||||
|
});
|
||||||
fetchEvents({
|
fetchEvents({
|
||||||
nodeID,
|
nodeID,
|
||||||
eventCategory: newParams.panelParameters.eventCategory,
|
eventCategory: newParams.panelParameters.eventCategory,
|
||||||
cursor: null,
|
cursor: null,
|
||||||
|
// only use the id for initial requests, reuse for load more.
|
||||||
|
dataRequestID: newID,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (isLoadingMoreEvents) {
|
} else if (isLoadingMoreEvents) {
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { ResolverState, DataAccessLayer } from '../../types';
|
||||||
import * as selectors from '../selectors';
|
import * as selectors from '../selectors';
|
||||||
import { ResolverAction } from '../actions';
|
import { ResolverAction } from '../actions';
|
||||||
import { ancestorsRequestAmount, descendantsRequestAmount } from '../../models/resolver_tree';
|
import { ancestorsRequestAmount, descendantsRequestAmount } from '../../models/resolver_tree';
|
||||||
import { createRange } from './../../models/time_range';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that handles syncing ResolverTree data w/ the current entity ID.
|
* A function that handles syncing ResolverTree data w/ the current entity ID.
|
||||||
|
@ -45,6 +44,8 @@ export function ResolverTreeFetcher(
|
||||||
let dataSource: string | undefined;
|
let dataSource: string | undefined;
|
||||||
let dataSourceSchema: ResolverSchema | undefined;
|
let dataSourceSchema: ResolverSchema | undefined;
|
||||||
let result: ResolverNode[] | undefined;
|
let result: ResolverNode[] | undefined;
|
||||||
|
const timeRangeFilters = selectors.timeRangeFilters(state);
|
||||||
|
|
||||||
// Inform the state that we've made the request. Without this, the middleware will try to make the request again
|
// Inform the state that we've made the request. Without this, the middleware will try to make the request again
|
||||||
// immediately.
|
// immediately.
|
||||||
api.dispatch({
|
api.dispatch({
|
||||||
|
@ -70,7 +71,7 @@ export function ResolverTreeFetcher(
|
||||||
result = await dataAccessLayer.resolverTree({
|
result = await dataAccessLayer.resolverTree({
|
||||||
dataId: entityIDToFetch,
|
dataId: entityIDToFetch,
|
||||||
schema: dataSourceSchema,
|
schema: dataSourceSchema,
|
||||||
timeRange: createRange(),
|
timeRange: timeRangeFilters,
|
||||||
indices: databaseParameters.indices,
|
indices: databaseParameters.indices,
|
||||||
ancestors: ancestorsRequestAmount(dataSourceSchema),
|
ancestors: ancestorsRequestAmount(dataSourceSchema),
|
||||||
descendants: descendantsRequestAmount(),
|
descendants: descendantsRequestAmount(),
|
||||||
|
|
|
@ -132,6 +132,13 @@ export const currentRelatedEventData = composeSelectors(
|
||||||
dataSelectors.currentRelatedEventData
|
dataSelectors.currentRelatedEventData
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter indicating how many times a user has requested new data for resolver.
|
||||||
|
*/
|
||||||
|
export const refreshCount = composeSelectors(dataStateSelector, dataSelectors.refreshCount);
|
||||||
|
|
||||||
|
export const timeRangeFilters = composeSelectors(dataStateSelector, dataSelectors.timeRangeFilters);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the id of the "current" tree node (fake-focused)
|
* Returns the id of the "current" tree node (fake-focused)
|
||||||
*/
|
*/
|
||||||
|
@ -359,6 +366,16 @@ export const isLoadingMoreNodeEventsInCategory = composeSelectors(
|
||||||
dataSelectors.isLoadingMoreNodeEventsInCategory
|
dataSelectors.isLoadingMoreNodeEventsInCategory
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const eventsInCategoryResultIsStale = composeSelectors(
|
||||||
|
dataStateSelector,
|
||||||
|
dataSelectors.eventsInCategoryResultIsStale
|
||||||
|
);
|
||||||
|
|
||||||
|
export const currentRelatedEventIsStale = composeSelectors(
|
||||||
|
dataStateSelector,
|
||||||
|
dataSelectors.currentRelatedEventIsStale
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the state of the node, loading, running, or terminated.
|
* Returns the state of the node, loading, running, or terminated.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,7 +13,13 @@ import { spyMiddlewareFactory } from '../spy_middleware_factory';
|
||||||
import { resolverMiddlewareFactory } from '../../store/middleware';
|
import { resolverMiddlewareFactory } from '../../store/middleware';
|
||||||
import { resolverReducer } from '../../store/reducer';
|
import { resolverReducer } from '../../store/reducer';
|
||||||
import { MockResolver } from './mock_resolver';
|
import { MockResolver } from './mock_resolver';
|
||||||
import { ResolverState, DataAccessLayer, SpyMiddleware, SideEffectSimulator } from '../../types';
|
import {
|
||||||
|
ResolverState,
|
||||||
|
DataAccessLayer,
|
||||||
|
SpyMiddleware,
|
||||||
|
SideEffectSimulator,
|
||||||
|
TimeFilters,
|
||||||
|
} from '../../types';
|
||||||
import { ResolverAction } from '../../store/actions';
|
import { ResolverAction } from '../../store/actions';
|
||||||
import { sideEffectSimulatorFactory } from '../../view/side_effect_simulator_factory';
|
import { sideEffectSimulatorFactory } from '../../view/side_effect_simulator_factory';
|
||||||
import { uiSetting } from '../../mocks/ui_setting';
|
import { uiSetting } from '../../mocks/ui_setting';
|
||||||
|
@ -75,6 +81,8 @@ export class Simulator {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
indices,
|
indices,
|
||||||
history,
|
history,
|
||||||
|
filters,
|
||||||
|
shouldUpdate,
|
||||||
}: {
|
}: {
|
||||||
/**
|
/**
|
||||||
* A (mock) data access layer that will be used to create the Resolver store.
|
* A (mock) data access layer that will be used to create the Resolver store.
|
||||||
|
@ -93,6 +101,8 @@ export class Simulator {
|
||||||
*/
|
*/
|
||||||
databaseDocumentID: string;
|
databaseDocumentID: string;
|
||||||
history?: HistoryPackageHistoryInterface<never>;
|
history?: HistoryPackageHistoryInterface<never>;
|
||||||
|
filters: TimeFilters;
|
||||||
|
shouldUpdate: boolean;
|
||||||
}) {
|
}) {
|
||||||
// create the spy middleware (for debugging tests)
|
// create the spy middleware (for debugging tests)
|
||||||
this.spyMiddleware = spyMiddlewareFactory();
|
this.spyMiddleware = spyMiddlewareFactory();
|
||||||
|
@ -131,6 +141,8 @@ export class Simulator {
|
||||||
coreStart={coreStart}
|
coreStart={coreStart}
|
||||||
databaseDocumentID={databaseDocumentID}
|
databaseDocumentID={databaseDocumentID}
|
||||||
indices={indices}
|
indices={indices}
|
||||||
|
filters={filters}
|
||||||
|
shouldUpdate={shouldUpdate}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -170,6 +182,25 @@ export class Simulator {
|
||||||
return this.wrapper.prop('indices');
|
return this.wrapper.prop('indices');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the shouldUpdate prop (updates the React component props.)
|
||||||
|
*/
|
||||||
|
public set shouldUpdate(value: boolean) {
|
||||||
|
this.wrapper.setProps({ shouldUpdate: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
public get shouldUpdate(): boolean {
|
||||||
|
return this.wrapper.prop('shouldUpdate');
|
||||||
|
}
|
||||||
|
|
||||||
|
public set filters(value: TimeFilters) {
|
||||||
|
this.wrapper.setProps({ filters: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
public get filters(): TimeFilters {
|
||||||
|
return this.wrapper.prop('filters');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call this to console.log actions (and state). Use this to debug your tests.
|
* Call this to console.log actions (and state). Use this to debug your tests.
|
||||||
* State and actions aren't exposed otherwise because the tests using this simulator should
|
* State and actions aren't exposed otherwise because the tests using this simulator should
|
||||||
|
|
|
@ -100,6 +100,8 @@ export const MockResolver = React.memo((props: MockResolverProps) => {
|
||||||
databaseDocumentID={props.databaseDocumentID}
|
databaseDocumentID={props.databaseDocumentID}
|
||||||
resolverComponentInstanceID={props.resolverComponentInstanceID}
|
resolverComponentInstanceID={props.resolverComponentInstanceID}
|
||||||
indices={props.indices}
|
indices={props.indices}
|
||||||
|
shouldUpdate={props.shouldUpdate}
|
||||||
|
filters={props.filters}
|
||||||
/>
|
/>
|
||||||
</Provider>
|
</Provider>
|
||||||
</SideEffectContext.Provider>
|
</SideEffectContext.Provider>
|
||||||
|
|
|
@ -208,6 +208,13 @@ export interface TreeFetcherParameters {
|
||||||
* The indices that the backend will use to search for the document ID.
|
* The indices that the backend will use to search for the document ID.
|
||||||
*/
|
*/
|
||||||
indices: string[];
|
indices: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The count of data invalidation actions at the time the data was requested.
|
||||||
|
*/
|
||||||
|
dataRequestID?: number;
|
||||||
|
|
||||||
|
filters: TimeFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,6 +244,23 @@ export interface NodeEventsInCategoryState {
|
||||||
|
|
||||||
lastCursorRequested?: null | string;
|
lastCursorRequested?: null | string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request ID for the currently displayed events.
|
||||||
|
*/
|
||||||
|
dataRequestID?: number;
|
||||||
|
|
||||||
|
pendingRequest?: {
|
||||||
|
/**
|
||||||
|
* Parameters used for a request currently in progress.
|
||||||
|
*/
|
||||||
|
parameters: PanelViewAndParameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request ID for any inflight requests
|
||||||
|
*/
|
||||||
|
dataRequestID: number;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag for showing an error message when fetching additional related events.
|
* Flag for showing an error message when fetching additional related events.
|
||||||
*/
|
*/
|
||||||
|
@ -298,11 +322,6 @@ export interface NodeData {
|
||||||
* State for `data` reducer which handles receiving Resolver data from the back-end.
|
* State for `data` reducer which handles receiving Resolver data from the back-end.
|
||||||
*/
|
*/
|
||||||
export interface DataState {
|
export interface DataState {
|
||||||
/**
|
|
||||||
* @deprecated Use the API
|
|
||||||
*/
|
|
||||||
readonly relatedEvents: Map<string, ResolverRelatedEvents>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used when the panelView is `nodeEventsInCategory`.
|
* Used when the panelView is `nodeEventsInCategory`.
|
||||||
* Store the `nodeEventsInCategory` data for the current panel view. If the panel view or parameters change, the reducer may delete this.
|
* Store the `nodeEventsInCategory` data for the current panel view. If the panel view or parameters change, the reducer may delete this.
|
||||||
|
@ -310,6 +329,11 @@ export interface DataState {
|
||||||
*/
|
*/
|
||||||
readonly nodeEventsInCategory?: NodeEventsInCategoryState;
|
readonly nodeEventsInCategory?: NodeEventsInCategoryState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter used to have resolver fetch updated data.
|
||||||
|
*/
|
||||||
|
readonly refreshCount: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used when the panelView is `eventDetail`.
|
* Used when the panelView is `eventDetail`.
|
||||||
*
|
*
|
||||||
|
@ -317,6 +341,7 @@ export interface DataState {
|
||||||
readonly currentRelatedEvent: {
|
readonly currentRelatedEvent: {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
data: SafeResolverEvent | null;
|
data: SafeResolverEvent | null;
|
||||||
|
dataRequestID?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
readonly tree?: {
|
readonly tree?: {
|
||||||
|
@ -680,8 +705,8 @@ export interface IsometricTaxiLayout {
|
||||||
* Defines the type for bounding a search by a time box.
|
* Defines the type for bounding a search by a time box.
|
||||||
*/
|
*/
|
||||||
export interface TimeRange {
|
export interface TimeRange {
|
||||||
from: Date;
|
from: string;
|
||||||
to: Date;
|
to: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -791,6 +816,11 @@ export interface DataAccessLayer {
|
||||||
}) => Promise<ResolverEntityIndex>;
|
}) => Promise<ResolverEntityIndex>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TimeFilters {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The externally provided React props.
|
* The externally provided React props.
|
||||||
*/
|
*/
|
||||||
|
@ -815,6 +845,13 @@ export interface ResolverProps {
|
||||||
* Indices that the backend should use to find the originating document.
|
* Indices that the backend should use to find the originating document.
|
||||||
*/
|
*/
|
||||||
indices: string[];
|
indices: string[];
|
||||||
|
|
||||||
|
filters: TimeFilters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A flag to update data from an external source
|
||||||
|
*/
|
||||||
|
shouldUpdate: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -43,6 +43,8 @@ describe("Resolver, when rendered with the `indices` prop set to `[]` and the `d
|
||||||
dataAccessLayer,
|
dataAccessLayer,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -96,6 +98,8 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children',
|
||||||
dataAccessLayer,
|
dataAccessLayer,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -299,6 +303,8 @@ describe('Resolver, when using a generated tree with 20 generations, 4 children
|
||||||
dataAccessLayer: { ...generatorDAL, nodeData: nodeDataError },
|
dataAccessLayer: { ...generatorDAL, nodeData: nodeDataError },
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await findAndClickFirstLoadingNodeInPanel(simulator);
|
await findAndClickFirstLoadingNodeInPanel(simulator);
|
||||||
|
@ -354,6 +360,8 @@ describe('Resolver, when using a generated tree with 20 generations, 4 children
|
||||||
dataAccessLayer: generatorDAL,
|
dataAccessLayer: generatorDAL,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
await findAndClickFirstLoadingNodeInPanel(simulator);
|
await findAndClickFirstLoadingNodeInPanel(simulator);
|
||||||
|
@ -432,6 +440,8 @@ describe('Resolver, when analyzing a tree that has 2 related registry and 1 rela
|
||||||
dataAccessLayer,
|
dataAccessLayer,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ describe('graph controls: when relsover is loaded with an origin node', () => {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
originEntityID = entityIDs.origin;
|
originEntityID = entityIDs.origin;
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children',
|
||||||
dataAccessLayer,
|
dataAccessLayer,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,8 @@ describe(`Resolver: when analyzing a tree with no ancestors and two children and
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
history: memoryHistory,
|
history: memoryHistory,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
return simulatorInstance;
|
return simulatorInstance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ function EventDetailBreadcrumbs({
|
||||||
breadcrumbEventCategory: string;
|
breadcrumbEventCategory: string;
|
||||||
}) {
|
}) {
|
||||||
const countByCategory = useSelector((state: ResolverState) =>
|
const countByCategory = useSelector((state: ResolverState) =>
|
||||||
selectors.relatedEventCountByCategory(state)(nodeID, breadcrumbEventCategory)
|
selectors.relatedEventCountOfTypeForNode(state)(nodeID, breadcrumbEventCategory)
|
||||||
);
|
);
|
||||||
const relatedEventCount: number | undefined = useSelector((state: ResolverState) =>
|
const relatedEventCount: number | undefined = useSelector((state: ResolverState) =>
|
||||||
selectors.relatedEventTotalCount(state)(nodeID)
|
selectors.relatedEventTotalCount(state)(nodeID)
|
||||||
|
|
|
@ -41,6 +41,8 @@ describe.skip(`Resolver: when analyzing a tree with only the origin and paginate
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
history: memoryHistory,
|
history: memoryHistory,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
return simulatorInstance;
|
return simulatorInstance;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,8 @@ describe('Resolver: panel loading and resolution states', () => {
|
||||||
history: memoryHistory,
|
history: memoryHistory,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
memoryHistory.push({
|
memoryHistory.push({
|
||||||
|
@ -111,6 +113,8 @@ describe('Resolver: panel loading and resolution states', () => {
|
||||||
history: memoryHistory,
|
history: memoryHistory,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
memoryHistory.push({
|
memoryHistory.push({
|
||||||
search: queryStringWithEventDetailSelected,
|
search: queryStringWithEventDetailSelected,
|
||||||
|
@ -150,6 +154,8 @@ describe('Resolver: panel loading and resolution states', () => {
|
||||||
history: memoryHistory,
|
history: memoryHistory,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
memoryHistory.push({
|
memoryHistory.push({
|
||||||
|
@ -207,6 +213,8 @@ describe('Resolver: panel loading and resolution states', () => {
|
||||||
history: memoryHistory,
|
history: memoryHistory,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
memoryHistory.push({
|
memoryHistory.push({
|
||||||
|
|
|
@ -34,6 +34,8 @@ describe('Resolver, when analyzing a tree that has no ancestors and 2 children',
|
||||||
dataAccessLayer,
|
dataAccessLayer,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@ describe('Resolver: data loading and resolution states', () => {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,6 +60,8 @@ describe('Resolver: data loading and resolution states', () => {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -88,6 +92,8 @@ describe('Resolver: data loading and resolution states', () => {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -118,6 +124,8 @@ describe('Resolver: data loading and resolution states', () => {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -150,6 +158,8 @@ describe('Resolver: data loading and resolution states', () => {
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices: [],
|
indices: [],
|
||||||
|
shouldUpdate: false,
|
||||||
|
filters: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,14 @@ export const ResolverWithoutProviders = React.memo(
|
||||||
* Use `forwardRef` so that the `Simulator` used in testing can access the top level DOM element.
|
* Use `forwardRef` so that the `Simulator` used in testing can access the top level DOM element.
|
||||||
*/
|
*/
|
||||||
React.forwardRef(function (
|
React.forwardRef(function (
|
||||||
{ className, databaseDocumentID, resolverComponentInstanceID, indices }: ResolverProps,
|
{
|
||||||
|
className,
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
indices,
|
||||||
|
shouldUpdate,
|
||||||
|
filters,
|
||||||
|
}: ResolverProps,
|
||||||
refToForward
|
refToForward
|
||||||
) {
|
) {
|
||||||
useResolverQueryParamCleaner();
|
useResolverQueryParamCleaner();
|
||||||
|
@ -41,7 +48,13 @@ export const ResolverWithoutProviders = React.memo(
|
||||||
* This is responsible for dispatching actions that include any external data.
|
* This is responsible for dispatching actions that include any external data.
|
||||||
* `databaseDocumentID`
|
* `databaseDocumentID`
|
||||||
*/
|
*/
|
||||||
useStateSyncingActions({ databaseDocumentID, resolverComponentInstanceID, indices });
|
useStateSyncingActions({
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
indices,
|
||||||
|
shouldUpdate,
|
||||||
|
filters,
|
||||||
|
});
|
||||||
|
|
||||||
const { timestamp } = useContext(SideEffectContext);
|
const { timestamp } = useContext(SideEffectContext);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ export function useStateSyncingActions({
|
||||||
databaseDocumentID,
|
databaseDocumentID,
|
||||||
resolverComponentInstanceID,
|
resolverComponentInstanceID,
|
||||||
indices,
|
indices,
|
||||||
|
filters,
|
||||||
|
shouldUpdate,
|
||||||
}: {
|
}: {
|
||||||
/**
|
/**
|
||||||
* The `_id` of an event in ES. Used to determine the origin of the Resolver graph.
|
* The `_id` of an event in ES. Used to determine the origin of the Resolver graph.
|
||||||
|
@ -23,13 +25,30 @@ export function useStateSyncingActions({
|
||||||
databaseDocumentID: string;
|
databaseDocumentID: string;
|
||||||
resolverComponentInstanceID: string;
|
resolverComponentInstanceID: string;
|
||||||
indices: string[];
|
indices: string[];
|
||||||
|
shouldUpdate: boolean;
|
||||||
|
filters: object;
|
||||||
}) {
|
}) {
|
||||||
const dispatch = useResolverDispatch();
|
const dispatch = useResolverDispatch();
|
||||||
const locationSearch = useLocation().search;
|
const locationSearch = useLocation().search;
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'appReceivedNewExternalProperties',
|
type: 'appReceivedNewExternalProperties',
|
||||||
payload: { databaseDocumentID, resolverComponentInstanceID, locationSearch, indices },
|
payload: {
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
locationSearch,
|
||||||
|
indices,
|
||||||
|
shouldUpdate,
|
||||||
|
filters,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}, [dispatch, databaseDocumentID, resolverComponentInstanceID, locationSearch, indices]);
|
}, [
|
||||||
|
dispatch,
|
||||||
|
databaseDocumentID,
|
||||||
|
resolverComponentInstanceID,
|
||||||
|
locationSearch,
|
||||||
|
indices,
|
||||||
|
shouldUpdate,
|
||||||
|
filters,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,14 @@ import { timelineDefaults } from '../../store/timeline/defaults';
|
||||||
import { isFullScreen } from '../timeline/body/column_headers';
|
import { isFullScreen } from '../timeline/body/column_headers';
|
||||||
import { updateTimelineGraphEventId } from '../../../timelines/store/timeline/actions';
|
import { updateTimelineGraphEventId } from '../../../timelines/store/timeline/actions';
|
||||||
import { Resolver } from '../../../resolver/view';
|
import { Resolver } from '../../../resolver/view';
|
||||||
|
import {
|
||||||
|
isLoadingSelector,
|
||||||
|
startSelector,
|
||||||
|
endSelector,
|
||||||
|
} from '../../../common/components/super_date_picker/selectors';
|
||||||
|
import * as i18n from './translations';
|
||||||
import { useUiSetting$ } from '../../../common/lib/kibana';
|
import { useUiSetting$ } from '../../../common/lib/kibana';
|
||||||
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
|
import { useSignalIndex } from '../../../detections/containers/detection_engine/alerts/use_signal_index';
|
||||||
import * as i18n from './translations';
|
|
||||||
|
|
||||||
const OverlayContainer = styled.div`
|
const OverlayContainer = styled.div`
|
||||||
${({ $restrictWidth }: { $restrictWidth: boolean }) =>
|
${({ $restrictWidth }: { $restrictWidth: boolean }) =>
|
||||||
|
@ -119,6 +123,31 @@ const GraphOverlayComponent: React.FC<OwnProps> = ({ isEventViewer, timelineId }
|
||||||
|
|
||||||
const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
|
const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
|
||||||
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
||||||
|
const getStartSelector = useMemo(() => startSelector(), []);
|
||||||
|
const getEndSelector = useMemo(() => endSelector(), []);
|
||||||
|
const getIsLoadingSelector = useMemo(() => isLoadingSelector(), []);
|
||||||
|
const isActive = useMemo(() => timelineId === TimelineId.active, [timelineId]);
|
||||||
|
const shouldUpdate = useDeepEqualSelector((state) => {
|
||||||
|
if (isActive) {
|
||||||
|
return getIsLoadingSelector(state.inputs.timeline);
|
||||||
|
} else {
|
||||||
|
return getIsLoadingSelector(state.inputs.global);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const from = useDeepEqualSelector((state) => {
|
||||||
|
if (isActive) {
|
||||||
|
return getStartSelector(state.inputs.timeline);
|
||||||
|
} else {
|
||||||
|
return getStartSelector(state.inputs.global);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const to = useDeepEqualSelector((state) => {
|
||||||
|
if (isActive) {
|
||||||
|
return getEndSelector(state.inputs.timeline);
|
||||||
|
} else {
|
||||||
|
return getEndSelector(state.inputs.global);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const fullScreen = useMemo(
|
const fullScreen = useMemo(
|
||||||
() => isFullScreen({ globalFullScreen, timelineId, timelineFullScreen }),
|
() => isFullScreen({ globalFullScreen, timelineId, timelineFullScreen }),
|
||||||
|
@ -173,6 +202,8 @@ const GraphOverlayComponent: React.FC<OwnProps> = ({ isEventViewer, timelineId }
|
||||||
databaseDocumentID={graphEventId}
|
databaseDocumentID={graphEventId}
|
||||||
resolverComponentInstanceID={timelineId}
|
resolverComponentInstanceID={timelineId}
|
||||||
indices={indices}
|
indices={indices}
|
||||||
|
shouldUpdate={shouldUpdate}
|
||||||
|
filters={{ from, to }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<EuiFlexGroup alignItems="center" justifyContent="center" style={{ height: '100%' }}>
|
<EuiFlexGroup alignItems="center" justifyContent="center" style={{ height: '100%' }}>
|
||||||
|
|
|
@ -83,6 +83,8 @@ const AppRoot = React.memo(
|
||||||
databaseDocumentID=""
|
databaseDocumentID=""
|
||||||
resolverComponentInstanceID="test"
|
resolverComponentInstanceID="test"
|
||||||
indices={[]}
|
indices={[]}
|
||||||
|
shouldUpdate={false}
|
||||||
|
filters={{}}
|
||||||
/>
|
/>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
Loading…
Reference in a new issue