Tighten block transaction drilldown paging
This commit is contained in:
@@ -21,6 +21,8 @@ export default function BlockDetailPage() {
|
||||
const [blockTransactions, setBlockTransactions] = useState<Transaction[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [transactionsLoading, setTransactionsLoading] = useState(true)
|
||||
const [transactionsError, setTransactionsError] = useState(false)
|
||||
const [hasNextTransactionsPage, setHasNextTransactionsPage] = useState(false)
|
||||
const [transactionPage, setTransactionPage] = useState(1)
|
||||
const blockTransactionPageSize = 25
|
||||
|
||||
@@ -39,12 +41,17 @@ export default function BlockDetailPage() {
|
||||
|
||||
const loadBlockTransactions = useCallback(async () => {
|
||||
setTransactionsLoading(true)
|
||||
setTransactionsError(false)
|
||||
try {
|
||||
const { ok, data } = await transactionsApi.listByBlockSafe(chainId, blockNumber, transactionPage, blockTransactionPageSize)
|
||||
setBlockTransactions(ok ? data : [])
|
||||
const { ok, data, hasNextPage } = await transactionsApi.listByBlockSafe(chainId, blockNumber, transactionPage, blockTransactionPageSize)
|
||||
setBlockTransactions(data)
|
||||
setHasNextTransactionsPage(hasNextPage)
|
||||
setTransactionsError(!ok)
|
||||
} catch (error) {
|
||||
console.error('Failed to load block transactions:', error)
|
||||
setBlockTransactions([])
|
||||
setHasNextTransactionsPage(false)
|
||||
setTransactionsError(true)
|
||||
} finally {
|
||||
setTransactionsLoading(false)
|
||||
}
|
||||
@@ -57,6 +64,8 @@ export default function BlockDetailPage() {
|
||||
if (!isValidBlock) {
|
||||
setLoading(false)
|
||||
setTransactionsLoading(false)
|
||||
setTransactionsError(false)
|
||||
setHasNextTransactionsPage(false)
|
||||
setBlock(null)
|
||||
setBlockTransactions([])
|
||||
return
|
||||
@@ -81,8 +90,6 @@ export default function BlockDetailPage() {
|
||||
const gasUtilization = block && block.gas_limit > 0
|
||||
? Math.round((block.gas_used / block.gas_limit) * 100)
|
||||
: null
|
||||
const canGoNextTransactionsPage = blockTransactions.length === blockTransactionPageSize
|
||||
|
||||
const transactionColumns = [
|
||||
{
|
||||
header: 'Hash',
|
||||
@@ -232,7 +239,9 @@ export default function BlockDetailPage() {
|
||||
columns={transactionColumns}
|
||||
data={blockTransactions}
|
||||
emptyMessage={
|
||||
block.transaction_count > 0
|
||||
transactionsError
|
||||
? 'Unable to load indexed block transactions right now. Please retry from this page in a moment.'
|
||||
: block.transaction_count > 0
|
||||
? 'No indexed block transactions were returned for this page yet.'
|
||||
: 'This block does not contain any indexed transactions.'
|
||||
}
|
||||
@@ -255,7 +264,7 @@ export default function BlockDetailPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTransactionPage((current) => current + 1)}
|
||||
disabled={transactionsLoading || !canGoNextTransactionsPage}
|
||||
disabled={transactionsLoading || !hasNextTransactionsPage}
|
||||
className="rounded bg-gray-200 px-4 py-2 text-gray-900 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-gray-800 dark:text-gray-100"
|
||||
>
|
||||
Next tx page
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('transactionsApi.listByBlockSafe', () => {
|
||||
timestamp: '2026-04-16T09:40:12.000000Z',
|
||||
},
|
||||
],
|
||||
next_page_params: { page: 2, page_size: 10 },
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -36,12 +37,25 @@ describe('transactionsApi.listByBlockSafe', () => {
|
||||
|
||||
expect(result.ok).toBe(true)
|
||||
expect(result.data).toHaveLength(1)
|
||||
expect(result.hasNextPage).toBe(true)
|
||||
expect(result.data[0]?.hash).toBe('0xabc')
|
||||
expect(fetchMock).toHaveBeenCalledTimes(1)
|
||||
expect(fetchMock.mock.calls[0]?.[0]).toEqual(
|
||||
expect.stringContaining('/api/v2/blocks/123/transactions?page=1&page_size=10'),
|
||||
)
|
||||
})
|
||||
|
||||
it('returns a non-throwing failure result when the block transaction request fails', async () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('network down')))
|
||||
|
||||
const result = await transactionsApi.listByBlockSafe(138, 123, 1, 10)
|
||||
|
||||
expect(result).toEqual({
|
||||
ok: false,
|
||||
data: [],
|
||||
hasNextPage: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('transactionsApi.diagnoseMissing', () => {
|
||||
|
||||
@@ -76,6 +76,11 @@ export interface TransactionLookupDiagnostic {
|
||||
rpc_url?: string
|
||||
}
|
||||
|
||||
export interface BlockTransactionListPage {
|
||||
data: Transaction[]
|
||||
next_page_params: Record<string, unknown> | null
|
||||
}
|
||||
|
||||
const CHAIN_138_PUBLIC_RPC_URL = 'https://rpc-http-pub.d-bis.org'
|
||||
|
||||
function resolvePublicRpcUrl(chainId: number): string | null {
|
||||
@@ -227,26 +232,37 @@ export const transactionsApi = {
|
||||
return { ok: false, data: [] }
|
||||
}
|
||||
},
|
||||
listByBlock: async (chainId: number, blockNumber: number, page = 1, pageSize = 25): Promise<ApiResponse<Transaction[]>> => {
|
||||
listByBlock: async (chainId: number, blockNumber: number, page = 1, pageSize = 25): Promise<ApiResponse<BlockTransactionListPage>> => {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
})
|
||||
const raw = await fetchBlockscoutJson<{ items?: unknown[] }>(`/api/v2/blocks/${blockNumber}/transactions?${params.toString()}`)
|
||||
const raw = await fetchBlockscoutJson<{ items?: unknown[]; next_page_params?: Record<string, unknown> | null }>(
|
||||
`/api/v2/blocks/${blockNumber}/transactions?${params.toString()}`
|
||||
)
|
||||
const data = Array.isArray(raw?.items) ? raw.items.map((item) => normalizeTransaction(item as never, chainId)) : []
|
||||
return { data }
|
||||
return {
|
||||
data: {
|
||||
data,
|
||||
next_page_params: raw?.next_page_params ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
listByBlockSafe: async (
|
||||
chainId: number,
|
||||
blockNumber: number,
|
||||
page = 1,
|
||||
pageSize = 25,
|
||||
): Promise<{ ok: boolean; data: Transaction[] }> => {
|
||||
): Promise<{ ok: boolean; data: Transaction[]; hasNextPage: boolean }> => {
|
||||
try {
|
||||
const { data } = await transactionsApi.listByBlock(chainId, blockNumber, page, pageSize)
|
||||
return { ok: true, data }
|
||||
return {
|
||||
ok: true,
|
||||
data: data.data,
|
||||
hasNextPage: data.next_page_params != null,
|
||||
}
|
||||
} catch {
|
||||
return { ok: false, data: [] }
|
||||
return { ok: false, data: [], hasNextPage: false }
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user