Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion app/pages/project/instances/instance/tabs/StorageTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type { LoaderFunctionArgs } from 'react-router-dom'
import {
type Disk,
apiQueryClient,
diskCan,
genName,
instanceCan,
useApiMutation,
useApiQueryClient,
Expand Down Expand Up @@ -60,11 +62,35 @@ export function StorageTab() {
)

const detachDisk = useApiMutation('instanceDiskDetach', {})
const createSnapshot = useApiMutation('snapshotCreate', {
onSuccess() {
queryClient.invalidateQueries('snapshotList')
addToast({ content: 'Snapshot successfully created' })
},
})

const { data: instance } = usePrefetchedApiQuery('instanceView', instancePathQuery)

const makeActions = useCallback(
(disk: Disk): MenuAction[] => [
{
label: 'Snapshot',
disabled: !diskCan.snapshot(disk) && (
<>
Only disks in state {fancifyStates(diskCan.snapshot.states)} can be snapshotted
</>
),
onActivate() {
createSnapshot.mutate({
query: { project },
body: {
name: genName(disk.name),
disk: disk.name,
description: '',
},
})
},
},
{
label: 'Detach',
disabled: !instanceCan.detachDisk(instance) && (
Expand All @@ -82,7 +108,7 @@ export function StorageTab() {
},
},
],
[detachDisk, instance, queryClient, instancePathQuery]
[detachDisk, instance, queryClient, instancePathQuery, createSnapshot, project]
)

const attachDisk = useApiMutation('instanceDiskAttach', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
*
* Copyright Oxide Computer Company
*/
import { expect, expectNotVisible, expectVisible, stopInstance, test } from '../utils'
import {
clickRowAction,
expect,
expectNotVisible,
expectRowVisible,
expectVisible,
stopInstance,
test,
} from '../utils'

test('Attach disk', async ({ page }) => {
await page.goto('/projects/mock-project/instances/db1')
Expand Down Expand Up @@ -57,3 +65,23 @@ test('Attach disk', async ({ page }) => {
await page.click('role=button[name="Attach Disk"]')
await expectVisible(page, ['role=cell[name="disk-3"]'])
})

test('Snapshot disk', async ({ page }) => {
await page.goto('/projects/mock-project/instances/db1')

// have to use nth with toasts because the text shows up in multiple spots
const successMsg = page.getByText('Snapshot successfully created').nth(0)
await expect(successMsg).toBeHidden()

await clickRowAction(page, 'disk-2', 'Snapshot')

await expect(successMsg).toBeVisible() // we see the toast!

// now go see the snapshot on the snapshots page
await page.getByRole('link', { name: 'Snapshots' }).click()
const table = page.getByRole('table')
await expectRowVisible(table, {
name: expect.stringMatching(/^disk-2-/),
Copy link
Collaborator Author

@david-crespo david-crespo Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to add this to be able to match on a partial cell content. It works for free because we're already using objectContaining in expectRowVisible.

disk: 'disk-2',
})
})
8 changes: 7 additions & 1 deletion app/test/e2e/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ export async function expectNotVisible(page: Page, selectors: Selector[]) {
}
}

// Technically this has type AsymmetricMatcher, which is not exported by
// Playwright and is (surprisingly) just Record<string, any>. Rather than use
// that, I think it's smarter to do the following in case they ever make the
// type more interesting; this will still do what it's supposed to.
type StringMatcher = ReturnType<typeof expect.stringMatching>

/**
* Assert that a row matching `expectedRow` is present in `table`. The match
* uses `objectContaining`, so `expectedRow` does not need to contain every
Expand All @@ -55,7 +61,7 @@ export async function expectNotVisible(page: Page, selectors: Selector[]) {
*/
export async function expectRowVisible(
table: Locator,
expectedRow: Record<string, string>
expectedRow: Record<string, string | StringMatcher>
) {
// wait for header and rows to avoid flake town
const headerLoc = table.locator('thead >> role=cell')
Expand Down