Skip to content

Commit 0cc3f12

Browse files
authored
PLU-350: fix: add check for invalid pages in params (#777)
## Problem Right now, users can access pages out of bounds and this becomes an issue when a user has 11 pipes and if the pipe being deleted falls on the second page, it will continue displaying `?page=2` and load an empty state ## Solution - Check for the last possible page and load that instead - Apply to other pages: tiles, executions ## Tests Test 1 - Start with 11 pipes or tiles, delete the one on the 2nd page, it should load back to the first page and set the page param to be the nearest one which is 1 Test 2 - Search `?page=100` (basically over the current amount), it should load to the last page possible
1 parent 2d069ad commit 0cc3f12

File tree

3 files changed

+48
-18
lines changed

3 files changed

+48
-18
lines changed

packages/frontend/src/pages/Executions/index.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { IExecution } from '@plumber/types'
22

3-
import { ReactElement } from 'react'
3+
import { ReactElement, useEffect } from 'react'
44
import { useQuery } from '@apollo/client'
55
import { Center, Flex } from '@chakra-ui/react'
66
import { Pagination } from '@opengovsg/design-system-react'
@@ -16,7 +16,7 @@ import { usePaginationAndFilter } from '@/hooks/usePaginationAndFilter'
1616

1717
import SearchWithFilterInput from './components/SearchWithFilterInput'
1818

19-
const EXECUTION_PER_PAGE = 10
19+
const EXECUTIONS_PER_PAGE = 10
2020
const EXECUTIONS_TITLE = 'Executions'
2121

2222
interface ExecutionParameters {
@@ -32,8 +32,8 @@ interface ExecutionsListProps {
3232
}
3333

3434
const getLimitAndOffset = (params: ExecutionParameters) => ({
35-
limit: EXECUTION_PER_PAGE,
36-
offset: (params.page - 1) * EXECUTION_PER_PAGE,
35+
limit: EXECUTIONS_PER_PAGE,
36+
offset: (params.page - 1) * EXECUTIONS_PER_PAGE,
3737
...(params.status !== StatusType.Waiting && { status: params.status }),
3838
searchInput: params.input,
3939
})
@@ -98,7 +98,17 @@ export default function Executions(): ReactElement {
9898
edges?.map(({ node }: { node: IExecution }) => node) ?? []
9999

100100
const hasNoUserExecutions = executions.length === 0 && !isSearching
101-
const hasPagination = !loading && pageInfo?.totalCount > EXECUTION_PER_PAGE
101+
const totalCount: number = pageInfo?.totalCount ?? 0
102+
const hasPagination = !loading && totalCount > EXECUTIONS_PER_PAGE
103+
104+
// ensure invalid pages won't be accessed even after deleting executions
105+
const lastPage = Math.ceil(totalCount / EXECUTIONS_PER_PAGE)
106+
useEffect(() => {
107+
// Defer the search params update till after the initial render
108+
if (lastPage !== 0 && page > lastPage) {
109+
setSearchParams({ page: lastPage })
110+
}
111+
}, [lastPage, page, setSearchParams])
102112

103113
return (
104114
<Container py={9}>
@@ -127,8 +137,8 @@ export default function Executions(): ReactElement {
127137
<Pagination
128138
currentPage={pageInfo?.currentPage}
129139
onPageChange={(page) => setSearchParams({ page })}
130-
pageSize={EXECUTION_PER_PAGE}
131-
totalCount={pageInfo?.totalCount}
140+
pageSize={EXECUTIONS_PER_PAGE}
141+
totalCount={totalCount}
132142
/>
133143
</Flex>
134144
)}

packages/frontend/src/pages/Flows/index.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { IFlow } from '@plumber/types'
22

3+
import { ReactElement, useEffect } from 'react'
34
import { useQuery } from '@apollo/client'
45
import { Box, Center, Flex, useDisclosure } from '@chakra-ui/react'
56
import { Button, Pagination } from '@opengovsg/design-system-react'
@@ -17,7 +18,7 @@ import ApproveTransfersInfobox from './components/ApproveTransfersInfobox'
1718
import CreateFlowModal from './components/CreateFlowModal'
1819
import EmptyFlows from './components/EmptyFlows'
1920

20-
const FLOW_PER_PAGE = 10
21+
const FLOWS_PER_PAGE = 10
2122
const FLOWS_TITLE = 'Pipes'
2223

2324
interface FlowsInternalProps {
@@ -28,8 +29,8 @@ interface FlowsInternalProps {
2829
}
2930

3031
const getLimitAndOffset = (page: number) => ({
31-
limit: FLOW_PER_PAGE,
32-
offset: (page - 1) * FLOW_PER_PAGE,
32+
limit: FLOWS_PER_PAGE,
33+
offset: (page - 1) * FLOWS_PER_PAGE,
3334
})
3435

3536
function FlowsList({
@@ -71,7 +72,7 @@ function FlowsList({
7172
)
7273
}
7374

74-
export default function Flows(): React.ReactElement {
75+
export default function Flows(): ReactElement {
7576
const { input, page, setSearchParams, isSearching } = usePaginationAndFilter()
7677
const { isOpen, onOpen, onClose } = useDisclosure()
7778

@@ -84,10 +85,19 @@ export default function Flows(): React.ReactElement {
8485

8586
const { pageInfo, edges } = data?.getFlows || {}
8687
const flows: IFlow[] = edges?.map(({ node }: { node: IFlow }) => node) ?? []
87-
const hasPagination =
88-
!loading && pageInfo && pageInfo.totalCount > FLOW_PER_PAGE
88+
const totalCount: number = pageInfo?.totalCount ?? 0
89+
const hasPagination = !loading && totalCount > FLOWS_PER_PAGE
8990
const hasNoUserFlows = flows.length === 0 && !isSearching
9091

92+
// ensure invalid pages won't be accessed even after deleting flows
93+
const lastPage = Math.ceil(totalCount / FLOWS_PER_PAGE)
94+
useEffect(() => {
95+
// Defer the search params update till after the initial render
96+
if (lastPage !== 0 && page > lastPage) {
97+
setSearchParams({ page: lastPage })
98+
}
99+
}, [lastPage, page, setSearchParams])
100+
91101
return (
92102
<Container py={9}>
93103
{!hasNoUserFlows && (
@@ -121,8 +131,8 @@ export default function Flows(): React.ReactElement {
121131
<Pagination
122132
currentPage={pageInfo?.currentPage}
123133
onPageChange={(page) => setSearchParams({ page })}
124-
pageSize={FLOW_PER_PAGE}
125-
totalCount={pageInfo?.totalCount}
134+
pageSize={FLOWS_PER_PAGE}
135+
totalCount={totalCount}
126136
/>
127137
</Flex>
128138
)}

packages/frontend/src/pages/Tiles/index.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect } from 'react'
12
import { useQuery } from '@apollo/client'
23
import { Center, Flex } from '@chakra-ui/react'
34
import { Pagination } from '@opengovsg/design-system-react'
@@ -70,8 +71,17 @@ export default function Tiles(): JSX.Element {
7071
const tilesToDisplay = edges?.map(({ node }) => node) ?? []
7172

7273
const hasNoUserTiles = tilesToDisplay.length === 0 && !isSearching
73-
const hasPagination =
74-
!loading && pageInfo && pageInfo.totalCount > TILES_PER_PAGE
74+
const totalCount: number = pageInfo?.totalCount ?? 0
75+
const hasPagination = !loading && pageInfo && totalCount > TILES_PER_PAGE
76+
77+
// ensure invalid pages won't be accessed even after deleting tiles
78+
const lastPage = Math.ceil(totalCount / TILES_PER_PAGE)
79+
useEffect(() => {
80+
// Defer the search params update till after the initial render
81+
if (lastPage !== 0 && page > lastPage) {
82+
setSearchParams({ page: lastPage })
83+
}
84+
}, [lastPage, page, setSearchParams])
7585

7686
return (
7787
<Container py={9}>
@@ -100,7 +110,7 @@ export default function Tiles(): JSX.Element {
100110
currentPage={pageInfo.currentPage}
101111
onPageChange={(page) => setSearchParams({ page })}
102112
pageSize={TILES_PER_PAGE}
103-
totalCount={pageInfo.totalCount}
113+
totalCount={totalCount}
104114
/>
105115
</Flex>
106116
)}

0 commit comments

Comments
 (0)