Skip to content

Commit 175d75b

Browse files
authored
refactor: Convert all remaining tables to new useQueryTable (#2575)
* redo useQueryTable so that the types are legit and it returns the data * fix junky getRowId and use new QueryTable on sleds and physical disks * get wild with a list-specific helper to make the call sites clean * encapsulate pageSize in the query config so it is defined in one place * do the placeholderData thing for all lists * scroll to top when page changes * loading spinner on page changes! * fix the pagination test, test lovely new scroll reset logic * fix other e2es, don't scroll reset on browser forward/back * fix bug found while converting other tables, extract useScrollReset * move columns up * convert instance list to new QueryTable, fix polling bug * convert the rest of the tables * convert a few more * a hard one: IpPoolPage, have to handle rows with no ID field * last few easy ones * last ones and delete QueryTable * rename file back to QueryTable
1 parent 3e40ff7 commit 175d75b

26 files changed

+394
-414
lines changed

app/api/client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ import {
1919
wrapQueryClient,
2020
} from './hooks'
2121

22-
export { ensurePrefetched, PAGE_SIZE, type PaginatedQuery } from './hooks'
22+
export {
23+
ensurePrefetched,
24+
usePrefetchedQuery,
25+
PAGE_SIZE,
26+
type PaginatedQuery,
27+
} from './hooks'
2328

2429
export const api = new Api({
2530
// unit tests run in Node, whose fetch implementation requires a full URL

app/api/hooks.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ export function ensurePrefetched<TData, TError>(
232232
return result as SetNonNullable<typeof result, 'data'>
233233
}
234234

235+
export const usePrefetchedQuery = <TData>(options: UseQueryOptions<TData, ApiError>) =>
236+
ensurePrefetched(useQuery(options), options.queryKey)
237+
235238
const ERRORS_ALLOWED = 'errors-allowed'
236239

237240
/** Result that includes both success and error so it can be cached by RQ */

app/pages/ProjectsPage.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import { Outlet, useNavigate } from 'react-router-dom'
1111

1212
import {
1313
apiQueryClient,
14+
getListQFn,
15+
queryClient,
1416
useApiMutation,
15-
useApiQueryClient,
16-
usePrefetchedApiQuery,
1717
type Project,
1818
} from '@oxide/api'
1919
import { Folder16Icon, Folder24Icon } from '@oxide/design-system/icons/react'
@@ -24,7 +24,7 @@ import { confirmDelete } from '~/stores/confirm-delete'
2424
import { makeLinkCell } from '~/table/cells/LinkCell'
2525
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
2626
import { Columns } from '~/table/columns/common'
27-
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
27+
import { useQueryTable } from '~/table/QueryTable'
2828
import { CreateLink } from '~/ui/lib/CreateButton'
2929
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3030
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
@@ -42,8 +42,10 @@ const EmptyState = () => (
4242
/>
4343
)
4444

45+
const projectList = getListQFn('projectList', {})
46+
4547
export async function loader() {
46-
await apiQueryClient.prefetchQuery('projectList', { query: { limit: PAGE_SIZE } })
48+
await queryClient.prefetchQuery(projectList.optionsFn())
4749
return null
4850
}
4951

@@ -60,17 +62,11 @@ Component.displayName = 'ProjectsPage'
6062
export function Component() {
6163
const navigate = useNavigate()
6264

63-
const queryClient = useApiQueryClient()
64-
const { Table } = useQueryTable('projectList', {})
65-
const { data: projects } = usePrefetchedApiQuery('projectList', {
66-
query: { limit: PAGE_SIZE },
67-
})
68-
6965
const { mutateAsync: deleteProject } = useApiMutation('projectDelete', {
7066
onSuccess() {
7167
// TODO: figure out if this is invalidating as expected, can we leave out the query
7268
// altogether, etc. Look at whether limit param matters.
73-
queryClient.invalidateQueries('projectList')
69+
apiQueryClient.invalidateQueries('projectList')
7470
},
7571
})
7672

@@ -100,6 +96,12 @@ export function Component() {
10096
[deleteProject, navigate]
10197
)
10298

99+
const columns = useColsWithActions(staticCols, makeActions)
100+
const {
101+
table,
102+
query: { data: projects },
103+
} = useQueryTable({ query: projectList, columns, emptyState: <EmptyState /> })
104+
103105
useQuickActions(
104106
useMemo(
105107
() => [
@@ -117,8 +119,6 @@ export function Component() {
117119
)
118120
)
119121

120-
const columns = useColsWithActions(staticCols, makeActions)
121-
122122
return (
123123
<>
124124
<PageHeader>
@@ -133,7 +133,7 @@ export function Component() {
133133
<TableActions>
134134
<CreateLink to={pb.projectsNew()}>New Project</CreateLink>
135135
</TableActions>
136-
<Table columns={columns} emptyState={<EmptyState />} />
136+
{table}
137137
<Outlet />
138138
</>
139139
)

app/pages/project/disks/DisksPage.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
apiQueryClient,
1414
diskCan,
1515
genName,
16+
getListQFn,
17+
queryClient,
1618
useApiMutation,
1719
useApiQueryClient,
1820
type Disk,
@@ -28,7 +30,7 @@ import { addToast } from '~/stores/toast'
2830
import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
2931
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
3032
import { Columns } from '~/table/columns/common'
31-
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
33+
import { useQueryTable } from '~/table/QueryTable'
3234
import { CreateLink } from '~/ui/lib/CreateButton'
3335
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3436
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
@@ -48,12 +50,12 @@ const EmptyState = () => (
4850
/>
4951
)
5052

53+
const diskList = (project: string) => getListQFn('diskList', { query: { project } })
54+
5155
DisksPage.loader = async ({ params }: LoaderFunctionArgs) => {
5256
const { project } = getProjectSelector(params)
5357
await Promise.all([
54-
apiQueryClient.prefetchQuery('diskList', {
55-
query: { project, limit: PAGE_SIZE },
56-
}),
58+
queryClient.prefetchQuery(diskList(project).optionsFn()),
5759

5860
// fetch instances and preload into RQ cache so fetches by ID in
5961
// InstanceLinkCell can be mostly instant yet gracefully fall back to
@@ -97,7 +99,6 @@ const staticCols = [
9799
export function DisksPage() {
98100
const queryClient = useApiQueryClient()
99101
const { project } = useProjectSelector()
100-
const { Table } = useQueryTable('diskList', { query: { project } })
101102

102103
const { mutateAsync: deleteDisk } = useApiMutation('diskDelete', {
103104
onSuccess(_data, variables) {
@@ -160,6 +161,11 @@ export function DisksPage() {
160161
)
161162

162163
const columns = useColsWithActions(staticCols, makeActions)
164+
const { table } = useQueryTable({
165+
query: diskList(project),
166+
columns,
167+
emptyState: <EmptyState />,
168+
})
163169

164170
return (
165171
<>
@@ -175,7 +181,7 @@ export function DisksPage() {
175181
<TableActions>
176182
<CreateLink to={pb.disksNew({ project })}>New Disk</CreateLink>
177183
</TableActions>
178-
<Table columns={columns} emptyState={<EmptyState />} />
184+
{table}
179185
<Outlet />
180186
</>
181187
)

app/pages/project/floating-ips/FloatingIpsPage.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import { Outlet, useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
1212

1313
import {
1414
apiQueryClient,
15+
getListQFn,
16+
queryClient,
1517
useApiMutation,
1618
useApiQueryClient,
17-
usePrefetchedApiQuery,
19+
usePrefetchedQuery,
1820
type FloatingIp,
1921
type Instance,
2022
} from '@oxide/api'
@@ -31,7 +33,7 @@ import { InstanceLinkCell } from '~/table/cells/InstanceLinkCell'
3133
import { IpPoolCell } from '~/table/cells/IpPoolCell'
3234
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
3335
import { Columns } from '~/table/columns/common'
34-
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
36+
import { useQueryTable } from '~/table/QueryTable'
3537
import { CopyableIp } from '~/ui/lib/CopyableIp'
3638
import { CreateLink } from '~/ui/lib/CreateButton'
3739
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
@@ -53,15 +55,15 @@ const EmptyState = () => (
5355
/>
5456
)
5557

58+
const fipList = (project: string) => getListQFn('floatingIpList', { query: { project } })
59+
const instanceList = (project: string) =>
60+
getListQFn('instanceList', { query: { project, limit: ALL_ISH } })
61+
5662
FloatingIpsPage.loader = async ({ params }: LoaderFunctionArgs) => {
5763
const { project } = getProjectSelector(params)
5864
await Promise.all([
59-
apiQueryClient.prefetchQuery('floatingIpList', {
60-
query: { project, limit: PAGE_SIZE },
61-
}),
62-
apiQueryClient.prefetchQuery('instanceList', {
63-
query: { project },
64-
}),
65+
queryClient.prefetchQuery(fipList(project).optionsFn()),
66+
queryClient.prefetchQuery(instanceList(project).optionsFn()),
6567
// fetch IP Pools and preload into RQ cache so fetches by ID in
6668
// IpPoolCell can be mostly instant yet gracefully fall back to
6769
// fetching individually if we don't fetch them all here
@@ -102,9 +104,7 @@ export function FloatingIpsPage() {
102104
const [floatingIpToModify, setFloatingIpToModify] = useState<FloatingIp | null>(null)
103105
const queryClient = useApiQueryClient()
104106
const { project } = useProjectSelector()
105-
const { data: instances } = usePrefetchedApiQuery('instanceList', {
106-
query: { project },
107-
})
107+
const { data: instances } = usePrefetchedQuery(instanceList(project).optionsFn())
108108
const navigate = useNavigate()
109109

110110
const { mutateAsync: floatingIpDetach } = useApiMutation('floatingIpDetach', {
@@ -202,9 +202,12 @@ export function FloatingIpsPage() {
202202
[deleteFloatingIp, floatingIpDetach, navigate, project, instances]
203203
)
204204

205-
const { Table } = useQueryTable('floatingIpList', { query: { project } })
206-
207205
const columns = useColsWithActions(staticCols, makeActions)
206+
const { table } = useQueryTable({
207+
query: fipList(project),
208+
columns,
209+
emptyState: <EmptyState />,
210+
})
208211

209212
return (
210213
<>
@@ -220,7 +223,7 @@ export function FloatingIpsPage() {
220223
<TableActions>
221224
<CreateLink to={pb.floatingIpsNew({ project })}>New Floating IP</CreateLink>
222225
</TableActions>
223-
<Table columns={columns} emptyState={<EmptyState />} />
226+
{table}
224227
<Outlet />
225228
{floatingIpToModify && (
226229
<AttachFloatingIpModal

app/pages/project/images/ImagesPage.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@ import { createColumnHelper } from '@tanstack/react-table'
99
import { useCallback, useMemo, useState } from 'react'
1010
import { Outlet, type LoaderFunctionArgs } from 'react-router-dom'
1111

12-
import { apiQueryClient, useApiMutation, useApiQueryClient, type Image } from '@oxide/api'
12+
import {
13+
apiQueryClient,
14+
getListQFn,
15+
queryClient,
16+
useApiMutation,
17+
useApiQueryClient,
18+
type Image,
19+
} from '@oxide/api'
1320
import { Images16Icon, Images24Icon } from '@oxide/design-system/icons/react'
1421

1522
import { DocsPopover } from '~/components/DocsPopover'
@@ -20,7 +27,7 @@ import { addToast } from '~/stores/toast'
2027
import { makeLinkCell } from '~/table/cells/LinkCell'
2128
import { getActionsCol, type MenuAction } from '~/table/columns/action-col'
2229
import { Columns } from '~/table/columns/common'
23-
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
30+
import { useQueryTable } from '~/table/QueryTable'
2431
import { CreateLink } from '~/ui/lib/CreateButton'
2532
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
2633
import { Message } from '~/ui/lib/Message'
@@ -42,25 +49,23 @@ const EmptyState = () => (
4249

4350
const colHelper = createColumnHelper<Image>()
4451

52+
const imageList = (project: string) => getListQFn('imageList', { query: { project } })
53+
4554
ImagesPage.loader = async ({ params }: LoaderFunctionArgs) => {
4655
const { project } = getProjectSelector(params)
47-
await apiQueryClient.prefetchQuery('imageList', {
48-
query: { project, limit: PAGE_SIZE },
49-
})
56+
await queryClient.prefetchQuery(imageList(project).optionsFn())
5057
return null
5158
}
5259

5360
export function ImagesPage() {
5461
const { project } = useProjectSelector()
55-
const { Table } = useQueryTable('imageList', { query: { project } })
56-
const queryClient = useApiQueryClient()
5762

5863
const [promoteImageName, setPromoteImageName] = useState<string | null>(null)
5964

6065
const { mutateAsync: deleteImage } = useApiMutation('imageDelete', {
6166
onSuccess(_data, variables) {
6267
addToast(<>Image <HL>{variables.path.image}</HL> deleted</>) // prettier-ignore
63-
queryClient.invalidateQueries('imageList')
68+
apiQueryClient.invalidateQueries('imageList')
6469
},
6570
})
6671

@@ -97,6 +102,12 @@ export function ImagesPage() {
97102
]
98103
}, [project, makeActions])
99104

105+
const { table } = useQueryTable({
106+
query: imageList(project),
107+
columns,
108+
emptyState: <EmptyState />,
109+
})
110+
100111
return (
101112
<>
102113
<PageHeader>
@@ -111,7 +122,7 @@ export function ImagesPage() {
111122
<TableActions>
112123
<CreateLink to={pb.projectImagesNew({ project })}>Upload image</CreateLink>
113124
</TableActions>
114-
<Table columns={columns} emptyState={<EmptyState />} />
125+
{table}
115126
{promoteImageName && (
116127
<PromoteImageModal
117128
onDismiss={() => setPromoteImageName(null)}

app/pages/project/instances/InstancesPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import { InstanceStateCell } from '~/table/cells/InstanceStateCell'
3030
import { makeLinkCell } from '~/table/cells/LinkCell'
3131
import { getActionsCol } from '~/table/columns/action-col'
3232
import { Columns } from '~/table/columns/common'
33-
import { useQueryTable } from '~/table/QueryTable2'
33+
import { useQueryTable } from '~/table/QueryTable'
3434
import { CreateLink } from '~/ui/lib/CreateButton'
3535
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
3636
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'

app/pages/project/snapshots/SnapshotsPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { confirmDelete } from '~/stores/confirm-delete'
2727
import { SkeletonCell } from '~/table/cells/EmptyCell'
2828
import { useColsWithActions, type MenuAction } from '~/table/columns/action-col'
2929
import { Columns } from '~/table/columns/common'
30-
import { useQueryTable } from '~/table/QueryTable2'
30+
import { useQueryTable } from '~/table/QueryTable'
3131
import { Badge } from '~/ui/lib/Badge'
3232
import { CreateLink } from '~/ui/lib/CreateButton'
3333
import { EmptyMessage } from '~/ui/lib/EmptyMessage'

0 commit comments

Comments
 (0)