Skip to content

Commit d83024e

Browse files
Fetch and prefill VPC list in NIC forms (#2977)
This prefetches the list of VPCs and selects the first one in the "select a VPC" form fields in NIC create form and in the custom NIC subsection of the instance create form. I accidentally picked up console/2051 instead of omicron/2051 and figured I would just knock it out. It's worth noting that the NIC form contains both a VPC and a Subnet, which is based on the VPC. At the moment, this implementation only pre-fills the VPC, though we could chain the calls and pre-fill the Subnet as well with just the first one in the list. I think it's better to let the user pick the Subnet, though? Open to thoughts. Closes #2051 --------- Co-authored-by: David Crespo <david-crespo@users.noreply.github.com> Co-authored-by: David Crespo <david.crespo@oxidecomputer.com>
1 parent 3f2fb9c commit d83024e

3 files changed

Lines changed: 18 additions & 20 deletions

File tree

app/forms/network-interface-create.tsx

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import { useQuery } from '@tanstack/react-query'
9-
import { useMemo } from 'react'
108
import { useForm } from 'react-hook-form'
119
import { match } from 'ts-pattern'
1210

1311
import {
1412
api,
1513
q,
14+
usePrefetchedQuery,
1615
type ApiError,
1716
type InstanceNetworkInterfaceCreate,
1817
type IpVersion,
@@ -28,6 +27,7 @@ import { SideModalForm } from '~/components/form/SideModalForm'
2827
import { useProjectSelector } from '~/hooks/use-params'
2928
import { FormDivider } from '~/ui/lib/Divider'
3029
import { SideModalFormDocs } from '~/ui/lib/ModalLinks'
30+
import { ALL_ISH } from '~/util/consts'
3131
import { docLinks } from '~/util/links'
3232

3333
type NetworkInterfaceFormValues = {
@@ -40,25 +40,13 @@ type NetworkInterfaceFormValues = {
4040
ipv6: string
4141
}
4242

43-
const defaultValues: NetworkInterfaceFormValues = {
44-
name: '',
45-
description: '',
46-
subnetName: '',
47-
vpcName: '',
48-
ipStackType: 'dual_stack',
49-
ipv4: '',
50-
ipv6: '',
51-
}
52-
53-
// Helper to build IP assignment from string
5443
function buildIpAssignment(
5544
ipString: string
5645
): { type: 'auto' } | { type: 'explicit'; value: string } {
5746
const trimmed = ipString.trim()
5847
return trimmed ? { type: 'explicit', value: trimmed } : { type: 'auto' }
5948
}
6049

61-
// Helper to build a single IP stack (v4 or v6)
6250
function buildIpStack(ipString: string) {
6351
return {
6452
ip: buildIpAssignment(ipString),
@@ -84,10 +72,18 @@ export function CreateNetworkInterfaceForm({
8472
submitError = null,
8573
}: CreateNetworkInterfaceFormProps) {
8674
const projectSelector = useProjectSelector()
87-
88-
const { data: vpcsData } = useQuery(q(api.vpcList, { query: projectSelector }))
89-
const vpcs = useMemo(() => vpcsData?.items || [], [vpcsData])
90-
75+
const {
76+
data: { items: vpcs },
77+
} = usePrefetchedQuery(q(api.vpcList, { query: { ...projectSelector, limit: ALL_ISH } }))
78+
const defaultValues: NetworkInterfaceFormValues = {
79+
name: '',
80+
description: '',
81+
subnetName: '',
82+
vpcName: vpcs.length === 1 ? vpcs[0].name : '',
83+
ipStackType: 'dual_stack',
84+
ipv4: '',
85+
ipv6: '',
86+
}
9187
const form = useForm({ defaultValues })
9288
const ipStackType = form.watch('ipStackType')
9389

app/pages/project/instances/NetworkingTab.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ export async function clientLoader({ params }: LoaderFunctionArgs) {
172172
queryClient.setQueryData(queryKey, { type: 'success', data: pool })
173173
}
174174
}),
175+
// Fetch VPCs for Add NIC form
176+
queryClient.fetchQuery(q(api.vpcList, { query: { project, limit: ALL_ISH } })),
175177
])
176178
return null
177179
}

test/e2e/network-interface-create.e2e.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ test('can create a NIC with a specified IP address', async ({ page }) => {
1818

1919
// fill out the form
2020
await page.getByLabel('Name').fill('nic-1')
21-
await page.getByLabel('VPC', { exact: true }).click()
22-
await page.getByRole('option', { name: 'mock-vpc' }).click()
21+
// VPC is preselected because the project has exactly one
22+
await expect(page.getByLabel('VPC', { exact: true })).toContainText('mock-vpc')
2323
await page.getByRole('dialog').getByRole('button', { name: 'VPC subnet' }).click()
2424
await page.getByRole('option', { name: 'mock-subnet', exact: true }).click()
2525

0 commit comments

Comments
 (0)