Skip to content

Commit 7c6f53d

Browse files
Handle empty IP Address field in Network Interface create flow (#1854)
* Add onBlur to better handle empty IP Address field * Add test for network-interface-create flow * Use transform rather than onChange * More succinct prop description * small tweaks --------- Co-authored-by: David Crespo <[email protected]>
1 parent b9013a3 commit 7c6f53d

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

app/components/form/fields/TextField.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export interface TextFieldProps<
5656
units?: string
5757
validate?: Validate<FieldPathValue<TFieldValues, TName>, TFieldValues>
5858
control: Control<TFieldValues>
59+
/** Alters the value of the input during the field's onChange event. */
60+
transform?: (value: string) => FieldPathValue<TFieldValues, TName>
5961
}
6062

6163
export function TextField<
@@ -119,6 +121,7 @@ export const TextFieldInner = <
119121
tooltipText,
120122
required,
121123
id: idProp,
124+
transform,
122125
...props
123126
}: TextFieldProps<TFieldValues, TName> & UITextAreaProps) => {
124127
const generatedId = useId()
@@ -144,6 +147,10 @@ export const TextFieldInner = <
144147
// for the calling code despite the actual input value necessarily
145148
// being a string.
146149
onChange={(e) => {
150+
if (transform) {
151+
onChange(transform(e.target.value))
152+
return
153+
}
147154
if (type === 'number') {
148155
if (e.target.value.trim() === '') {
149156
onChange(0)

app/forms/network-interface-create.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ export default function CreateNetworkInterfaceForm({
8080
required
8181
control={form.control}
8282
/>
83-
<TextField name="ip" label="IP Address" control={form.control} />
83+
<TextField
84+
name="ip"
85+
label="IP Address"
86+
control={form.control}
87+
transform={(ip) => (ip.trim() === '' ? undefined : ip)}
88+
/>
8489
</SideModalForm>
8590
)
8691
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
5+
*
6+
* Copyright Oxide Computer Company
7+
*/
8+
import { test } from '@playwright/test'
9+
10+
import { expect, expectRowVisible } from './utils'
11+
12+
test('can create a NIC with a specified IP address', async ({ page }) => {
13+
// go to an instance’s Network Interfaces page
14+
await page.goto('/projects/mock-project/instances/db1/network-interfaces')
15+
16+
// stop the instance
17+
await page.getByRole('button', { name: 'Instance actions' }).click()
18+
await page.getByRole('menuitem', { name: 'Stop' }).click()
19+
20+
// open the add network interface side modal
21+
await page.getByRole('button', { name: 'Add network interface' }).click()
22+
23+
// fill out the form
24+
await page.getByLabel('Name').fill('nic-1')
25+
await page.getByRole('button', { name: 'VPC' }).click()
26+
await page.getByRole('option', { name: 'mock-vpc' }).click()
27+
await page.getByRole('button', { name: 'Subnet' }).click()
28+
await page.getByRole('option', { name: 'mock-subnet' }).click()
29+
await page.getByLabel('IP Address').fill('1.2.3.4')
30+
31+
const sidebar = page.getByRole('dialog', { name: 'Add network interface' })
32+
33+
// test that the form can be submitted and a new network interface is created
34+
await sidebar.getByRole('button', { name: 'Add network interface' }).click()
35+
await expect(sidebar).toBeHidden()
36+
37+
await expectRowVisible(page.getByRole('table'), { name: 'nic-1', ip: '1.2.3.4' })
38+
})
39+
40+
test('can create a NIC with a blank IP address', async ({ page }) => {
41+
// go to an instance’s Network Interfaces page
42+
await page.goto('/projects/mock-project/instances/db1/network-interfaces')
43+
44+
// stop the instance
45+
await page.getByRole('button', { name: 'Instance actions' }).click()
46+
await page.getByRole('menuitem', { name: 'Stop' }).click()
47+
48+
// open the add network interface side modal
49+
await page.getByRole('button', { name: 'Add network interface' }).click()
50+
51+
// fill out the form
52+
await page.getByLabel('Name').fill('nic-2')
53+
await page.getByRole('button', { name: 'VPC' }).click()
54+
await page.getByRole('option', { name: 'mock-vpc' }).click()
55+
await page.getByRole('button', { name: 'Subnet' }).click()
56+
await page.getByRole('option', { name: 'mock-subnet' }).click()
57+
58+
// make sure the IP address field has a non-conforming bit of text in it
59+
await page.getByLabel('IP Address').fill('x')
60+
61+
// try to submit it
62+
const sidebar = page.getByRole('dialog', { name: 'Add network interface' })
63+
await sidebar.getByRole('button', { name: 'Add network interface' }).click()
64+
65+
// it should error out
66+
// todo: improve error message from API
67+
await expect(sidebar.getByText('Unknown server error')).toBeVisible()
68+
69+
// make sure the IP address field has spaces in it
70+
await page.getByLabel('IP Address').fill(' ')
71+
72+
// test that the form can be submitted and a new network interface is created
73+
await sidebar.getByRole('button', { name: 'Add network interface' }).click()
74+
await expect(sidebar).toBeHidden()
75+
76+
// ip address is auto-assigned
77+
await expectRowVisible(page.getByRole('table'), { name: 'nic-2', ip: '123.45.68.8' })
78+
})

0 commit comments

Comments
 (0)