[SECURITY SOLUTIONS][Timeline] correlation bug (#96099)

* fix pagination bug with correlation

* fix close resolver to go back to prev tab
This commit is contained in:
Xavier Mouligneau 2021-04-02 09:00:51 -04:00 committed by GitHub
parent d2a484c5bd
commit f042ec8945
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 114 additions and 6 deletions

View file

@ -204,6 +204,7 @@ export const mockGlobalState: State = {
timelineById: {
test: {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.notes,
deletedEventIds: [],
id: 'test',
savedObjectId: null,

View file

@ -2062,6 +2062,7 @@ export const mockTimelineResults: OpenTimelineResult[] = [
export const mockTimelineModel: TimelineModel = {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.notes,
columns: [
{
columnHeaderType: 'not-filtered',
@ -2209,6 +2210,7 @@ export const defaultTimelineProps: CreateTimelineProps = {
from: '2018-11-05T18:58:25.937Z',
timeline: {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{ columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', width: 190 },
{ columnHeaderType: 'not-filtered', id: 'message', width: 180 },

View file

@ -108,6 +108,7 @@ describe('alert actions', () => {
notes: null,
timeline: {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',

View file

@ -240,6 +240,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',
@ -350,6 +351,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',
@ -460,6 +462,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',
@ -568,6 +571,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',
@ -676,6 +680,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
savedObjectId: 'savedObject-1',
columns: [
{
@ -852,6 +857,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
savedObjectId: 'savedObject-1',
columns: [
{
@ -1000,6 +1006,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.template);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',
@ -1110,6 +1117,7 @@ describe('helpers', () => {
const newTimeline = defaultTimelineToTimelineModel(timeline, false, TimelineType.default);
expect(newTimeline).toEqual({
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: [
{
columnHeaderType: 'not-filtered',

View file

@ -208,4 +208,35 @@ describe('useTimelineEvents', () => {
]);
});
});
test('Correlation pagination is calling search strategy when switching page', async () => {
await act(async () => {
const { result, waitForNextUpdate, rerender } = renderHook<
UseTimelineEventsProps,
[boolean, TimelineArgs]
>((args) => useTimelineEvents(args), {
initialProps: {
...props,
language: 'eql',
eqlOptions: {
eventCategoryField: 'category',
tiebreakerField: '',
timestampField: '@timestamp',
query: 'find it EQL',
size: 100,
},
},
});
// useEffect on params request
await waitForNextUpdate();
rerender({ ...props, startDate, endDate });
// useEffect on params request
await waitForNextUpdate();
expect(mockSearch).toHaveBeenCalledTimes(2);
result.current[1].loadPage(4);
await waitForNextUpdate();
expect(mockSearch).toHaveBeenCalledTimes(3);
});
});
});

View file

@ -143,7 +143,6 @@ export const useTimelineEvents = ({
activeTimeline.setExpandedDetail({});
activeTimeline.setActivePage(newActivePage);
}
setActivePage(newActivePage);
},
[clearSignalsState, id]
@ -294,22 +293,22 @@ export const useTimelineEvents = ({
querySize: prevRequest?.pagination.querySize ?? 0,
sort: prevRequest?.sort ?? initSortDefault,
timerange: prevRequest?.timerange ?? {},
...(prevEqlRequest?.eventCategoryField
...(!isEmpty(prevEqlRequest?.eventCategoryField)
? {
eventCategoryField: prevEqlRequest?.eventCategoryField,
}
: {}),
...(prevEqlRequest?.size
...(!isEmpty(prevEqlRequest?.size)
? {
size: prevEqlRequest?.size,
}
: {}),
...(prevEqlRequest?.tiebreakerField
...(!isEmpty(prevEqlRequest?.tiebreakerField)
? {
tiebreakerField: prevEqlRequest?.tiebreakerField,
}
: {}),
...(prevEqlRequest?.timestampField
...(!isEmpty(prevEqlRequest?.timestampField)
? {
timestampField: prevEqlRequest?.timestampField,
}

View file

@ -18,6 +18,7 @@ const { from: start, to: end } = normalizeTimeRange({ from: '', to: '' }, false)
export const timelineDefaults: SubsetTimelineModel &
Pick<TimelineModel, 'filters' | 'eqlOptions'> = {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.query,
columns: defaultHeaders,
dataProviders: [],
dateRange: { start, end },

View file

@ -16,6 +16,7 @@ describe('Epic Timeline', () => {
test('should return a TimelineInput instead of TimelineModel ', () => {
const timelineModel: TimelineModel = {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.notes,
columns: [
{
columnHeaderType: 'not-filtered',

View file

@ -305,6 +305,9 @@ export const updateGraphEventId = ({
[id]: {
...timeline,
graphEventId,
...(graphEventId === '' && id === TimelineId.active
? { activeTab: timeline.prevActiveTab, prevActiveTab: timeline.activeTab }
: {}),
},
};
};

View file

@ -51,6 +51,7 @@ export interface ColumnHeaderOptions {
export interface TimelineModel {
/** The selected tab to displayed in the timeline */
activeTab: TimelineTabs;
prevActiveTab: TimelineTabs;
/** The columns displayed in the timeline */
columns: ColumnHeaderOptions[];
/** Timeline saved object owner */
@ -142,6 +143,7 @@ export type SubsetTimelineModel = Readonly<
Pick<
TimelineModel,
| 'activeTab'
| 'prevActiveTab'
| 'columns'
| 'dataProviders'
| 'deletedEventIds'

View file

@ -6,7 +6,12 @@
*/
import { cloneDeep } from 'lodash/fp';
import { TimelineType, TimelineStatus, TimelineTabs } from '../../../../common/types/timeline';
import {
TimelineType,
TimelineStatus,
TimelineTabs,
TimelineId,
} from '../../../../common/types/timeline';
import {
IS_OPERATOR,
@ -39,6 +44,7 @@ import {
updateTimelineSort,
updateTimelineTitleAndDescription,
upsertTimelineColumn,
updateGraphEventId,
} from './helpers';
import { ColumnHeaderOptions, TimelineModel } from './model';
import { timelineDefaults } from './defaults';
@ -69,6 +75,7 @@ const basicDataProvider: DataProvider = {
};
const basicTimeline: TimelineModel = {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.graph,
columns: [],
dataProviders: [{ ...basicDataProvider }],
dateRange: {
@ -1757,4 +1764,55 @@ describe('Timeline', () => {
]);
});
});
describe('#updateGraphEventId', () => {
test('should return a new reference and not the same reference', () => {
const update = updateGraphEventId({
id: 'foo',
graphEventId: '123',
timelineById: timelineByIdMock,
});
expect(update).not.toBe(timelineByIdMock);
});
test('should empty graphEventId', () => {
const update = updateGraphEventId({
id: 'foo',
graphEventId: '',
timelineById: timelineByIdMock,
});
expect(update.foo.graphEventId).toEqual('');
});
test('should empty graphEventId and not change activeTab and prevActiveTab because TimelineId !== TimelineId.active', () => {
const update = updateGraphEventId({
id: 'foo',
graphEventId: '',
timelineById: timelineByIdMock,
});
expect(update.foo.graphEventId).toEqual('');
expect(update.foo.activeTab).toEqual(timelineByIdMock.foo.activeTab);
expect(update.foo.prevActiveTab).toEqual(timelineByIdMock.foo.prevActiveTab);
});
test('should empty graphEventId and return to the previous tab if TimelineId === TimelineId.active', () => {
const mock = cloneDeep(timelineByIdMock);
mock[TimelineId.active] = {
...timelineByIdMock.foo,
activeTab: TimelineTabs.graph,
prevActiveTab: TimelineTabs.eql,
};
delete mock.foo;
const update = updateGraphEventId({
id: TimelineId.active,
graphEventId: '',
timelineById: mock,
});
expect(update[TimelineId.active].graphEventId).toEqual('');
expect(update[TimelineId.active].activeTab).toEqual(TimelineTabs.eql);
expect(update[TimelineId.active].prevActiveTab).toEqual(TimelineTabs.graph);
});
});
});

View file

@ -526,6 +526,7 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
[id]: {
...state.timelineById[id],
activeTab,
prevActiveTab: state.timelineById[id].activeTab,
},
},
}))