Skip to content

Commit 4d226cd

Browse files
Update disk size from image (and snapshots) (#1891)
* Update disk size from image (and snapshots) * Fix test * Wow thats embarassing * CI * Update filesize import
1 parent 7490104 commit 4d226cd

File tree

3 files changed

+57
-9
lines changed

3 files changed

+57
-9
lines changed

app/forms/disk-create.tsx

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Copyright Oxide Computer Company
77
*/
88
import { format } from 'date-fns'
9+
import { filesize } from 'filesize'
910
import { useMemo } from 'react'
1011
import { useController, type Control } from 'react-hook-form'
1112
import { useNavigate, type NavigateFunction } from 'react-router-dom'
@@ -94,9 +95,24 @@ export function CreateDiskSideModalForm({
9495
)
9596
const areImagesLoading = projectImages.isPending || siloImages.isPending
9697

97-
const selectedImageId = form.watch('diskSource.imageId')
98-
const selectedImageSize = images.find((image) => image.id === selectedImageId)?.size
99-
const imageSizeGiB = selectedImageSize ? bytesToGiB(selectedImageSize) : undefined
98+
const snapshotsQuery = useApiQuery('snapshotList', { query: projectSelector })
99+
const snapshots = snapshotsQuery.data?.items || []
100+
101+
// validate disk source size
102+
const diskSource = form.watch('diskSource').type
103+
104+
let validateSizeGiB: number | undefined = undefined
105+
if (diskSource === 'snapshot') {
106+
const selectedSnapshotId = form.watch('diskSource.snapshotId')
107+
const selectedSnapshotSize = snapshots.find(
108+
(snapshot) => snapshot.id === selectedSnapshotId
109+
)?.size
110+
validateSizeGiB = selectedSnapshotSize ? bytesToGiB(selectedSnapshotSize) : undefined
111+
} else if (diskSource === 'image') {
112+
const selectedImageId = form.watch('diskSource.imageId')
113+
const selectedImageSize = images.find((image) => image.id === selectedImageId)?.size
114+
validateSizeGiB = selectedImageSize ? bytesToGiB(selectedImageSize) : undefined
115+
}
100116

101117
return (
102118
<SideModalForm
@@ -123,8 +139,8 @@ export function CreateDiskSideModalForm({
123139
name="size"
124140
control={form.control}
125141
validate={(diskSizeGiB: number) => {
126-
if (imageSizeGiB && diskSizeGiB < imageSizeGiB) {
127-
return `Must be as large as selected image (min. ${imageSizeGiB} GiB)`
142+
if (validateSizeGiB && diskSizeGiB < validateSizeGiB) {
143+
return `Must be as large as selected ${diskSource} (min. ${validateSizeGiB} GiB)`
128144
}
129145
}}
130146
/>
@@ -144,6 +160,7 @@ const DiskSourceField = ({
144160
const {
145161
field: { value, onChange },
146162
} = useController({ control, name: 'diskSource' })
163+
const diskSizeField = useController({ control, name: 'size' }).field
147164

148165
return (
149166
<>
@@ -191,6 +208,14 @@ const DiskSourceField = ({
191208
isLoading={areImagesLoading}
192209
items={images.map((i) => toListboxItem(i, true))}
193210
required
211+
onChange={(id) => {
212+
const image = images.find((i) => i.id === id)! // if it's selected, it must be present
213+
const imageSizeGiB = image.size / GiB
214+
if (diskSizeField.value < imageSizeGiB) {
215+
const nearest10 = Math.ceil(imageSizeGiB / 10) * 10
216+
diskSizeField.onChange(nearest10)
217+
}
218+
}}
194219
/>
195220
)}
196221

@@ -218,6 +243,7 @@ const SnapshotSelectField = ({ control }: { control: Control<DiskCreate> }) => {
218243
const snapshotsQuery = useApiQuery('snapshotList', { query: projectSelector })
219244

220245
const snapshots = snapshotsQuery.data?.items || []
246+
const diskSizeField = useController({ control, name: 'size' }).field
221247

222248
return (
223249
<ListboxField
@@ -226,22 +252,33 @@ const SnapshotSelectField = ({ control }: { control: Control<DiskCreate> }) => {
226252
label="Source snapshot"
227253
placeholder="Select a snapshot"
228254
items={snapshots.map((i) => {
255+
const formattedSize = filesize(i.size, { base: 2, output: 'object' })
229256
return {
230257
value: i.id,
231258
labelString: `${i.name}`,
232259
label: (
233260
<>
234261
<div>{i.name}</div>
235-
<div className="text-secondary">
262+
<div className="text-tertiary selected:text-accent-secondary">
236263
Created on {format(i.timeCreated, 'MMM d, yyyy')}
237-
<DiskNameFromId disk={i.diskId} />
264+
<DiskNameFromId disk={i.diskId} />{' '}
265+
<span className="mx-1 text-quinary selected:text-accent-disabled">/</span>{' '}
266+
{formattedSize.value} {formattedSize.unit}
238267
</div>
239268
</>
240269
),
241270
}
242271
})}
243272
isLoading={snapshotsQuery.isPending}
244273
required
274+
onChange={(id) => {
275+
const snapshot = snapshots.find((i) => i.id === id)! // if it's selected, it must be present
276+
const snapshotSizeGiB = snapshot.size / GiB
277+
if (diskSizeField.value < snapshotSizeGiB) {
278+
const nearest10 = Math.ceil(snapshotSizeGiB / 10) * 10
279+
diskSizeField.onChange(nearest10)
280+
}
281+
}}
245282
/>
246283
)
247284
}

mock-api/snapshot.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,24 @@ export const snapshots: Json<Snapshot>[] = [
7474
disk_id: 'a6f61e3f-25c1-49b0-a013-ac6a2d98a948',
7575
state: 'ready',
7676
},
77+
{
78+
id: '7fc6ca11-452e-d3e4-9e1c-752ff615abea',
79+
name: 'snapshot-heavy',
80+
description: '',
81+
project_id: project.id,
82+
time_created: new Date().toISOString(),
83+
time_modified: new Date().toISOString(),
84+
size: 1024 * 1024 * 1024 * 20,
85+
disk_id: disks[3].id,
86+
state: 'ready',
87+
},
7788
...generatedSnapshots,
7889
]
7990

8091
function generateSnapshot(index: number): Json<Snapshot> {
8192
return {
8293
id: uuid(),
83-
name: `disk-1-snapshot-${index + 5}`,
94+
name: `disk-1-snapshot-${index + 6}`,
8495
description: '',
8596
project_id: project.id,
8697
time_created: new Date().toISOString(),

test/e2e/snapshots.e2e.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test('Click through snapshots', async ({ page }) => {
2828
test('Confirm delete snapshot', async ({ page }) => {
2929
await page.goto('/projects/mock-project/snapshots')
3030

31-
const row = page.getByRole('row', { name: 'disk-1-snapshot-5' })
31+
const row = page.getByRole('row', { name: 'disk-1-snapshot-6' })
3232

3333
async function clickDelete() {
3434
await row.getByRole('button', { name: 'Row actions' }).click()

0 commit comments

Comments
 (0)