Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] Fix v1 pagination #3181

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
TopicMessageEvent,
TopicMessageEventTypeEnum,
} from 'generated-sources';
import React, { useContext } from 'react';
import React, { useContext, useRef } from 'react';
import omitBy from 'lodash/omitBy';
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
import MultiSelect from 'components/common/MultiSelect/MultiSelect.styled';
Expand All @@ -38,6 +38,8 @@ import { useTopicDetails } from 'lib/hooks/api/topics';
import { InputLabel } from 'components/common/Input/InputLabel.styled';
import { getSerdeOptions } from 'components/Topics/Topic/SendMessage/utils';
import { useSerdes } from 'lib/hooks/api/topicMessages';
import { useAppSelector } from 'lib/hooks/redux';
import { getTopicMessges } from 'redux/reducers/topicMessages/selectors';

import * as S from './Filters.styled';
import {
Expand Down Expand Up @@ -78,6 +80,39 @@ export const SeekTypeOptions = [
{ value: SeekType.TIMESTAMP, label: 'Timestamp' },
];

const getNextSeekTo = (
messages: TopicMessage[],
searchParams: URLSearchParams,
defaultCalculatedSeekTo: string
) => {
const seekTo = searchParams.get('seekTo') || defaultCalculatedSeekTo;

// parse current seekTo query param to array of [partition, offset] tuples
const configTuple = seekTo?.split(',').map((item) => {
const [partition, offset] = item.split('::');
return { partition: Number(partition), offset: Number(offset) };
});

// Reverse messages array for faster last displayed message search.
const reversedMessages = [...messages].reverse();

if (!configTuple) return '';

const newConfigTuple = configTuple.map(({ partition, offset }) => {
const message = reversedMessages.find((m) => partition === m.partition);
if (!message) {
return { partition, offset };
}

return {
partition,
offset: message.offset,
};
});

return newConfigTuple.map((t) => `${t.partition}::${t.offset}`).join(',');
};

const Filters: React.FC<FiltersProps> = ({
phaseMessage,
meta: { elapsedMs, bytesConsumed, messagesConsumed },
Expand All @@ -93,14 +128,22 @@ const Filters: React.FC<FiltersProps> = ({
const navigate = useNavigate();
const [searchParams] = useSearchParams();

const page = searchParams.get('page');

const { data: topic } = useTopicDetails({ clusterName, topicName });

const partitions = topic?.partitions || [];

const { seekDirection, isLive, changeSeekDirection } =
useContext(TopicMessagesContext);
const {
seekDirection,
isLive,
changeSeekDirection,
page,
setPageNumber,
paginated,
} = useContext(TopicMessagesContext);

const paginationCache = useRef(page);

const messages = useAppSelector(getTopicMessges);

const { value: isOpen, toggle } = useBoolean();

Expand Down Expand Up @@ -156,15 +199,18 @@ const Filters: React.FC<FiltersProps> = ({
[selectedPartitions]
);

const isFiltersDisabled = isTailing || paginated;

const isSubmitDisabled = React.useMemo(() => {
if (isSeekTypeControlVisible) {
return (
(currentSeekType === SeekType.TIMESTAMP && !timestamp) || isTailing
(currentSeekType === SeekType.TIMESTAMP && !timestamp) ||
isFiltersDisabled
);
}

return false;
}, [isSeekTypeControlVisible, currentSeekType, timestamp, isTailing]);
}, [isSeekTypeControlVisible, currentSeekType, timestamp, isFiltersDisabled]);

const partitionMap = React.useMemo(
() =>
Expand All @@ -183,6 +229,8 @@ const Filters: React.FC<FiltersProps> = ({
setOffset('');
setQuery('');
changeSeekDirection(SeekDirection.FORWARD);
setPageNumber(0);
paginationCache.current = 0;
getSelectedPartitionsFromSeekToParam(searchParams, partitions);
setSelectedPartitions(
partitions.map((partition: Partition) => {
Expand All @@ -195,6 +243,14 @@ const Filters: React.FC<FiltersProps> = ({
};

const handleFiltersSubmit = (currentOffset: string) => {
// so it will not work during back navigations

if (paginationCache.current > page) {
// update for the next
paginationCache.current = page;
return;
}

const nextAttempt = Number(searchParams.get('attempt') || 0) + 1;
const props: Query = {
q:
Expand Down Expand Up @@ -231,24 +287,41 @@ const Filters: React.FC<FiltersProps> = ({
props.seekType = SeekType.TIMESTAMP;
}

props.seekTo = selectedPartitions.map(({ value }) => {
const defaultPartitionedSeekTo = selectedPartitions.map(({ value }) => {
const offsetProperty =
seekDirection === SeekDirection.FORWARD ? 'offsetMin' : 'offsetMax';

const offsetBasedSeekTo =
currentOffset || partitionMap[value][offsetProperty];

const seekToOffset =
currentSeekType === SeekType.OFFSET
? offsetBasedSeekTo
: timestamp?.getTime();

return `${value}::${seekToOffset || '0'}`;
});

if (page && page !== 0) {
// during pagination, it should always be offset
props.seekType = SeekType.OFFSET;
props.seekTo = getNextSeekTo(
messages,
searchParams,
defaultPartitionedSeekTo.join(',')
);
} else {
props.seekTo = defaultPartitionedSeekTo;
}
}

const newProps = omitBy(props, (v) => v === undefined || v === '');
const qs = Object.keys(newProps)
.map((key) => `${key}=${encodeURIComponent(newProps[key] as string)}`)
.join('&');

paginationCache.current = Number(page);

navigate({
search: `?${qs}`,
});
Expand Down Expand Up @@ -314,6 +387,7 @@ const Filters: React.FC<FiltersProps> = ({
localStorage.setItem('savedFilters', JSON.stringify(filters));
setSavedFilters(filters);
};

// eslint-disable-next-line consistent-return
React.useEffect(() => {
if (location.search?.length !== 0) {
Expand Down Expand Up @@ -364,27 +438,14 @@ const Filters: React.FC<FiltersProps> = ({
}, [
clusterName,
topicName,
seekDirection,
location,
addMessage,
resetMessages,
setIsFetching,
updateMeta,
updatePhase,
]);
React.useEffect(() => {
if (location.search?.length === 0) {
handleFiltersSubmit(offset);
}
}, [
seekDirection,
queryType,
activeFilter,
currentSeekType,
timestamp,
query,
location,
]);

React.useEffect(() => {
handleFiltersSubmit(offset);
}, [
Expand Down Expand Up @@ -422,7 +483,7 @@ const Filters: React.FC<FiltersProps> = ({
selectSize="M"
minWidth="100px"
options={SeekTypeOptions}
disabled={isTailing}
disabled={isFiltersDisabled}
/>

{currentSeekType === SeekType.OFFSET ? (
Expand All @@ -433,7 +494,7 @@ const Filters: React.FC<FiltersProps> = ({
value={offset}
placeholder="Offset"
onChange={({ target: { value } }) => setOffset(value)}
disabled={isTailing}
disabled={isFiltersDisabled}
/>
) : (
<S.DatePickerInput
Expand All @@ -443,7 +504,7 @@ const Filters: React.FC<FiltersProps> = ({
timeInputLabel="Time:"
dateFormat="MMM d, yyyy HH:mm"
placeholderText="Select timestamp"
disabled={isTailing}
disabled={isFiltersDisabled}
/>
)}
</S.SeekTypeSelectorWrapper>
Expand All @@ -459,7 +520,7 @@ const Filters: React.FC<FiltersProps> = ({
value={selectedPartitions}
onChange={setSelectedPartitions}
labelledBy="Select partitions"
disabled={isTailing}
disabled={isFiltersDisabled}
/>
</div>
<div>
Expand All @@ -472,7 +533,7 @@ const Filters: React.FC<FiltersProps> = ({
options={getSerdeOptions(serdes.key || [])}
value={searchParams.get('keySerde') as string}
selectSize="M"
disabled={isTailing}
disabled={isFiltersDisabled}
/>
</div>
<div>
Expand All @@ -485,7 +546,7 @@ const Filters: React.FC<FiltersProps> = ({
value={searchParams.get('valueSerde') as string}
minWidth="170px"
selectSize="M"
disabled={isTailing}
disabled={isFiltersDisabled}
/>
</div>
<S.ClearAll onClick={handleClearAllFilters}>Clear all</S.ClearAll>
Expand All @@ -509,12 +570,18 @@ const Filters: React.FC<FiltersProps> = ({
minWidth="120px"
options={SeekDirectionOptions}
isLive={isLive}
disabled={paginated}
/>
</div>
<S.ActiveSmartFilterWrapper>
<Search placeholder="Search" disabled={isTailing} />

<Button buttonType="primary" buttonSize="M" onClick={toggle}>
<Search placeholder="Search" disabled={isFiltersDisabled} />

<Button
buttonType="primary"
buttonSize="M"
onClick={toggle}
disabled={paginated}
>
<PlusIcon />
Add Filters
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const defaultContextValue: ContextProps = {
isLive: false,
seekDirection: SeekDirection.FORWARD,
changeSeekDirection: jest.fn(),
page: 0,
setPageNumber: jest.fn(),
paginated: false,
};

jest.mock('components/common/Icons/CloseIcon', () => () => 'mock-CloseIcon');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ const Messages: React.FC = () => {
SeekDirectionOptionsObj[seekDirection].isLive
);

const [page, setPage] = useState<number>(
Number(searchParams.get('page') || '0')
);

const changeSeekDirection = useCallback((val: string) => {
switch (val) {
case SeekDirection.FORWARD:
Expand All @@ -88,8 +92,11 @@ const Messages: React.FC = () => {
seekDirection,
changeSeekDirection,
isLive,
page,
paginated: !!page,
setPageNumber: setPage,
}),
[seekDirection, changeSeekDirection]
[seekDirection, page, changeSeekDirection]
);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { Table } from 'components/common/table/Table/Table.styled';
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
import { TopicMessage } from 'generated-sources';
import React, { useContext, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
getTopicMessges,
getIsTopicMessagesFetching,
} from 'redux/reducers/topicMessages/selectors';
import TopicMessagesContext from 'components/contexts/TopicMessagesContext';
import { useAppSelector } from 'lib/hooks/redux';
import { Button } from 'components/common/Button/Button';
import { useSearchParams } from 'react-router-dom';
import { MESSAGES_PER_PAGE } from 'lib/constants';
import * as S from 'components/common/NewTable/Table.styled';

Expand All @@ -19,13 +19,12 @@ import Message, { PreviewFilter } from './Message';

const MessagesTable: React.FC = () => {
const [previewFor, setPreviewFor] = useState<string | null>(null);
const navigate = useNavigate();

const [keyFilters, setKeyFilters] = useState<PreviewFilter[]>([]);
const [contentFilters, setContentFilters] = useState<PreviewFilter[]>([]);

const [searchParams, setSearchParams] = useSearchParams();
const page = searchParams.get('page');
const { isLive } = useContext(TopicMessagesContext);
const { isLive, setPageNumber, page } = useContext(TopicMessagesContext);

const messages = useAppSelector(getTopicMessges);
const isFetching = useAppSelector(getIsTopicMessagesFetching);
Expand All @@ -38,17 +37,12 @@ const MessagesTable: React.FC = () => {

const isNextPageButtonDisabled =
isPaginationDisabled || messages.length < Number(MESSAGES_PER_PAGE);
const isPrevPageButtonDisabled =
isPaginationDisabled || !Number(searchParams.get('page'));

const handleNextPage = () => {
searchParams.set('page', String(Number(page || 0) + 1));
setSearchParams(searchParams);
};
const isPrevPageButtonDisabled = isPaginationDisabled || !page;

const handlePrevPage = () => {
searchParams.set('page', String(Number(page || 0) - 1));
setSearchParams(searchParams);
const handlePrevClick = () => {
setPageNumber((prevState) => prevState - 1);
navigate(-1);
};

return (
Expand Down Expand Up @@ -125,15 +119,15 @@ const MessagesTable: React.FC = () => {
buttonType="secondary"
buttonSize="L"
disabled={isPrevPageButtonDisabled}
onClick={handlePrevPage}
onClick={handlePrevClick}
>
← Back
</Button>
<Button
buttonType="secondary"
buttonSize="L"
disabled={isNextPageButtonDisabled}
onClick={handleNextPage}
onClick={() => setPageNumber((prev) => prev + 1)}
>
Next →
</Button>
Expand Down
Loading