Skip to content

Commit a21dfd7

Browse files
committed
Improve the comprehensive pagination engine
- Add documentation - Rename things for clarity - Add support for transforming the data, besides filtering
1 parent 61c87ee commit a21dfd7

File tree

5 files changed

+139
-36
lines changed

5 files changed

+139
-36
lines changed

.changelog/1385.internal.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve the comprehensive pagination engine

src/app/components/Table/PaginationEngine.ts

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,67 @@ export interface SimplePaginationEngine {
77
linkToPage: (pageNumber: number) => To
88
}
99

10-
export interface PaginatedResults<Item> {
10+
/**
11+
* The data returned by a comprehensive pagination engine to the data consumer component
12+
*/
13+
export interface ComprehensivePaginatedResults<Item, ExtractedData = typeof undefined> {
14+
/**
15+
* Control interface that can be plugged to a Table's `pagination` prop
16+
*/
1117
tablePaginationProps: TablePaginationProps
18+
19+
/**
20+
* The data provided to the data consumer in the current window
21+
*/
1222
data: Item[] | undefined
23+
24+
/**
25+
* Any extra data produced by the transformer function (besides the array of items)
26+
*/
27+
extractedData?: ExtractedData | undefined
28+
29+
/**
30+
* Is the data set still loading from the server?
31+
*/
32+
isLoading: boolean
33+
34+
/**
35+
* Has the data been loaded from the server?
36+
*/
37+
isFetched: boolean
1338
}
1439

15-
export interface ComprehensivePaginationEngine<Item, QueryResult extends List> {
16-
selectedPage: number
17-
offsetForQuery: number
18-
limitForQuery: number
19-
paramsForQuery: { offset: number; limit: number }
20-
getResults: (queryResult: QueryResult | undefined, key?: keyof QueryResult) => PaginatedResults<Item>
40+
/**
41+
* A Comprehensive PaginationEngine sits between the server and the consumer of the data and does transformations
42+
*
43+
* Specifically, the interface for loading the data and the one for the data consumers are decoupled.
44+
*/
45+
export interface ComprehensivePaginationEngine<
46+
Item,
47+
QueryResult extends List,
48+
ExtractedData = typeof undefined,
49+
> {
50+
/**
51+
* The currently selected page from the data consumer's POV
52+
*/
53+
selectedPageForClient: number
54+
55+
/**
56+
* Parameters for data to be loaded from the server
57+
*/
58+
paramsForServer: { offset: number; limit: number }
59+
60+
/**
61+
* Get the current data/state info for the data consumer component.
62+
*
63+
* @param isLoading Is the data still being loaded from the server?
64+
* @param queryResult the data coming in the server, requested according to this engine's specs, including metadata
65+
* @param key The field where the actual records can be found within queryResults
66+
*/
67+
getResults: (
68+
isLoading: boolean,
69+
isFetched: boolean,
70+
queryResult: QueryResult | undefined,
71+
key?: keyof QueryResult,
72+
) => ComprehensivePaginatedResults<Item, ExtractedData>
2173
}

src/app/components/Table/useClientSidePagination.ts

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
11
import { To, useSearchParams } from 'react-router-dom'
22
import { AppErrors } from '../../../types/errors'
3-
import { ComprehensivePaginationEngine } from './PaginationEngine'
3+
import { ComprehensivePaginatedResults, ComprehensivePaginationEngine } from './PaginationEngine'
44
import { List } from '../../../oasis-nexus/api'
55
import { TablePaginationProps } from './TablePagination'
66

7-
type ClientSizePaginationParams<Item> = {
7+
type ClientSizePaginationParams<Item, QueryResult extends List, ExtractedData = typeof undefined> = {
8+
/**
9+
* How should we call the query parameter (in the URL)?
10+
*/
811
paramName: string
12+
13+
/**
14+
* The pagination page size from the POV of the data consumer component
15+
*/
916
clientPageSize: number
17+
18+
/**
19+
* The pagination page size used for actually loading the data from the server.
20+
*
21+
* Please note that currently this engine doesn't handle when the data consumer requires data which is not
22+
* part of the initial window on the server side.
23+
*/
1024
serverPageSize: number
25+
26+
/**
27+
* Filter to be applied to the loaded data.
28+
*
29+
* If both transform and filter is set, transform will run first.
30+
*/
1131
filter?: (item: Item) => boolean
32+
33+
/**
34+
* Transformation to be applied after loading the data from the server, before presenting it to the data consumer component
35+
*
36+
* Can be used for ordering, aggregation, etc.D
37+
* If both transform and filter is set, transform will run first.
38+
*/
39+
transform?: (input: Item[], results: QueryResult) => [Item[], ExtractedData]
1240
}
1341

1442
const knownListKeys: string[] = ['total_count', 'is_total_count_clipped']
@@ -27,13 +55,20 @@ function findListIn<T extends List, Item>(data: T): Item[] {
2755
}
2856
}
2957

30-
export function useClientSidePagination<Item, QueryResult extends List>({
58+
/**
59+
* The ClientSidePagination engine loads the data from the server with a big window in one go, for in-memory pagination
60+
*/
61+
export function useClientSidePagination<Item, QueryResult extends List, ExtractedData = typeof undefined>({
3162
paramName,
3263
clientPageSize,
3364
serverPageSize,
3465
filter,
35-
}: ClientSizePaginationParams<Item>): ComprehensivePaginationEngine<Item, QueryResult> {
36-
const selectedServerPage = 1
66+
transform,
67+
}: ClientSizePaginationParams<Item, QueryResult, ExtractedData>): ComprehensivePaginationEngine<
68+
Item,
69+
QueryResult,
70+
ExtractedData
71+
> {
3772
const [searchParams] = useSearchParams()
3873
const selectedClientPageString = searchParams.get(paramName)
3974
const selectedClientPage = parseInt(selectedClientPageString ?? '1', 10)
@@ -57,30 +92,43 @@ export function useClientSidePagination<Item, QueryResult extends List>({
5792
return { search: newSearchParams.toString() }
5893
}
5994

60-
const limit = serverPageSize
61-
const offset = (selectedServerPage - 1) * clientPageSize
95+
// From the server, we always want to load the first batch of data, with the provided (big) window.
96+
// In theory, we could move this window as required, but currently this is not implemented.
97+
const selectedServerPage = 1
98+
99+
// The query parameters that should be used for loading the data from the server
62100
const paramsForQuery = {
63-
offset,
64-
limit,
101+
offset: (selectedServerPage - 1) * serverPageSize,
102+
limit: serverPageSize,
65103
}
66104

67105
return {
68-
selectedPage: selectedClientPage,
69-
offsetForQuery: offset,
70-
limitForQuery: limit,
71-
paramsForQuery,
72-
getResults: (queryResult, key) => {
73-
const data = queryResult
74-
? key
75-
? (queryResult[key] as Item[])
76-
: findListIn<QueryResult, Item>(queryResult)
106+
selectedPageForClient: selectedClientPage,
107+
paramsForServer: paramsForQuery,
108+
getResults: (
109+
isLoading,
110+
isFetched,
111+
queryResult,
112+
key,
113+
): ComprehensivePaginatedResults<Item, ExtractedData> => {
114+
const data = queryResult // we want to get list of items out from the incoming results
115+
? key // do we know where (in which field) to look?
116+
? (queryResult[key] as Item[]) // If yes, just get out the data
117+
: findListIn<QueryResult, Item>(queryResult) // If no, we will try to guess
77118
: undefined
78-
const filteredData = !!data && !!filter ? data.filter(filter) : data
79119

120+
// Apply the specified client-side transformation
121+
const [transformedData, extractedData] = !!data && !!transform ? transform(data, queryResult!) : [data]
122+
123+
// Apply the specified filtering
124+
const filteredData = !!transformedData && !!filter ? transformedData.filter(filter) : transformedData
125+
126+
// The data window from the POV of the data consumer component
80127
const offset = (selectedClientPage - 1) * clientPageSize
81128
const limit = clientPageSize
82129
const dataWindow = filteredData ? filteredData.slice(offset, offset + limit) : undefined
83130

131+
// The control interface for the data consumer component (i.e. Table)
84132
const tableProps: TablePaginationProps = {
85133
selectedPage: selectedClientPage,
86134
linkToPage,
@@ -96,8 +144,10 @@ export function useClientSidePagination<Item, QueryResult extends List>({
96144
return {
97145
tablePaginationProps: tableProps,
98146
data: dataWindow,
147+
extractedData,
148+
isLoading,
149+
isFetched,
99150
}
100151
},
101-
// tableProps,
102152
}
103153
}

src/app/components/Table/useComprehensiveSearchParamsPagination.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,9 @@ export function useComprehensiveSearchParamsPagination<Item, QueryResult extends
6464
}
6565

6666
return {
67-
selectedPage,
68-
offsetForQuery: offset,
69-
limitForQuery: limit,
70-
paramsForQuery,
71-
getResults: (queryResult, key) => {
67+
selectedPageForClient: selectedPage,
68+
paramsForServer: paramsForQuery,
69+
getResults: (isLoading, isFetched, queryResult, key) => {
7270
const data = queryResult
7371
? key
7472
? (queryResult[key] as Item[])
@@ -85,6 +83,8 @@ export function useComprehensiveSearchParamsPagination<Item, QueryResult extends
8583
return {
8684
tablePaginationProps: tableProps,
8785
data: filteredData,
86+
isLoading,
87+
isFetched,
8888
}
8989
},
9090
// tableProps,

src/app/pages/TokenDashboardPage/hook.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const _useTokenTransfers = (scope: SearchScope, params: undefined | GetRu
7272
network,
7373
layer, // This is OK since consensus has been handled separately
7474
{
75-
...pagination.paramsForQuery,
75+
...pagination.paramsForServer,
7676
type: RuntimeEventType.evmlog,
7777
// The following is the hex-encoded signature for Transfer(address,address,uint256)
7878
evm_log_signature: 'ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
@@ -87,16 +87,16 @@ export const _useTokenTransfers = (scope: SearchScope, params: undefined | GetRu
8787

8888
const { isFetched, isLoading, data } = query
8989

90-
const results = pagination.getResults(data?.data)
90+
const results = pagination.getResults(isLoading, isFetched, data?.data)
9191

92-
if (isFetched && pagination.selectedPage > 1 && !results.data?.length) {
92+
if (isFetched && pagination.selectedPageForClient > 1 && !results.data?.length) {
9393
throw AppErrors.PageDoesNotExist
9494
}
9595

9696
return {
9797
isLoading,
9898
isFetched,
99-
results: pagination.getResults(data?.data),
99+
results,
100100
}
101101
}
102102

0 commit comments

Comments
 (0)