Skip to content

Commit 3e40ff7

Browse files
authored
Fix instance polling bug by switching to new useQueryTable (#2571)
* 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
1 parent 034ca87 commit 3e40ff7

File tree

1 file changed

+67
-60
lines changed

1 file changed

+67
-60
lines changed

app/pages/project/instances/InstancesPage.tsx

Lines changed: 67 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { type UseQueryOptions } from '@tanstack/react-query'
89
import { createColumnHelper } from '@tanstack/react-table'
910
import { filesize } from 'filesize'
1011
import { useMemo, useRef } from 'react'
1112
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'
1213

13-
import { apiQueryClient, usePrefetchedApiQuery, type Instance } from '@oxide/api'
14+
import {
15+
apiQueryClient,
16+
getListQFn,
17+
queryClient,
18+
type ApiError,
19+
type Instance,
20+
type InstanceResultsPage,
21+
} from '@oxide/api'
1422
import { Instances24Icon } from '@oxide/design-system/icons/react'
1523

1624
import { instanceTransitioning } from '~/api/util'
@@ -22,7 +30,7 @@ import { InstanceStateCell } from '~/table/cells/InstanceStateCell'
2230
import { makeLinkCell } from '~/table/cells/LinkCell'
2331
import { getActionsCol } from '~/table/columns/action-col'
2432
import { Columns } from '~/table/columns/common'
25-
import { PAGE_SIZE, useQueryTable } from '~/table/QueryTable'
33+
import { useQueryTable } from '~/table/QueryTable2'
2634
import { CreateLink } from '~/ui/lib/CreateButton'
2735
import { EmptyMessage } from '~/ui/lib/EmptyMessage'
2836
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
@@ -46,11 +54,16 @@ const EmptyState = () => (
4654

4755
const colHelper = createColumnHelper<Instance>()
4856

57+
const instanceList = (
58+
project: string,
59+
// kinda gnarly, but we need refetchInterval in the component but not in the loader.
60+
// pick refetchInterval to avoid annoying type conflicts on the full object
61+
options?: Pick<UseQueryOptions<InstanceResultsPage, ApiError>, 'refetchInterval'>
62+
) => getListQFn('instanceList', { query: { project } }, options)
63+
4964
InstancesPage.loader = async ({ params }: LoaderFunctionArgs) => {
5065
const { project } = getProjectSelector(params)
51-
await apiQueryClient.prefetchQuery('instanceList', {
52-
query: { project, limit: PAGE_SIZE },
53-
})
66+
await queryClient.prefetchQuery(instanceList(project).optionsFn())
5467
return null
5568
}
5669

@@ -69,6 +82,46 @@ export function InstancesPage() {
6982
{ onSuccess: refetchInstances, onDelete: refetchInstances }
7083
)
7184

85+
const columns = useMemo(
86+
() => [
87+
colHelper.accessor('name', {
88+
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
89+
}),
90+
colHelper.accessor('ncpus', {
91+
header: 'CPU',
92+
cell: (info) => (
93+
<>
94+
{info.getValue()} <span className="ml-1 text-quaternary">vCPU</span>
95+
</>
96+
),
97+
}),
98+
colHelper.accessor('memory', {
99+
header: 'Memory',
100+
cell: (info) => {
101+
const memory = filesize(info.getValue(), { output: 'object', base: 2 })
102+
return (
103+
<>
104+
{memory.value} <span className="ml-1 text-quaternary">{memory.unit}</span>
105+
</>
106+
)
107+
},
108+
}),
109+
colHelper.accessor(
110+
(i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
111+
{
112+
header: 'state',
113+
cell: (info) => <InstanceStateCell value={info.getValue()} />,
114+
}
115+
),
116+
colHelper.accessor('timeCreated', Columns.timeCreated),
117+
getActionsCol((instance: Instance) => [
118+
...makeButtonActions(instance),
119+
...makeMenuActions(instance),
120+
]),
121+
],
122+
[project, makeButtonActions, makeMenuActions]
123+
)
124+
72125
// this is a whole thing. sit down.
73126

74127
// We initialize this set as empty because we don't have the instances on hand
@@ -77,10 +130,8 @@ export function InstancesPage() {
77130
const transitioningInstances = useRef<Set<string>>(new Set())
78131
const pollingStartTime = useRef<number>(Date.now())
79132

80-
const { data: instances, dataUpdatedAt } = usePrefetchedApiQuery(
81-
'instanceList',
82-
{ query: { project, limit: PAGE_SIZE } },
83-
{
133+
const { table, query } = useQueryTable({
134+
query: instanceList(project, {
84135
// The point of all this is to poll quickly for a certain amount of time
85136
// after some instance in the current page enters a transitional state
86137
// like starting or stopping. After that, it will keep polling, but more
@@ -122,8 +173,12 @@ export function InstancesPage() {
122173
? POLL_INTERVAL_FAST
123174
: POLL_INTERVAL_SLOW
124175
},
125-
}
126-
)
176+
}),
177+
columns,
178+
emptyState: <EmptyState />,
179+
})
180+
181+
const { data: instances, dataUpdatedAt } = query
127182

128183
const navigate = useNavigate()
129184
useQuickActions(
@@ -143,54 +198,6 @@ export function InstancesPage() {
143198
)
144199
)
145200

146-
const { Table } = useQueryTable(
147-
'instanceList',
148-
{ query: { project } },
149-
{ placeholderData: (x) => x }
150-
)
151-
152-
const columns = useMemo(
153-
() => [
154-
colHelper.accessor('name', {
155-
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
156-
}),
157-
colHelper.accessor('ncpus', {
158-
header: 'CPU',
159-
cell: (info) => (
160-
<>
161-
{info.getValue()} <span className="ml-1 text-quaternary">vCPU</span>
162-
</>
163-
),
164-
}),
165-
colHelper.accessor('memory', {
166-
header: 'Memory',
167-
cell: (info) => {
168-
const memory = filesize(info.getValue(), { output: 'object', base: 2 })
169-
return (
170-
<>
171-
{memory.value} <span className="ml-1 text-quaternary">{memory.unit}</span>
172-
</>
173-
)
174-
},
175-
}),
176-
colHelper.accessor(
177-
(i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
178-
{
179-
header: 'state',
180-
cell: (info) => <InstanceStateCell value={info.getValue()} />,
181-
}
182-
),
183-
colHelper.accessor('timeCreated', Columns.timeCreated),
184-
getActionsCol((instance: Instance) => [
185-
...makeButtonActions(instance),
186-
...makeMenuActions(instance),
187-
]),
188-
],
189-
[project, makeButtonActions, makeMenuActions]
190-
)
191-
192-
if (!instances) return null
193-
194201
return (
195202
<>
196203
<PageHeader>
@@ -213,7 +220,7 @@ export function InstancesPage() {
213220
</div>
214221
<CreateLink to={pb.instancesNew({ project })}>New Instance</CreateLink>
215222
</TableActions>
216-
<Table columns={columns} emptyState={<EmptyState />} />
223+
{table}
217224
</>
218225
)
219226
}

0 commit comments

Comments
 (0)