mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[ Security Solution ] Fix Refetch logic with new timeline batching (#205893)
## Summary PR : https://github.com/elastic/kibana/pull/204034 fixed some issues with timeline batching. It was not able to fix one of the issue with `Refetch` logic which exists in `main` ( resulting in a flaky test ) and causing some tests to fail in `8.16`, `8.17` and `8.x`. ## Issue Description There are 2 issues with below video: 1. When user updates a status of an alert, the `Refetch` only happens on the first `batch`. This behaviour is flaky currently. Even if the user is on nth batch, table will fetch 0th batch and reset the user's page back to 1. https://github.com/user-attachments/assets/eaf88a82-0e9b-4743-8b2d-60fd327a2443 3. When user clicks `Refresh` manually, then also only first (0th) `batch` is fetched, which should have rather fetched all the present batches. https://github.com/user-attachments/assets/8d578ce3-4f24-4e70-bc3a-ed6ba99167a0 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
ab7aae4c49
commit
54b4fac705
2 changed files with 160 additions and 38 deletions
|
@ -434,6 +434,8 @@ describe('useTimelineEventsHandler', () => {
|
|||
);
|
||||
});
|
||||
|
||||
expect(mockSearch).toHaveBeenCalledTimes(1);
|
||||
|
||||
mockSearch.mockClear();
|
||||
|
||||
await loadNextBatch(result);
|
||||
|
@ -487,39 +489,142 @@ describe('useTimelineEventsHandler', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('should reset batch to 0th when the data is `refetched`', async () => {
|
||||
const { result } = renderHook((args) => useTimelineEvents(args), {
|
||||
initialProps: { ...props },
|
||||
describe('refetching', () => {
|
||||
/*
|
||||
* Below are some use cases where refetch is triggered :
|
||||
*
|
||||
* - When user triggers a manual refresh of the data
|
||||
* - When user updates an event, which triggers a refresh of the data
|
||||
* - For example, when alert status is updated.
|
||||
* - When user adds a new column
|
||||
*
|
||||
*/
|
||||
|
||||
test('should fetch first batch again when refetch is triggered', async () => {
|
||||
const { result } = renderHook((args) => useTimelineEvents(args), {
|
||||
initialProps: { ...props, timerangeKind: 'absolute' } as UseTimelineEventsProps,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
|
||||
mockSearch.mockClear();
|
||||
|
||||
act(() => {
|
||||
result.current[1].refetch();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
test('should fetch first batch again when refetch is triggered with relative timerange', async () => {
|
||||
const { result } = renderHook((args) => useTimelineEvents(args), {
|
||||
initialProps: { ...props, timerangeKind: 'relative' } as UseTimelineEventsProps,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
|
||||
mockSearch.mockClear();
|
||||
|
||||
act(() => {
|
||||
result.current[1].refetch();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledTimes(1);
|
||||
expect(mockSearch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
mockSearch.mockClear();
|
||||
test('should fetch first batch again when refetch is triggered when user has already fetched multiple batches', async () => {
|
||||
const { result } = renderHook((args) => useTimelineEvents(args), {
|
||||
initialProps: { ...props },
|
||||
});
|
||||
|
||||
await loadNextBatch(result);
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 1, querySize: 25 } })
|
||||
);
|
||||
mockSearch.mockClear();
|
||||
|
||||
await loadNextBatch(result);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 1, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
|
||||
mockSearch.mockClear();
|
||||
|
||||
act(() => {
|
||||
result.current[1].refetch();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
mockSearch.mockClear();
|
||||
describe('sort', () => {
|
||||
test('should fetch first batch again when sort is updated', async () => {
|
||||
const { result, rerender } = renderHook((args) => useTimelineEvents(args), {
|
||||
initialProps: { ...props } as UseTimelineEventsProps,
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current[1].refetch();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledTimes(1);
|
||||
expect(mockSearch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
act(() => {
|
||||
result.current[1].loadNextBatch();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current[0]).toBe(DataLoadingState.loaded);
|
||||
expect(mockSearch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
|
||||
mockSearch.mockClear();
|
||||
|
||||
act(() => {
|
||||
rerender({
|
||||
...props,
|
||||
sort: [...initSortDefault, { ...initSortDefault[0], field: 'event.kind' }],
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ pagination: { activePage: 0, querySize: 25 } })
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -636,7 +741,7 @@ describe('useTimelineEventsHandler', () => {
|
|||
//////////////////////
|
||||
});
|
||||
|
||||
test('should request 0th batch (refetch) when batchSize is changed', async () => {
|
||||
test('should request 0th batch when batchSize is changed', async () => {
|
||||
const { result, rerender } = renderHook((args) => useTimelineEvents(args), {
|
||||
initialProps: { ...props, limit: 5 },
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { isEmpty, noop } from 'lodash/fp';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
@ -163,7 +163,6 @@ export const useTimelineEventsHandler = ({
|
|||
const [{ pageName }] = useRouteSpy();
|
||||
const dispatch = useDispatch();
|
||||
const { data } = useKibana().services;
|
||||
const refetch = useRef<inputsModel.Refetch>(noop);
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const searchSubscription$ = useRef(new Subscription());
|
||||
const [loading, setLoading] = useState<DataLoadingState>(DataLoadingState.loaded);
|
||||
|
@ -215,13 +214,6 @@ export const useTimelineEventsHandler = ({
|
|||
};
|
||||
}, []);
|
||||
|
||||
const refetchGrid = useCallback(() => {
|
||||
if (refetch.current != null) {
|
||||
refetch.current();
|
||||
}
|
||||
loadBatchHandler(0);
|
||||
}, [loadBatchHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
// when batch size changes, refetch DataGrid
|
||||
setActiveBatch(0);
|
||||
|
@ -233,7 +225,7 @@ export const useTimelineEventsHandler = ({
|
|||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
refetch: refetchGrid,
|
||||
refetch: () => {},
|
||||
totalCount: -1,
|
||||
pageInfo: {
|
||||
activePage: 0,
|
||||
|
@ -353,6 +345,31 @@ export const useTimelineEventsHandler = ({
|
|||
[pageName, skip, id, activeBatch, startTracking, data.search, dataViewId]
|
||||
);
|
||||
|
||||
const refetchGrid = useCallback(() => {
|
||||
/*
|
||||
*
|
||||
* Trigger search with a new request object to fetch the latest data.
|
||||
*
|
||||
*/
|
||||
const newTimelineRequest: typeof timelineRequest = {
|
||||
...timelineRequest,
|
||||
factoryQueryType: TimelineEventsQueries.all,
|
||||
language,
|
||||
sort,
|
||||
fieldRequested: timelineRequest?.fieldRequested ?? fields,
|
||||
fields: timelineRequest?.fieldRequested ?? fields,
|
||||
pagination: {
|
||||
activePage: 0,
|
||||
querySize: limit,
|
||||
},
|
||||
};
|
||||
|
||||
setTimelineRequest(newTimelineRequest);
|
||||
|
||||
timelineSearch(newTimelineRequest);
|
||||
setActiveBatch(0);
|
||||
}, [timelineRequest, timelineSearch, limit, language, sort, fields]);
|
||||
|
||||
useEffect(() => {
|
||||
if (indexNames.length === 0) {
|
||||
return;
|
||||
|
@ -411,7 +428,7 @@ export const useTimelineEventsHandler = ({
|
|||
* For example, newly requested fields
|
||||
*
|
||||
* */
|
||||
activePage: activeBatch,
|
||||
activePage: newActiveBatch,
|
||||
querySize: limit,
|
||||
};
|
||||
|
||||
|
@ -475,7 +492,7 @@ export const useTimelineEventsHandler = ({
|
|||
dsl: [],
|
||||
response: [],
|
||||
},
|
||||
refetch: refetchGrid,
|
||||
refetch: () => {},
|
||||
totalCount: -1,
|
||||
pageInfo: {
|
||||
activePage: 0,
|
||||
|
@ -486,7 +503,7 @@ export const useTimelineEventsHandler = ({
|
|||
refreshedAt: 0,
|
||||
});
|
||||
}
|
||||
}, [filterQuery, id, refetchGrid, loadNextBatch]);
|
||||
}, [filterQuery, id, loadNextBatch]);
|
||||
|
||||
const timelineSearchHandler = useCallback(
|
||||
async (onNextHandler?: OnNextResponseHandler) => {
|
||||
|
@ -560,7 +577,7 @@ export const useTimelineEvents = ({
|
|||
|
||||
setEventsPerPage((prev) => {
|
||||
let result = [...prev];
|
||||
if (querySize === limit) {
|
||||
if (querySize === limit && activePage > 0) {
|
||||
result[activePage] = timelineResponse.events;
|
||||
} else {
|
||||
result = [timelineResponse.events];
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue