Skip to content

Commit 8463e8a

Browse files
Improve updates of message table pagination. (#23013)
* Improve updates of message table pagination. * Adding changelog * Simplify state management by getting errors from store instead of response. * Remove not needed type. * Only show errors for related search type. --------- Co-authored-by: Maksym Yadlovskyi <[email protected]>
1 parent f6120e0 commit 8463e8a

File tree

7 files changed

+90
-74
lines changed

7 files changed

+90
-74
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type = "f"
2+
message = "Fixing issue with message table pagination on search page, where the active page was not set correctly when opening pages very fast."
3+
4+
pulls = ["23013"]
5+
issues = ["22321"]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
type = "f"
2+
message = "Fixing issue with message table pagination on search page, where active page was not reset correctly when executing new search."
3+
4+
pulls = ["23013"]
5+
issues = ["23002"]

graylog2-web-interface/src/components/common/PaginatedList.tsx

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Props = {
3434
pageSizes?: Array<number>;
3535
showPageSizeSelect?: boolean;
3636
totalItems: number;
37+
enforcePageBounds?: boolean;
3738
};
3839

3940
const ListBase = ({
@@ -48,6 +49,7 @@ const ListBase = ({
4849
setPagination,
4950
showPageSizeSelect,
5051
totalItems,
52+
enforcePageBounds,
5153
}: Required<Props> & {
5254
currentPageSize: number;
5355
currentPage: number;
@@ -75,8 +77,10 @@ const ListBase = ({
7577
);
7678

7779
useEffect(() => {
78-
if (numberPages > 0 && currentPage > numberPages) _onChangePage(numberPages);
79-
}, [currentPage, numberPages, _onChangePage]);
80+
if (enforcePageBounds && numberPages > 0 && currentPage > numberPages) {
81+
_onChangePage(numberPages);
82+
}
83+
}, [enforcePageBounds, currentPage, numberPages, _onChangePage]);
8084

8185
return (
8286
<>
@@ -95,6 +99,7 @@ const ListBase = ({
9599
<IfInteractive>
96100
<div className={`text-center pagination-wrapper ${className ?? ''}`}>
97101
<Pagination
102+
warnIfPageOutOfBounds={false}
98103
totalPages={numberPages}
99104
currentPage={currentPage}
100105
hidePreviousAndNextPageLinks={hidePreviousAndNextPageLinks}
@@ -171,38 +176,30 @@ const PaginatedList = ({
171176
showPageSizeSelect = true,
172177
totalItems,
173178
useQueryParameter = true,
179+
enforcePageBounds = true,
174180
}: Props & {
175181
activePage?: number;
176182
pageSize?: number;
177183
useQueryParameter?: boolean;
178184
}) => {
185+
const baseProps = {
186+
enforcePageBounds: enforcePageBounds,
187+
className: className,
188+
hideFirstAndLastPageLinks: hideFirstAndLastPageLinks,
189+
hidePreviousAndNextPageLinks: hidePreviousAndNextPageLinks,
190+
onChange: onChange,
191+
pageSizes: pageSizes,
192+
pageSize: pageSize,
193+
showPageSizeSelect: showPageSizeSelect,
194+
totalItems: totalItems,
195+
};
196+
179197
if (useQueryParameter) {
180-
return (
181-
<ListBasedOnQueryParams
182-
className={className}
183-
hideFirstAndLastPageLinks={hideFirstAndLastPageLinks}
184-
hidePreviousAndNextPageLinks={hidePreviousAndNextPageLinks}
185-
onChange={onChange}
186-
pageSizes={pageSizes}
187-
pageSize={pageSize}
188-
showPageSizeSelect={showPageSizeSelect}
189-
totalItems={totalItems}>
190-
{children}
191-
</ListBasedOnQueryParams>
192-
);
198+
return <ListBasedOnQueryParams {...baseProps}>{children}</ListBasedOnQueryParams>;
193199
}
194200

195201
return (
196-
<ListWithOwnState
197-
className={className}
198-
hideFirstAndLastPageLinks={hideFirstAndLastPageLinks}
199-
hidePreviousAndNextPageLinks={hidePreviousAndNextPageLinks}
200-
onChange={onChange}
201-
pageSizes={pageSizes}
202-
pageSize={pageSize}
203-
showPageSizeSelect={showPageSizeSelect}
204-
totalItems={totalItems}
205-
activePage={activePage}>
202+
<ListWithOwnState {...baseProps} activePage={activePage}>
206203
{children}
207204
</ListWithOwnState>
208205
);

graylog2-web-interface/src/components/common/Pagination.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Props = {
3232
hideFirstAndLastPageLinks?: boolean;
3333
disabled?: boolean;
3434
onChange?: (nextPage: number) => void;
35+
warnIfPageOutOfBounds?: boolean;
3536
};
3637

3738
const StyledBootstrapPagination = styled(BootstrapPagination)(
@@ -185,14 +186,17 @@ const Pagination = ({
185186
hideFirstAndLastPageLinks = false,
186187
disabled = false,
187188
onChange = () => {},
189+
warnIfPageOutOfBounds = true,
188190
}: Props) => {
189191
if (totalPages <= 1) {
190192
return null;
191193
}
192194

193195
if (currentPage > totalPages) {
194-
// eslint-disable-next-line no-console
195-
console.warn('Pagination: `currentPage` prop should not be larger than `totalPages` prop.');
196+
if (warnIfPageOutOfBounds) {
197+
// eslint-disable-next-line no-console
198+
console.warn('Pagination: `currentPage` prop should not be larger than `totalPages` prop.');
199+
}
196200

197201
return null;
198202
}

graylog2-web-interface/src/views/components/widgets/MessageList.test.tsx

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { render, screen, waitFor, within } from 'wrappedTestingLibrary';
1919
import * as Immutable from 'immutable';
2020
import userEvent from '@testing-library/user-event';
2121

22+
import useSearchResult from 'views/hooks/useSearchResult';
2223
import { StoreMock as MockStore } from 'helpers/mocking';
2324
import asMock from 'helpers/mocking/AsMock';
2425
import { TIMESTAMP_FIELD, Messages } from 'views/Constants';
@@ -33,7 +34,6 @@ import { finishedLoading } from 'views/logic/slices/searchExecutionSlice';
3334
import type { AbsoluteTimeRange } from 'views/logic/queries/Query';
3435
import SearchResult from 'views/logic/SearchResult';
3536
import reexecuteSearchTypes from 'views/components/widgets/reexecuteSearchTypes';
36-
import type { SearchErrorResponse } from 'views/logic/SearchError';
3737
import TestStoreProvider from 'views/test/TestStoreProvider';
3838
import useViewsPlugin from 'views/test/testViewsPlugin';
3939
import useAutoRefresh from 'views/hooks/useAutoRefresh';
@@ -55,6 +55,7 @@ jest.mock('stores/inputs/InputsStore', () => ({
5555
}));
5656

5757
jest.mock('views/hooks/useAutoRefresh');
58+
jest.mock('views/hooks/useSearchResult');
5859

5960
const searchTypeResults = {
6061
'search-type-id': {
@@ -70,7 +71,13 @@ const dummySearchJobResults = {
7071
id: 'foo',
7172
owner: 'me',
7273
search_id: 'bar',
73-
results: {},
74+
results: {
75+
'deadbeef': {
76+
query: { search_types: [{ id: 'search-type-id', limit: 10000 }] },
77+
execution_stats: {},
78+
errors: [],
79+
},
80+
},
7481
};
7582
jest.mock('views/hooks/useActiveQueryId');
7683
jest.mock('views/components/widgets/useCurrentSearchTypesResults');
@@ -235,28 +242,28 @@ describe('MessageList', () => {
235242
await waitFor(() => expect(stopAutoRefresh).toHaveBeenCalledTimes(1));
236243
});
237244

238-
it('displays error description, when using pagination throws an error', async () => {
239-
const dispatch = jest.fn().mockResolvedValue(
240-
finishedLoading({
241-
result: new SearchResult({
242-
...dummySearchJobResults,
243-
errors: [
244-
{
245-
description: 'Error description',
246-
} as SearchErrorResponse,
247-
],
248-
}),
245+
it('displays search result limit errors', async () => {
246+
asMock(useSearchResult).mockReturnValue({
247+
result: new SearchResult({
248+
...dummySearchJobResults,
249+
errors: [
250+
{
251+
query_id: 'deadbeef',
252+
search_type_id: 'search-type-id',
253+
description: 'Error description',
254+
backtrace: undefined,
255+
type: 'result_window_limit',
256+
result_window_limit: 10000,
257+
},
258+
],
249259
}),
250-
);
251-
asMock(useViewsDispatch).mockReturnValue(dispatch);
252-
253-
const secondPageSize = 10;
254-
255-
render(<SimpleMessageList data={{ ...data, total: Messages.DEFAULT_LIMIT + secondPageSize }} />);
260+
});
256261

257-
clickNextPageButton();
262+
render(<SimpleMessageList />);
258263

259-
await screen.findByText('Error description');
264+
await screen.findByText(
265+
'Elasticsearch limits the search result to 10000 messages. With a page size of 10000 messages, you can use the first 1 pages. Error description',
266+
);
260267
});
261268

262269
it('calls render completion callback after first render', async () => {

graylog2-web-interface/src/views/components/widgets/MessageList.tsx

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import useViewsDispatch from 'views/stores/useViewsDispatch';
3535
import reexecuteSearchTypes from 'views/components/widgets/reexecuteSearchTypes';
3636
import useOnSearchExecution from 'views/hooks/useOnSearchExecution';
3737
import useAutoRefresh from 'views/hooks/useAutoRefresh';
38+
import useSearchResult from 'views/hooks/useSearchResult';
3839

3940
import RenderCompletionCallback from './RenderCompletionCallback';
4041

@@ -49,11 +50,6 @@ const Wrapper = styled.div`
4950
}
5051
`;
5152

52-
type Pagination = {
53-
pageErrors: Array<{ description: string }>;
54-
currentPage: number;
55-
};
56-
5753
export type MessageListResult = {
5854
messages: Array<BackendMessage>;
5955
total: number;
@@ -65,12 +61,12 @@ type Props = WidgetComponentProps<MessagesWidgetConfig, MessageListResult> & {
6561
pageSize?: number;
6662
};
6763

68-
const useResetPaginationOnSearchExecution = (setPagination: (pagination: Pagination) => void, currentPage) => {
64+
const useResetPaginationOnSearchExecution = (setCurrentPage: (pageNr: number) => void, currentPage) => {
6965
const resetPagination = useCallback(() => {
7066
if (currentPage !== 1) {
71-
setPagination({ currentPage: 1, pageErrors: [] });
67+
setCurrentPage(1);
7268
}
73-
}, [currentPage, setPagination]);
69+
}, [currentPage, setCurrentPage]);
7470
useOnSearchExecution(resetPagination);
7571
};
7672

@@ -94,6 +90,13 @@ const useRenderCompletionCallback = () => {
9490
}, [renderCompletionCallback]);
9591
};
9692

93+
const usePageErrors = (searchTypeId: string) => {
94+
const searchResult = useSearchResult();
95+
const errors = searchResult?.result?.errors ?? [];
96+
97+
return errors.filter((error) => error.searchTypeId === searchTypeId);
98+
};
99+
97100
const MessageList = ({
98101
config,
99102
data: { id: searchTypeId, messages, total: totalMessages },
@@ -102,20 +105,19 @@ const MessageList = ({
102105
pageSize = Messages.DEFAULT_LIMIT,
103106
setLoadingState,
104107
}: Props) => {
105-
const [{ currentPage, pageErrors }, setPagination] = useState<Pagination>({
106-
pageErrors: [],
107-
currentPage: 1,
108-
});
108+
const [currentPage, setCurrentPage] = useState<number>(1);
109109
const { stopAutoRefresh } = useAutoRefresh();
110+
111+
const pageErrors = usePageErrors(searchTypeId);
110112
const activeQueryId = useActiveQueryId();
111113
const searchTypes = useCurrentSearchTypesResults();
112114
const scrollContainerRef = useResetScrollPositionOnPageChange(currentPage);
113115
const dispatch = useViewsDispatch();
114-
useResetPaginationOnSearchExecution(setPagination, currentPage);
116+
useResetPaginationOnSearchExecution(setCurrentPage, currentPage);
115117
useRenderCompletionCallback();
116118

117119
const handlePageChange = useCallback(
118-
(pageNo: number) => {
120+
(newCurrentPage: number) => {
119121
// execute search with new offset
120122
const { effectiveTimerange } = searchTypes[searchTypeId] as MessageResult;
121123
const searchTypePayload: SearchTypeOptions<{
@@ -124,21 +126,16 @@ const MessageList = ({
124126
}> = {
125127
[searchTypeId]: {
126128
limit: pageSize,
127-
offset: pageSize * (pageNo - 1),
129+
offset: pageSize * (newCurrentPage - 1),
128130
},
129131
};
130132

131133
stopAutoRefresh();
132134
setLoadingState(true);
135+
setCurrentPage(newCurrentPage);
133136

134-
dispatch(reexecuteSearchTypes(searchTypePayload, effectiveTimerange)).then((response) => {
135-
const { result } = response.payload;
137+
dispatch(reexecuteSearchTypes(searchTypePayload, effectiveTimerange)).then(() => {
136138
setLoadingState(false);
137-
138-
setPagination({
139-
pageErrors: result.errors,
140-
currentPage: pageNo,
141-
});
142139
});
143140
},
144141
[dispatch, pageSize, searchTypeId, searchTypes, setLoadingState, stopAutoRefresh],
@@ -162,8 +159,11 @@ const MessageList = ({
162159
showPageSizeSelect={false}
163160
totalItems={totalMessages}
164161
pageSize={pageSize}
162+
enforcePageBounds={false}
165163
useQueryParameter={false}>
166-
{!pageErrors?.length ? (
164+
{pageErrors.length > 0 ? (
165+
<ErrorWidget errors={pageErrors} />
166+
) : (
167167
<MessageTable
168168
activeQueryId={activeQueryId}
169169
config={config}
@@ -173,8 +173,6 @@ const MessageList = ({
173173
setLoadingState={setLoadingState}
174174
messages={messages}
175175
/>
176-
) : (
177-
<ErrorWidget errors={pageErrors} />
178176
)}
179177
</PaginatedList>
180178
</Wrapper>

graylog2-web-interface/src/views/logic/SearchResult.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export type SearchJobResult = {
4343
owner: string;
4444
results: { [id: string]: any };
4545
search_id: SearchId;
46-
errors: Array<SearchErrorResponse>;
46+
errors: Array<SearchErrorResponse | ResultWindowLimitErrorResponse>;
4747
};
4848

4949
class SearchResult {

0 commit comments

Comments
 (0)