diff --git a/.changelog/1489.trivial.md b/.changelog/1489.trivial.md new file mode 100644 index 000000000..3291e21b7 --- /dev/null +++ b/.changelog/1489.trivial.md @@ -0,0 +1 @@ +Enable pagination in Events lists diff --git a/playwright/tests/accounts.spec.ts b/playwright/tests/accounts.spec.ts index 5373f6771..da156b3cc 100644 --- a/playwright/tests/accounts.spec.ts +++ b/playwright/tests/accounts.spec.ts @@ -46,68 +46,71 @@ async function setup(page: Page) { } satisfies Partial), }) }) - await page.route('**/v1/sapphire/events?rel=oasis1qq2v39p9fqk997vk6742axrzqyu9v2ncyuqt8uek', route => { - route.fulfill({ - body: JSON.stringify({ - is_total_count_clipped: false, - total_count: 3, - events: [ - { - body: { - amount: { - Amount: '100000000000000000000', - Denomination: '', + await page.route( + '**/v1/sapphire/events?limit=10&offset=0&rel=oasis1qq2v39p9fqk997vk6742axrzqyu9v2ncyuqt8uek', + route => { + route.fulfill({ + body: JSON.stringify({ + is_total_count_clipped: false, + total_count: 3, + events: [ + { + body: { + amount: { + Amount: '100000000000000000000', + Denomination: '', + }, + from: 'oasis1qq235lqj77855qcemcr5w2qm372s4amqcc4v3ztc', + nonce: 29, + to: 'oasis1qrwncs459lauc77zw23efdn9dmfcp23cxv095l5z', }, - from: 'oasis1qq235lqj77855qcemcr5w2qm372s4amqcc4v3ztc', - nonce: 29, - to: 'oasis1qrwncs459lauc77zw23efdn9dmfcp23cxv095l5z', + evm_log_name: '', + round: 3038913, + timestamp: '2023-10-16T13:13:47Z', + tx_hash: null, + type: 'consensus_accounts.delegate', + layer: 'sapphire', + network: 'testnet', }, - evm_log_name: '', - round: 3038913, - timestamp: '2023-10-16T13:13:47Z', - tx_hash: null, - type: 'consensus_accounts.delegate', - layer: 'sapphire', - network: 'testnet', - }, - { - body: { - debond_end_time: 30013, - from: 'oasis1qrwncs459lauc77zw23efdn9dmfcp23cxv095l5z', - nonce: 30, - shares: '100', - to: 'oasis1qq235lqj77855qcemcr5w2qm372s4amqcc4v3ztc', + { + body: { + debond_end_time: 30013, + from: 'oasis1qrwncs459lauc77zw23efdn9dmfcp23cxv095l5z', + nonce: 30, + shares: '100', + to: 'oasis1qq235lqj77855qcemcr5w2qm372s4amqcc4v3ztc', + }, + evm_log_name: '', + round: 3038944, + timestamp: '2023-10-16T13:18:23Z', + tx_hash: null, + type: 'consensus_accounts.undelegate_start', + layer: 'sapphire', + network: 'testnet', }, - evm_log_name: '', - round: 3038944, - timestamp: '2023-10-16T13:18:23Z', - tx_hash: null, - type: 'consensus_accounts.undelegate_start', - layer: 'sapphire', - network: 'testnet', - }, - { - body: { - amount: { - Amount: '100000281888000000000', - Denomination: '', + { + body: { + amount: { + Amount: '100000281888000000000', + Denomination: '', + }, + from: 'oasis1qrwncs459lauc77zw23efdn9dmfcp23cxv095l5z', + shares: '100000281888', + to: 'oasis1qq235lqj77855qcemcr5w2qm372s4amqcc4v3ztc', }, - from: 'oasis1qrwncs459lauc77zw23efdn9dmfcp23cxv095l5z', - shares: '100000281888', - to: 'oasis1qq235lqj77855qcemcr5w2qm372s4amqcc4v3ztc', + evm_log_name: '', + round: 3216917, + timestamp: '2023-10-29T09:06:05Z', + tx_hash: null, + type: 'consensus_accounts.undelegate_done', + layer: 'sapphire', + network: 'testnet', }, - evm_log_name: '', - round: 3216917, - timestamp: '2023-10-29T09:06:05Z', - tx_hash: null, - type: 'consensus_accounts.undelegate_done', - layer: 'sapphire', - network: 'testnet', - }, - ], - } satisfies Partial), - }) - }) + ], + } satisfies Partial), + }) + }, + ) await page.goto( 'http://localhost:1234/mainnet/sapphire/address/0x0000000000000000000000000000000000000000/events', diff --git a/src/app/components/RuntimeEvents/RuntimeEventsDetailedList.tsx b/src/app/components/RuntimeEvents/RuntimeEventsDetailedList.tsx index 80535f561..c98343589 100644 --- a/src/app/components/RuntimeEvents/RuntimeEventsDetailedList.tsx +++ b/src/app/components/RuntimeEvents/RuntimeEventsDetailedList.tsx @@ -1,11 +1,13 @@ import { FC } from 'react' import { SearchScope } from '../../../types/searchScope' import { RuntimeEvent } from '../../../oasis-nexus/api' +import { TablePagination, TablePaginationProps } from '../Table/TablePagination' import { AddressSwitchOption } from '../AddressSwitch' import { useTranslation } from 'react-i18next' import { CardEmptyState } from '../CardEmptyState' import { TextSkeleton } from '../Skeleton' import { RuntimeEventDetails } from './RuntimeEventDetails' +import Box from '@mui/material/Box' import Divider from '@mui/material/Divider' const RuntimeEventDetailsWithSeparator: FC<{ @@ -28,7 +30,8 @@ export const RuntimeEventsDetailedList: FC<{ isLoading: boolean isError: boolean addressSwitchOption: AddressSwitchOption -}> = ({ scope, events, isLoading, isError, addressSwitchOption }) => { + pagination: false | TablePaginationProps +}> = ({ scope, events, isLoading, isError, addressSwitchOption, pagination }) => { const { t } = useTranslation() return ( <> @@ -44,6 +47,11 @@ export const RuntimeEventsDetailedList: FC<{ addressSwitchOption={addressSwitchOption} /> ))} + {pagination && ( + + + + )} ) } diff --git a/src/app/components/Transactions/TransactionEvents.tsx b/src/app/components/Transactions/TransactionEvents.tsx index 2bee495c9..9c95a2900 100644 --- a/src/app/components/Transactions/TransactionEvents.tsx +++ b/src/app/components/Transactions/TransactionEvents.tsx @@ -1,5 +1,7 @@ import { FC } from 'react' import { Layer, RuntimeTransaction, useGetRuntimeEvents } from '../../../oasis-nexus/api' +import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE as limit } from '../../config' +import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination' import { AppErrors } from '../../../types/errors' import { AddressSwitchOption } from '../AddressSwitch' import { RuntimeEventsDetailedList } from '../RuntimeEvents/RuntimeEventsDetailedList' @@ -9,21 +11,36 @@ export const TransactionEvents: FC<{ addressSwitchOption: AddressSwitchOption }> = ({ transaction, addressSwitchOption }) => { const { network, layer } = transaction + const pagination = useSearchParamsPagination('page') + const offset = (pagination.selectedPage - 1) * limit if (layer === Layer.consensus) { throw AppErrors.UnsupportedLayer } const eventsQuery = useGetRuntimeEvents(network, layer, { tx_hash: transaction.hash, - limit: 100, // We want to avoid pagination here, if possible + limit, + offset, }) - const { isLoading, data, isError } = eventsQuery + const { isFetched, isLoading, data, isError } = eventsQuery + const events = data?.data.events + if (isFetched && pagination.selectedPage > 1 && !events?.length) { + throw AppErrors.PageDoesNotExist + } + return ( ) } diff --git a/src/app/pages/RuntimeAccountDetailsPage/AccountEventsCard.tsx b/src/app/pages/RuntimeAccountDetailsPage/AccountEventsCard.tsx index ccab609eb..fed5c283b 100644 --- a/src/app/pages/RuntimeAccountDetailsPage/AccountEventsCard.tsx +++ b/src/app/pages/RuntimeAccountDetailsPage/AccountEventsCard.tsx @@ -3,6 +3,7 @@ import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import { useTranslation } from 'react-i18next' +import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE as limit } from '../../config' import { RuntimeEventsDetailedList } from '../../components/RuntimeEvents/RuntimeEventsDetailedList' import { AddressSwitchOption } from '../../components/AddressSwitch' import { ErrorBoundary } from '../../components/ErrorBoundary' @@ -14,7 +15,10 @@ export const eventsContainerId = 'events' export const AccountEventsCard: FC = ({ scope, address }) => { const { t } = useTranslation() - const { isLoading, isError, events } = useAccountEvents(scope, address) + const { isLoading, isError, events, pagination, totalCount, isTotalCountClipped } = useAccountEvents( + scope, + address, + ) return ( @@ -29,6 +33,13 @@ export const AccountEventsCard: FC = ({ scope, add isLoading={isLoading} isError={isError} addressSwitchOption={AddressSwitchOption.ETH} // TODO + pagination={{ + selectedPage: pagination.selectedPage, + linkToPage: pagination.linkToPage, + totalCount, + isTotalCountClipped, + rowsPerPage: limit, + }} /> diff --git a/src/app/pages/RuntimeAccountDetailsPage/hook.ts b/src/app/pages/RuntimeAccountDetailsPage/hook.ts index 50b0c60be..0dd6694d8 100644 --- a/src/app/pages/RuntimeAccountDetailsPage/hook.ts +++ b/src/app/pages/RuntimeAccountDetailsPage/hook.ts @@ -6,7 +6,7 @@ import { } from '../../../oasis-nexus/api' import { AppErrors } from '../../../types/errors' import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination' -import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE } from '../../config' +import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE as limit } from '../../config' import { SearchScope } from '../../../types/searchScope' import { getOasisAddressOrNull } from '../../utils/helpers' @@ -27,7 +27,7 @@ export const useAccount = (scope: SearchScope, address: string) => { export const useAccountTransactions = (scope: SearchScope, address: string) => { const { network, layer } = scope const pagination = useSearchParamsPagination('page') - const offset = (pagination.selectedPage - 1) * NUMBER_OF_ITEMS_ON_SEPARATE_PAGE + const offset = (pagination.selectedPage - 1) * limit if (layer === Layer.consensus) { throw AppErrors.UnsupportedLayer // Loading transactions on the consensus layer is not supported yet. @@ -39,7 +39,7 @@ export const useAccountTransactions = (scope: SearchScope, address: string) => { network, layer, // This is OK since consensus has been handled separately { - limit: NUMBER_OF_ITEMS_ON_SEPARATE_PAGE, + limit, offset: offset, rel: oasisAddress!, }, @@ -71,6 +71,8 @@ export const useAccountTransactions = (scope: SearchScope, address: string) => { export const useAccountEvents = (scope: SearchScope, address: string) => { const { network, layer } = scope + const pagination = useSearchParamsPagination('page') + const offset = (pagination.selectedPage - 1) * limit if (layer === Layer.consensus) { throw AppErrors.UnsupportedLayer // Loading events on the consensus layer is not supported yet. @@ -82,6 +84,8 @@ export const useAccountEvents = (scope: SearchScope, address: string) => { network, layer, { + limit, + offset: offset, rel: oasisAddress!, // TODO: implement filtering for non-transactional events }, @@ -93,5 +97,13 @@ export const useAccountEvents = (scope: SearchScope, address: string) => { ) const { isFetched, isLoading, isError, data } = query const events = data?.data.events - return { isFetched, isLoading, isError, events } + + if (isFetched && pagination.selectedPage > 1 && !events?.length) { + throw AppErrors.PageDoesNotExist + } + + const totalCount = data?.data.total_count + const isTotalCountClipped = data?.data.is_total_count_clipped + + return { isFetched, isLoading, isError, events, pagination, totalCount, isTotalCountClipped } } diff --git a/src/app/pages/RuntimeBlockDetailPage/BlockEventsCard.tsx b/src/app/pages/RuntimeBlockDetailPage/BlockEventsCard.tsx index bfea1645a..4c08368e8 100644 --- a/src/app/pages/RuntimeBlockDetailPage/BlockEventsCard.tsx +++ b/src/app/pages/RuntimeBlockDetailPage/BlockEventsCard.tsx @@ -4,6 +4,8 @@ import Card from '@mui/material/Card' import CardHeader from '@mui/material/CardHeader' import CardContent from '@mui/material/CardContent' import { Layer, useGetRuntimeEvents } from '../../../oasis-nexus/api' +import { NUMBER_OF_ITEMS_ON_SEPARATE_PAGE as limit } from '../../config' +import { useSearchParamsPagination } from '../../components/Table/useSearchParamsPagination' import { ErrorBoundary } from '../../components/ErrorBoundary' import { AppErrors } from '../../../types/errors' import { RuntimeEventsDetailedList } from '../../components/RuntimeEvents/RuntimeEventsDetailedList' @@ -16,6 +18,8 @@ export const eventsContainerId = 'events' const EventsList: FC = ({ scope, blockHeight }) => { const { t } = useTranslation() + const pagination = useSearchParamsPagination('page') + const offset = (pagination.selectedPage - 1) * limit if (scope.layer === Layer.consensus) { // Loading events for consensus blocks is not yet supported. // Should use useGetConsensusEvents() @@ -24,7 +28,8 @@ const EventsList: FC = ({ scope, blockHeight }) => { const eventsQuery = useGetRuntimeEvents(scope.network, scope.layer, { block: blockHeight, // TODO: search for tx_hash = null - limit: 100, // We want to avoid pagination here, if possible + limit, + offset, }) const { isLoading, isError, data } = eventsQuery @@ -48,6 +53,13 @@ const EventsList: FC = ({ scope, blockHeight }) => { isLoading={isLoading} isError={isError} addressSwitchOption={AddressSwitchOption.ETH} + pagination={{ + selectedPage: pagination.selectedPage, + linkToPage: pagination.linkToPage, + totalCount: data?.data.total_count, + isTotalCountClipped: data?.data.is_total_count_clipped, + rowsPerPage: limit, + }} /> ) }