Skip to content

Commit ccf2641

Browse files
authored
fix: Add pagination to reports table to fix UI performance (#1797)
1 parent 3367475 commit ccf2641

File tree

1 file changed

+109
-15
lines changed
  • ui/packages/evidently-ui-lib/src/routes-components/snapshots

1 file changed

+109
-15
lines changed

ui/packages/evidently-ui-lib/src/routes-components/snapshots/index.tsx

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
TableBody,
1515
TableCell,
1616
TableHead,
17+
TablePagination,
1718
TableRow,
1819
TableSortLabel,
1920
TextField,
@@ -23,7 +24,13 @@ import {
2324

2425
import { useLocalStorage } from '@uidotdev/usehooks'
2526

26-
import { Delete as DeleteIcon } from '@mui/icons-material'
27+
import {
28+
Delete as DeleteIcon,
29+
FirstPage as FirstPageIcon,
30+
KeyboardArrowLeft,
31+
KeyboardArrowRight,
32+
LastPage as LastPageIcon
33+
} from '@mui/icons-material'
2734
import { Autocomplete } from '@mui/material'
2835
import type { DownloadSnapshotURL, MetadataModel, ReportModel } from '~/api/types'
2936
import { DownloadButton } from '~/components/Actions/DownloadButton'
@@ -96,6 +103,59 @@ type SnapshotActionsWrapperProps = {
96103
snapshotSelection?: { title: string; action: (snapshots: string[]) => void }
97104
}
98105

106+
type TablePaginationActionsProps = {
107+
count: number
108+
page: number
109+
rowsPerPage: number
110+
onPageChange: (event: React.MouseEvent<HTMLButtonElement>, newPage: number) => void
111+
}
112+
113+
function TablePaginationActions({
114+
count,
115+
page,
116+
rowsPerPage,
117+
onPageChange
118+
}: TablePaginationActionsProps) {
119+
const handleFirstPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
120+
onPageChange(event, 0)
121+
}
122+
123+
const handleBackButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
124+
onPageChange(event, page - 1)
125+
}
126+
127+
const handleNextButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
128+
onPageChange(event, page + 1)
129+
}
130+
131+
const handleLastPageButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {
132+
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1))
133+
}
134+
135+
const isLastPage = page >= Math.ceil(count / rowsPerPage) - 1
136+
137+
return (
138+
<Box sx={{ flexShrink: 0, ml: 2.5 }}>
139+
<IconButton
140+
onClick={handleFirstPageButtonClick}
141+
disabled={page === 0}
142+
aria-label='first page'
143+
>
144+
<FirstPageIcon />
145+
</IconButton>
146+
<IconButton onClick={handleBackButtonClick} disabled={page === 0} aria-label='previous page'>
147+
<KeyboardArrowLeft />
148+
</IconButton>
149+
<IconButton onClick={handleNextButtonClick} disabled={isLastPage} aria-label='next page'>
150+
<KeyboardArrowRight />
151+
</IconButton>
152+
<IconButton onClick={handleLastPageButtonClick} disabled={isLastPage} aria-label='last page'>
153+
<LastPageIcon />
154+
</IconButton>
155+
</Box>
156+
)
157+
}
158+
99159
export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
100160
const {
101161
query,
@@ -116,6 +176,10 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
116176
const [selectedTags, setTags] = useState(() => query.tags?.split(',') || [])
117177
const [metadataQuery, setMetadataQuery] = useState(() => query['metadata-query'] || '')
118178

179+
// Pagination state
180+
const [page, setPage] = useState(0)
181+
const [rowsPerPage, setRowsPerPage] = useState(10)
182+
119183
useUpdateQueryStringValueWithoutNavigation('tags', selectedTags.join(','))
120184
useUpdateQueryStringValueWithoutNavigation('metadata-query', String(metadataQuery))
121185

@@ -162,6 +226,21 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
162226
[filteredSnapshotsByMetadata, sortByTimestamp]
163227
)
164228

229+
// Paginate results
230+
const paginatedSnapshots = useMemo(
231+
() => resultSnapshots.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage),
232+
[resultSnapshots, page, rowsPerPage]
233+
)
234+
235+
const handleChangePage = (_event: unknown, newPage: number) => {
236+
setPage(newPage)
237+
}
238+
239+
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
240+
setRowsPerPage(Number.parseInt(event.target.value, 10))
241+
setPage(0)
242+
}
243+
165244
const [selectedSnapshots, setSelectedSnapshots] = useState<Set<string>>(new Set())
166245
const isSnapshotSelected = (id: string) => selectedSnapshots.has(id)
167246

@@ -173,7 +252,10 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
173252
multiple
174253
limitTags={2}
175254
value={selectedTags}
176-
onChange={(_, newSelectedTags) => setTags(newSelectedTags)}
255+
onChange={(_, newSelectedTags) => {
256+
setTags(newSelectedTags)
257+
setPage(0)
258+
}}
177259
options={ALL_TAGS}
178260
renderInput={(params) => (
179261
<TextField {...params} variant='standard' label='Filter by Tags' />
@@ -185,7 +267,10 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
185267
<TextField
186268
fullWidth
187269
value={metadataQuery}
188-
onChange={(event) => setMetadataQuery(event.target.value)}
270+
onChange={(event) => {
271+
setMetadataQuery(event.target.value)
272+
setPage(0)
273+
}}
189274
variant='standard'
190275
label='Search in Metadata'
191276
/>
@@ -261,17 +346,16 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
261346
direction={sortByTimestamp}
262347
onClick={() => {
263348
setSortByTimestamp((prev) => {
349+
let next: typeof prev
264350
if (prev === undefined) {
265-
return 'desc'
266-
}
267-
268-
if (prev === 'desc') {
269-
return 'asc'
270-
}
271-
272-
if (prev === 'asc') {
273-
return undefined
351+
next = 'desc'
352+
} else if (prev === 'desc') {
353+
next = 'asc'
354+
} else {
355+
next = undefined
274356
}
357+
setPage(0)
358+
return next
275359
})
276360
}}
277361
>
@@ -283,7 +367,7 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
283367
<TableRow />
284368
</TableHead>
285369
<TableBody>
286-
{resultSnapshots.map((snapshot) => (
370+
{paginatedSnapshots.map((snapshot) => (
287371
<TableRow key={`r-${snapshot.id}`}>
288372
{snapshotSelection && (
289373
<TableCell padding='checkbox'>
@@ -339,8 +423,8 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
339423
variant={slots?.donwloadButtonVariant || 'outlined'}
340424
disabled={disabled ?? false}
341425
downloadLink={
342-
// better type safety
343-
downloadLink
426+
// normalize to string before replace (DownloadSnapshotURL isn't guaranteed to be string)
427+
String(downloadLink)
344428
.replace('{project_id}', projectId)
345429
.replace('{snapshot_id}', snapshot.id)
346430
}
@@ -376,6 +460,16 @@ export const SnapshotsListTemplate = (props: SnapshotActionsWrapperProps) => {
376460
))}
377461
</TableBody>
378462
</Table>
463+
<TablePagination
464+
rowsPerPageOptions={[10, 30, 50]}
465+
component='div'
466+
count={resultSnapshots.length}
467+
rowsPerPage={rowsPerPage}
468+
page={page}
469+
onPageChange={handleChangePage}
470+
onRowsPerPageChange={handleChangeRowsPerPage}
471+
ActionsComponent={TablePaginationActions}
472+
/>
379473
</>
380474
)
381475
}

0 commit comments

Comments
 (0)