From 2dec355ddcf032d28725f4ed64212f9a50fc746d Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 17 Jan 2024 13:02:37 +0000 Subject: [PATCH 1/5] Update disk size from image (and snapshots) --- app/forms/disk-create.tsx | 49 ++++++++++++++++++++++++++++++++------ libs/api-mocks/snapshot.ts | 13 +++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index 536998043d..f392fd5fd8 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -6,6 +6,7 @@ * Copyright Oxide Computer Company */ import { format } from 'date-fns' +import fileSize from 'filesize' import { useMemo } from 'react' import { useController, type Control } from 'react-hook-form' import { useNavigate, type NavigateFunction } from 'react-router-dom' @@ -20,6 +21,7 @@ import { type DiskSource, type Image, } from '@oxide/api' +import { snapshots } from '@oxide/api-mocks' import { FieldLabel, FormDivider, Radio, RadioGroup } from '@oxide/ui' import { bytesToGiB, GiB } from '@oxide/util' @@ -93,9 +95,21 @@ export function CreateDiskSideModalForm({ ) const areImagesLoading = projectImages.isPending || siloImages.isPending - const selectedImageId = form.watch('diskSource.imageId') - const selectedImageSize = images.find((image) => image.id === selectedImageId)?.size - const imageSizeGiB = selectedImageSize ? bytesToGiB(selectedImageSize) : undefined + // validate disk source size + const diskSource = form.watch('diskSource').type + + let validateSizeGiB: number | undefined = undefined + if (diskSource === 'snapshot') { + const selectedSnapshotId = form.watch('diskSource.snapshotId') + const selectedSnapshotSize = snapshots.find( + (snapshot) => snapshot.id === selectedSnapshotId + )?.size + validateSizeGiB = selectedSnapshotSize ? bytesToGiB(selectedSnapshotSize) : undefined + } else if (diskSource === 'image') { + const selectedImageId = form.watch('diskSource.imageId') + const selectedImageSize = images.find((image) => image.id === selectedImageId)?.size + validateSizeGiB = selectedImageSize ? bytesToGiB(selectedImageSize) : undefined + } return ( { - if (imageSizeGiB && diskSizeGiB < imageSizeGiB) { - return `Must be as large as selected image (min. ${imageSizeGiB} GiB)` + if (validateSizeGiB && diskSizeGiB < validateSizeGiB) { + return `Must be as large as selected ${diskSource} (min. ${validateSizeGiB} GiB)` } }} /> @@ -143,6 +157,7 @@ const DiskSourceField = ({ const { field: { value, onChange }, } = useController({ control, name: 'diskSource' }) + const diskSizeField = useController({ control, name: 'size' }).field return ( <> @@ -190,6 +205,14 @@ const DiskSourceField = ({ isLoading={areImagesLoading} items={images.map((i) => toListboxItem(i, true))} required + onChange={(id) => { + const image = images.find((i) => i.id === id)! // if it's selected, it must be present + const imageSizeGiB = image.size / GiB + if (diskSizeField.value < imageSizeGiB) { + const nearest10 = Math.ceil(imageSizeGiB / 10) * 10 + diskSizeField.onChange(nearest10) + } + }} /> )} @@ -217,6 +240,7 @@ const SnapshotSelectField = ({ control }: { control: Control }) => { const snapshotsQuery = useApiQuery('snapshotList', { query: projectSelector }) const snapshots = snapshotsQuery.data?.items || [] + const diskSizeField = useController({ control, name: 'size' }).field return ( }) => { label="Source snapshot" placeholder="Select a snapshot" items={snapshots.map((i) => { + const formattedSize = fileSize(i.size, { base: 2, output: 'object' }) return { value: i.id, labelString: `${i.name}`, label: ( <>
{i.name}
-
+
Created on {format(i.timeCreated, 'MMM d, yyyy')} - + {' '} + /{' '} + {formattedSize.value} {formattedSize.unit}
), @@ -241,6 +268,14 @@ const SnapshotSelectField = ({ control }: { control: Control }) => { })} isLoading={snapshotsQuery.isPending} required + onChange={(id) => { + const snapshot = snapshots.find((i) => i.id === id)! // if it's selected, it must be present + const snapshotSizeGiB = snapshot.size / GiB + if (diskSizeField.value < snapshotSizeGiB) { + const nearest10 = Math.ceil(snapshotSizeGiB / 10) * 10 + diskSizeField.onChange(nearest10) + } + }} /> ) } diff --git a/libs/api-mocks/snapshot.ts b/libs/api-mocks/snapshot.ts index 56cf8e73db..4a70380f9a 100644 --- a/libs/api-mocks/snapshot.ts +++ b/libs/api-mocks/snapshot.ts @@ -74,13 +74,24 @@ export const snapshots: Json[] = [ disk_id: 'a6f61e3f-25c1-49b0-a013-ac6a2d98a948', state: 'ready', }, + { + id: '7fc6ca11-452e-d3e4-9e1c-752ff615abea', + name: 'snapshot-heavy', + description: '', + project_id: project.id, + time_created: new Date().toISOString(), + time_modified: new Date().toISOString(), + size: 1024 * 1024 * 1024 * 20, + disk_id: disks[3].id, + state: 'ready', + }, ...generatedSnapshots, ] function generateSnapshot(index: number): Json { return { id: uuid(), - name: `disk-1-snapshot-${index + 5}`, + name: `disk-1-snapshot-${index + 6}`, description: '', project_id: project.id, time_created: new Date().toISOString(), From 3b9f12cdaf4d7e2c4b9b744355188ccf3aefc720 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 17 Jan 2024 15:25:08 +0000 Subject: [PATCH 2/5] Fix test --- app/test/e2e/snapshots.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/test/e2e/snapshots.e2e.ts b/app/test/e2e/snapshots.e2e.ts index 3d2c5e32a1..461f832628 100644 --- a/app/test/e2e/snapshots.e2e.ts +++ b/app/test/e2e/snapshots.e2e.ts @@ -28,7 +28,7 @@ test('Click through snapshots', async ({ page }) => { test('Confirm delete snapshot', async ({ page }) => { await page.goto('/projects/mock-project/snapshots') - const row = page.getByRole('row', { name: 'disk-1-snapshot-5' }) + const row = page.getByRole('row', { name: 'disk-1-snapshot-6' }) async function clickDelete() { await row.getByRole('button', { name: 'Row actions' }).click() From 74e996eccb306448425715555877b7ba534f19e9 Mon Sep 17 00:00:00 2001 From: Benjamin Leonard Date: Wed, 17 Jan 2024 15:25:18 +0000 Subject: [PATCH 3/5] Wow thats embarassing --- app/forms/disk-create.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index f392fd5fd8..a61f9c83f2 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -21,7 +21,6 @@ import { type DiskSource, type Image, } from '@oxide/api' -import { snapshots } from '@oxide/api-mocks' import { FieldLabel, FormDivider, Radio, RadioGroup } from '@oxide/ui' import { bytesToGiB, GiB } from '@oxide/util' @@ -95,6 +94,9 @@ export function CreateDiskSideModalForm({ ) const areImagesLoading = projectImages.isPending || siloImages.isPending + const snapshotsQuery = useApiQuery('snapshotList', { query: projectSelector }) + const snapshots = snapshotsQuery.data?.items || [] + // validate disk source size const diskSource = form.watch('diskSource').type @@ -130,6 +132,7 @@ export function CreateDiskSideModalForm({ Date: Wed, 17 Jan 2024 15:32:16 +0000 Subject: [PATCH 4/5] CI --- app/forms/disk-create.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index a61f9c83f2..e52209c534 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -132,7 +132,6 @@ export function CreateDiskSideModalForm({ Date: Mon, 18 Mar 2024 15:08:18 +0000 Subject: [PATCH 5/5] Update filesize import --- app/forms/disk-create.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/forms/disk-create.tsx b/app/forms/disk-create.tsx index f94bb6adc2..cf3122f3f8 100644 --- a/app/forms/disk-create.tsx +++ b/app/forms/disk-create.tsx @@ -6,7 +6,7 @@ * Copyright Oxide Computer Company */ import { format } from 'date-fns' -import fileSize from 'filesize' +import { filesize } from 'filesize' import { useMemo } from 'react' import { useController, type Control } from 'react-hook-form' import { useNavigate, type NavigateFunction } from 'react-router-dom' @@ -252,7 +252,7 @@ const SnapshotSelectField = ({ control }: { control: Control }) => { label="Source snapshot" placeholder="Select a snapshot" items={snapshots.map((i) => { - const formattedSize = fileSize(i.size, { base: 2, output: 'object' }) + const formattedSize = filesize(i.size, { base: 2, output: 'object' }) return { value: i.id, labelString: `${i.name}`,