Skip to content

Commit b9013a3

Browse files
Add user_data field to instance create (#1898)
* Block out `user_data` field * Update `instance-create` e2e test * Add help text and description * Switch description to tooltipText and allow nodes for helpText * Switch helpText to description * Add user data description with examples * Remove async from `FullPageForm` * simplify links object, don't bother falling back to GET if HEAD fails * remove fragment from url --------- Co-authored-by: David Crespo <[email protected]>
1 parent fb9e9ca commit b9013a3

22 files changed

+403
-153
lines changed

app/components/TimeAgo.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ import { timeAgoAbbr } from 'app/util/date'
1414

1515
export const TimeAgo = ({
1616
datetime,
17-
description,
17+
tooltipText,
1818
placement = 'top',
1919
}: {
2020
datetime: Date
21-
description?: string
21+
tooltipText?: string
2222
placement?: Placement
2323
}): JSX.Element => {
2424
const content = (
2525
<div className="flex flex-col">
26-
<span className="text-tertiary">{description}</span>
26+
<span className="text-tertiary">{tooltipText}</span>
2727
<span>{format(datetime, 'MMM d, yyyy p')}</span>
2828
</div>
2929
)

app/components/form/fields/FileField.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ export function FileField<
1818
id,
1919
name,
2020
label,
21-
description,
21+
tooltipText,
2222
control,
2323
required = false,
2424
accept,
25-
helpText,
25+
description,
2626
}: {
2727
id: string
2828
name: TName
2929
label: string
30-
description?: string
30+
tooltipText?: string
3131
control: Control<TFieldValues>
3232
required?: boolean
3333
accept?: string
34-
helpText?: string
34+
description?: string | React.ReactNode
3535
}) {
3636
return (
3737
<Controller
@@ -44,12 +44,14 @@ export function FileField<
4444
<FieldLabel
4545
id={`${id}-label`}
4646
htmlFor={id}
47-
tip={description}
47+
tip={tooltipText}
4848
optional={!required}
4949
>
5050
{label}
5151
</FieldLabel>
52-
{helpText && <TextInputHint id={`${id}-help-text`}>{helpText}</TextInputHint>}
52+
{description && (
53+
<TextInputHint id={`${id}-help-text`}>{description}</TextInputHint>
54+
)}
5355
</div>
5456
<FileInput id={id} className="mt-2" accept={accept} {...rest} error={!!error} />
5557
<ErrorMessage error={error} label={label} />

app/components/form/fields/ListboxField.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ export type ListboxFieldProps<
2222
className?: string
2323
label?: string
2424
required?: boolean
25-
helpText?: string
26-
description?: string
25+
description?: string | React.ReactNode | React.ReactNode
26+
tooltipText?: string
2727
control: Control<TFieldValues>
2828
disabled?: boolean
2929
items: ListboxItem[]
@@ -41,8 +41,8 @@ export function ListboxField<
4141
label = capitalize(name),
4242
disabled,
4343
required,
44+
tooltipText,
4445
description,
45-
helpText,
4646
className,
4747
control,
4848
onChange,
@@ -59,9 +59,9 @@ export function ListboxField<
5959
render={({ field, fieldState: { error } }) => (
6060
<>
6161
<Listbox
62-
helpText={helpText}
63-
label={label}
6462
description={description}
63+
label={label}
64+
tooltipText={tooltipText}
6565
required={required}
6666
placeholder={placeholder}
6767
selected={field.value || null}

app/components/form/fields/NetworkInterfaceField.tsx

Lines changed: 81 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -43,88 +43,90 @@ export function NetworkInterfaceField({
4343
return (
4444
<div className="max-w-lg space-y-2">
4545
<FieldLabel id="network-interface-type-label">Network interface</FieldLabel>
46-
<RadioGroup
47-
aria-labelledby="network-interface-type-label"
48-
name="networkInterfaceType"
49-
column
50-
className="pt-1"
51-
defaultChecked={value.type}
52-
onChange={(event) => {
53-
const newType = event.target.value as InstanceNetworkInterfaceAttachment['type']
46+
<div className="space-y-4">
47+
<RadioGroup
48+
aria-labelledby="network-interface-type-label"
49+
name="networkInterfaceType"
50+
column
51+
className="pt-1"
52+
defaultChecked={value.type}
53+
onChange={(event) => {
54+
const newType = event.target.value as InstanceNetworkInterfaceAttachment['type']
5455

55-
if (value.type === 'create') {
56-
setOldParams(value.params)
57-
}
56+
if (value.type === 'create') {
57+
setOldParams(value.params)
58+
}
5859

59-
newType === 'create'
60-
? onChange({ type: newType, params: oldParams })
61-
: onChange({ type: newType })
62-
}}
63-
disabled={disabled}
64-
>
65-
<Radio value="none">None</Radio>
66-
<Radio value="default">Default</Radio>
67-
<Radio value="create">Custom</Radio>
68-
</RadioGroup>
69-
{value.type === 'create' && (
70-
<>
71-
{value.params.length > 0 && (
72-
<MiniTable.Table className="mb-4">
73-
<MiniTable.Header>
74-
<MiniTable.HeadCell>Name</MiniTable.HeadCell>
75-
<MiniTable.HeadCell>VPC</MiniTable.HeadCell>
76-
<MiniTable.HeadCell>Subnet</MiniTable.HeadCell>
77-
{/* For remove button */}
78-
<MiniTable.HeadCell className="w-12" />
79-
</MiniTable.Header>
80-
<MiniTable.Body>
81-
{value.params.map((item, index) => (
82-
<MiniTable.Row
83-
tabIndex={0}
84-
aria-rowindex={index + 1}
85-
aria-label={`Name: ${item.name}, Vpc: ${item.vpcName}, Subnet: ${item.subnetName}`}
86-
key={item.name}
87-
>
88-
<MiniTable.Cell>{item.name}</MiniTable.Cell>
89-
<MiniTable.Cell>{item.vpcName}</MiniTable.Cell>
90-
<MiniTable.Cell>{item.subnetName}</MiniTable.Cell>
91-
<MiniTable.Cell>
92-
<button
93-
onClick={() =>
94-
onChange({
95-
type: 'create',
96-
params: value.params.filter((i) => i.name !== item.name),
97-
})
98-
}
99-
>
100-
<Error16Icon title={`remove ${item.name}`} />
101-
</button>
102-
</MiniTable.Cell>
103-
</MiniTable.Row>
104-
))}
105-
</MiniTable.Body>
106-
</MiniTable.Table>
107-
)}
60+
newType === 'create'
61+
? onChange({ type: newType, params: oldParams })
62+
: onChange({ type: newType })
63+
}}
64+
disabled={disabled}
65+
>
66+
<Radio value="none">None</Radio>
67+
<Radio value="default">Default</Radio>
68+
<Radio value="create">Custom</Radio>
69+
</RadioGroup>
70+
{value.type === 'create' && (
71+
<>
72+
{value.params.length > 0 && (
73+
<MiniTable.Table className="pt-2">
74+
<MiniTable.Header>
75+
<MiniTable.HeadCell>Name</MiniTable.HeadCell>
76+
<MiniTable.HeadCell>VPC</MiniTable.HeadCell>
77+
<MiniTable.HeadCell>Subnet</MiniTable.HeadCell>
78+
{/* For remove button */}
79+
<MiniTable.HeadCell className="w-12" />
80+
</MiniTable.Header>
81+
<MiniTable.Body>
82+
{value.params.map((item, index) => (
83+
<MiniTable.Row
84+
tabIndex={0}
85+
aria-rowindex={index + 1}
86+
aria-label={`Name: ${item.name}, Vpc: ${item.vpcName}, Subnet: ${item.subnetName}`}
87+
key={item.name}
88+
>
89+
<MiniTable.Cell>{item.name}</MiniTable.Cell>
90+
<MiniTable.Cell>{item.vpcName}</MiniTable.Cell>
91+
<MiniTable.Cell>{item.subnetName}</MiniTable.Cell>
92+
<MiniTable.Cell>
93+
<button
94+
onClick={() =>
95+
onChange({
96+
type: 'create',
97+
params: value.params.filter((i) => i.name !== item.name),
98+
})
99+
}
100+
>
101+
<Error16Icon title={`remove ${item.name}`} />
102+
</button>
103+
</MiniTable.Cell>
104+
</MiniTable.Row>
105+
))}
106+
</MiniTable.Body>
107+
</MiniTable.Table>
108+
)}
108109

109-
{showForm && (
110-
<CreateNetworkInterfaceForm
111-
onSubmit={(networkInterface) => {
112-
onChange({
113-
type: 'create',
114-
params: [...value.params, networkInterface],
115-
})
116-
setShowForm(false)
117-
}}
118-
onDismiss={() => setShowForm(false)}
119-
/>
120-
)}
121-
<div className="space-x-3">
122-
<Button size="sm" onClick={() => setShowForm(true)}>
123-
Add network interface
124-
</Button>
125-
</div>
126-
</>
127-
)}
110+
{showForm && (
111+
<CreateNetworkInterfaceForm
112+
onSubmit={(networkInterface) => {
113+
onChange({
114+
type: 'create',
115+
params: [...value.params, networkInterface],
116+
})
117+
setShowForm(false)
118+
}}
119+
onDismiss={() => setShowForm(false)}
120+
/>
121+
)}
122+
<div className="space-x-3">
123+
<Button size="sm" onClick={() => setShowForm(true)}>
124+
Add network interface
125+
</Button>
126+
</div>
127+
</>
128+
)}
129+
</div>
128130
</div>
129131
)
130132
}

app/components/form/fields/NumberField.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ export function NumberField<
2323
name,
2424
label = capitalize(name),
2525
units,
26+
tooltipText,
2627
description,
27-
helpText,
2828
required,
2929
...props
3030
}: Omit<TextFieldProps<TFieldValues, TName>, 'id'>) {
@@ -33,12 +33,12 @@ export function NumberField<
3333
return (
3434
<div className="max-w-lg">
3535
<div className="mb-2">
36-
<FieldLabel htmlFor={id} id={`${id}-label`} tip={description} optional={!required}>
36+
<FieldLabel htmlFor={id} id={`${id}-label`} tip={tooltipText} optional={!required}>
3737
{label} {units && <span className="ml-1 text-secondary">({units})</span>}
3838
</FieldLabel>
39-
{helpText && (
39+
{description && (
4040
<TextInputHint id={`${id}-help-text`} className="mb-2">
41-
{helpText}
41+
{description}
4242
</TextInputHint>
4343
)}
4444
</div>
@@ -65,7 +65,7 @@ export const NumberFieldInner = <
6565
label = capitalize(name),
6666
validate,
6767
control,
68-
description,
68+
tooltipText,
6969
required,
7070
id: idProp,
7171
disabled,
@@ -85,9 +85,9 @@ export const NumberFieldInner = <
8585
id={id}
8686
error={!!error}
8787
aria-labelledby={cn(`${id}-label`, {
88-
[`${id}-help-text`]: !!description,
88+
[`${id}-help-text`]: !!tooltipText,
8989
})}
90-
aria-describedby={description ? `${id}-label-tip` : undefined}
90+
aria-describedby={tooltipText ? `${id}-label-tip` : undefined}
9191
isDisabled={disabled}
9292
{...field}
9393
/>

0 commit comments

Comments
 (0)