Skip to content

Commit e733dbf

Browse files
UX Optimization: Replace debounce with prefetch additional page for consent registry
1 parent 7d07a8a commit e733dbf

4 files changed

Lines changed: 68 additions & 28 deletions

File tree

portal/frontend/src/__tests__/ConsentRegistryPage.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,41 @@ describe('ConsentRegistryPage', () => {
268268
expect(String(requestUrl)).toContain('offset=25')
269269
})
270270

271+
it('prefetches exactly one next page when more consents are available', async () => {
272+
vi.stubGlobal('fetch', fetchMock)
273+
fetchMock.mockImplementation(async (input: RequestInfo | URL) => {
274+
const url = new URL(String(input))
275+
const offset = Number(url.searchParams.get('offset') ?? 0)
276+
277+
return {
278+
ok: true,
279+
status: 200,
280+
json: async () => ({
281+
data: [],
282+
metadata: {
283+
total: 25,
284+
offset,
285+
count: 0,
286+
limit: 10,
287+
},
288+
}),
289+
}
290+
})
291+
292+
renderConsentRegistryPage(createQueryClient())
293+
294+
await waitFor(() => {
295+
expect(fetchMock).toHaveBeenCalledTimes(2)
296+
})
297+
298+
const requestedOffsets = fetchMock.mock.calls.map(([requestUrl]) => {
299+
const url = new URL(String(requestUrl))
300+
return Number(url.searchParams.get('offset'))
301+
})
302+
303+
expect(requestedOffsets).toEqual([0, 10])
304+
})
305+
271306
it('maps URL filters to v0.3 consent search parameters', async () => {
272307
vi.stubGlobal('fetch', fetchMock)
273308
fetchMock.mockResolvedValue({

portal/frontend/src/features/consent-registry/ConsentRegistryPage.tsx

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
import { Box, Stack, Typography } from '@wso2/oxygen-ui'
20-
import { useEffect, useMemo, useState } from 'react'
20+
import { useMemo, useState } from 'react'
2121
import { useTranslation } from 'react-i18next'
2222
import { useSearchParams } from 'react-router-dom'
2323
import HeaderBreadcrumbs from '../../components/layout/main-layout/HeaderBreadcrumbs'
@@ -50,7 +50,6 @@ const FILTER_STATUS_VALUES: ConsentRegistryFiltersModel['status'][] = [
5050
'Expired',
5151
]
5252

53-
const TABLE_SKELETON_DEBOUNCE_MS = 200
5453
const DEFAULT_PAGE = 0
5554
const DEFAULT_ROWS_PER_PAGE = 10
5655

@@ -134,36 +133,18 @@ function ConsentRegistryPage(): React.JSX.Element {
134133
const [selectedRevocationConsentID, setSelectedRevocationConsentID] = useState<string | null>(
135134
null,
136135
)
137-
const [showTableSkeleton, setShowTableSkeleton] = useState<boolean>(false)
138-
139136
const filters = useMemo(() => getFiltersFromSearchParams(searchParams), [searchParams])
140137
const page = useMemo(() => getPageFromSearchParams(searchParams), [searchParams])
141138
const rowsPerPage = useMemo(() => getRowsPerPageFromSearchParams(searchParams), [searchParams])
142139
const consentListQuery = useConsentListQuery(filters, page, rowsPerPage)
143140
const selectedApprovalConsentQuery = useConsentDetailQuery(selectedApprovalConsentID ?? undefined)
144141
const approveMutation = useApproveConsentMutation()
145142
const revokeMutation = useRevokeConsentMutation()
146-
const isTableFetching = consentListQuery.isFetching
143+
const isTableLoading = consentListQuery.isPending || consentListQuery.isPlaceholderData
147144

148145
const rows = consentListQuery.data?.rows ?? []
149146
const totalCount = consentListQuery.data?.total ?? 0
150147

151-
useEffect(() => {
152-
let debounceDelay = 0
153-
154-
if (!consentListQuery.isLoading && isTableFetching) {
155-
debounceDelay = TABLE_SKELETON_DEBOUNCE_MS
156-
}
157-
158-
const skeletonTimer = window.setTimeout(() => {
159-
setShowTableSkeleton(isTableFetching)
160-
}, debounceDelay)
161-
162-
return () => {
163-
window.clearTimeout(skeletonTimer)
164-
}
165-
}, [consentListQuery.isLoading, isTableFetching])
166-
167148
return (
168149
<Box component="main" sx={{ p: { xs: 2, md: 4 } }}>
169150
<Stack spacing={3}>
@@ -190,11 +171,11 @@ function ConsentRegistryPage(): React.JSX.Element {
190171
<Typography color="error.main">{t('consentRegistry.messages.loadFailed')}</Typography>
191172
) : null}
192173

193-
{!consentListQuery.isError && (rows.length > 0 || isTableFetching) ? (
174+
{!consentListQuery.isError && (rows.length > 0 || isTableLoading) ? (
194175
<ConsentRegistryTable
195176
rows={rows}
196177
totalCount={totalCount}
197-
isLoading={isTableFetching && showTableSkeleton}
178+
isLoading={isTableLoading}
198179
page={page}
199180
rowsPerPage={rowsPerPage}
200181
onPageChange={(nextPage) => {
@@ -217,7 +198,7 @@ function ConsentRegistryPage(): React.JSX.Element {
217198
/>
218199
) : null}
219200

220-
{!isTableFetching && !consentListQuery.isError && rows.length === 0 ? (
201+
{!isTableLoading && !consentListQuery.isError && rows.length === 0 ? (
221202
<Typography>{t('consentRegistry.messages.empty')}</Typography>
222203
) : null}
223204

portal/frontend/src/features/consent-registry/hooks/useConsentQueries.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
import {
2020
keepPreviousData,
21+
queryOptions,
2122
type UseMutationResult,
2223
type UseQueryResult,
2324
useMutation,
2425
useQuery,
2526
useQueryClient,
2627
} from '@tanstack/react-query'
28+
import { useEffect } from 'react'
2729
import {
2830
approveMyConsent,
2931
fetchMyConsentByID,
@@ -102,14 +104,14 @@ function toConsentRow(consent: ConsentDetailAPI): ConsentRecord {
102104
}
103105
}
104106

105-
export function useConsentListQuery(
107+
function consentListQueryOptions(
106108
filters: ConsentRegistryFilters,
107109
page: number,
108110
rowsPerPage: number,
109-
): UseQueryResult<ConsentListResult> {
111+
) {
110112
const params = toListParams(filters, page, rowsPerPage)
111113

112-
return useQuery<ConsentListResult>({
114+
return queryOptions({
113115
queryKey: ['consents', params],
114116
queryFn: async (): Promise<ConsentListResult> => {
115117
const response = await fetchMyConsents(params)
@@ -122,6 +124,28 @@ export function useConsentListQuery(
122124
})
123125
}
124126

127+
export function useConsentListQuery(
128+
filters: ConsentRegistryFilters,
129+
page: number,
130+
rowsPerPage: number,
131+
): UseQueryResult<ConsentListResult> {
132+
const queryClient = useQueryClient()
133+
const query = useQuery(consentListQueryOptions(filters, page, rowsPerPage))
134+
135+
useEffect(() => {
136+
const nextPage = page + 1
137+
const hasNextPage = nextPage * rowsPerPage < (query.data?.total ?? 0)
138+
139+
if (!query.isPlaceholderData && hasNextPage) {
140+
queryClient
141+
.prefetchQuery(consentListQueryOptions(filters, nextPage, rowsPerPage))
142+
.catch(() => undefined)
143+
}
144+
}, [filters, page, query.data?.total, query.isPlaceholderData, queryClient, rowsPerPage])
145+
146+
return query
147+
}
148+
125149
export function useConsentDetailQuery(
126150
consentID: string | undefined,
127151
): UseQueryResult<ConsentDetailAPI> {

portal/frontend/src/utils/queryClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import { QueryClient } from '@tanstack/react-query'
2020

21-
const STALE_TIME_IN_MS = 1 * 60 * 1000
21+
const STALE_TIME_IN_MS = 0.5 * 60 * 1000
2222

2323
const queryClient = new QueryClient({
2424
defaultOptions: {

0 commit comments

Comments
 (0)