Skip to content

Commit

Permalink
Fix instance polling bug by switching to new useQueryTable (#2571)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
david-crespo authored Nov 23, 2024
1 parent 034ca87 commit 3e40ff7
Showing 1 changed file with 67 additions and 60 deletions.
127 changes: 67 additions & 60 deletions app/pages/project/instances/InstancesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@
*
* Copyright Oxide Computer Company
*/
import { type UseQueryOptions } from '@tanstack/react-query'
import { createColumnHelper } from '@tanstack/react-table'
import { filesize } from 'filesize'
import { useMemo, useRef } from 'react'
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

import { apiQueryClient, usePrefetchedApiQuery, type Instance } from '@oxide/api'
import {
apiQueryClient,
getListQFn,
queryClient,
type ApiError,
type Instance,
type InstanceResultsPage,
} from '@oxide/api'
import { Instances24Icon } from '@oxide/design-system/icons/react'

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

const colHelper = createColumnHelper<Instance>()

const instanceList = (
project: string,
// kinda gnarly, but we need refetchInterval in the component but not in the loader.
// pick refetchInterval to avoid annoying type conflicts on the full object
options?: Pick<UseQueryOptions<InstanceResultsPage, ApiError>, 'refetchInterval'>
) => getListQFn('instanceList', { query: { project } }, options)

InstancesPage.loader = async ({ params }: LoaderFunctionArgs) => {
const { project } = getProjectSelector(params)
await apiQueryClient.prefetchQuery('instanceList', {
query: { project, limit: PAGE_SIZE },
})
await queryClient.prefetchQuery(instanceList(project).optionsFn())
return null
}

Expand All @@ -69,6 +82,46 @@ export function InstancesPage() {
{ onSuccess: refetchInstances, onDelete: refetchInstances }
)

const columns = useMemo(
() => [
colHelper.accessor('name', {
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
}),
colHelper.accessor('ncpus', {
header: 'CPU',
cell: (info) => (
<>
{info.getValue()} <span className="ml-1 text-quaternary">vCPU</span>
</>
),
}),
colHelper.accessor('memory', {
header: 'Memory',
cell: (info) => {
const memory = filesize(info.getValue(), { output: 'object', base: 2 })
return (
<>
{memory.value} <span className="ml-1 text-quaternary">{memory.unit}</span>
</>
)
},
}),
colHelper.accessor(
(i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
{
header: 'state',
cell: (info) => <InstanceStateCell value={info.getValue()} />,
}
),
colHelper.accessor('timeCreated', Columns.timeCreated),
getActionsCol((instance: Instance) => [
...makeButtonActions(instance),
...makeMenuActions(instance),
]),
],
[project, makeButtonActions, makeMenuActions]
)

// this is a whole thing. sit down.

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

const { data: instances, dataUpdatedAt } = usePrefetchedApiQuery(
'instanceList',
{ query: { project, limit: PAGE_SIZE } },
{
const { table, query } = useQueryTable({
query: instanceList(project, {
// The point of all this is to poll quickly for a certain amount of time
// after some instance in the current page enters a transitional state
// like starting or stopping. After that, it will keep polling, but more
Expand Down Expand Up @@ -122,8 +173,12 @@ export function InstancesPage() {
? POLL_INTERVAL_FAST
: POLL_INTERVAL_SLOW
},
}
)
}),
columns,
emptyState: <EmptyState />,
})

const { data: instances, dataUpdatedAt } = query

const navigate = useNavigate()
useQuickActions(
Expand All @@ -143,54 +198,6 @@ export function InstancesPage() {
)
)

const { Table } = useQueryTable(
'instanceList',
{ query: { project } },
{ placeholderData: (x) => x }
)

const columns = useMemo(
() => [
colHelper.accessor('name', {
cell: makeLinkCell((instance) => pb.instance({ project, instance })),
}),
colHelper.accessor('ncpus', {
header: 'CPU',
cell: (info) => (
<>
{info.getValue()} <span className="ml-1 text-quaternary">vCPU</span>
</>
),
}),
colHelper.accessor('memory', {
header: 'Memory',
cell: (info) => {
const memory = filesize(info.getValue(), { output: 'object', base: 2 })
return (
<>
{memory.value} <span className="ml-1 text-quaternary">{memory.unit}</span>
</>
)
},
}),
colHelper.accessor(
(i) => ({ runState: i.runState, timeRunStateUpdated: i.timeRunStateUpdated }),
{
header: 'state',
cell: (info) => <InstanceStateCell value={info.getValue()} />,
}
),
colHelper.accessor('timeCreated', Columns.timeCreated),
getActionsCol((instance: Instance) => [
...makeButtonActions(instance),
...makeMenuActions(instance),
]),
],
[project, makeButtonActions, makeMenuActions]
)

if (!instances) return null

return (
<>
<PageHeader>
Expand All @@ -213,7 +220,7 @@ export function InstancesPage() {
</div>
<CreateLink to={pb.instancesNew({ project })}>New Instance</CreateLink>
</TableActions>
<Table columns={columns} emptyState={<EmptyState />} />
{table}
</>
)
}

0 comments on commit 3e40ff7

Please sign in to comment.