Skip to content

Commit

Permalink
Move VPC edit form to view page
Browse files Browse the repository at this point in the history
  • Loading branch information
charliepark committed Sep 5, 2024
1 parent 880cb8c commit 4b07857
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 21 deletions.
22 changes: 13 additions & 9 deletions app/forms/vpc-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,22 @@ export function EditVpcSideModalForm() {
query: { project },
})

const onDismiss = () => navigate(pb.vpcs({ project }))
const onDismiss = () => navigate(pb.vpc({ project, vpc: vpcName }))

const editVpc = useApiMutation('vpcUpdate', {
onSuccess(vpc) {
onSuccess(updatedVpc) {
navigate(pb.vpc({ project, vpc: updatedVpc.name }))
queryClient.invalidateQueries('vpcList')
queryClient.setQueryData(
'vpcView',
{ path: { vpc: vpc.name }, query: { project } },
vpc
)
addToast({ content: 'Your VPC has been created' })
onDismiss()

// Only invalidate if we're staying on the same page. If the name
// _has_ changed, invalidating ipPoolView causes an error page to flash
// while the loader for the target page is running because the current
// page's pool gets cleared out while we're still on the page. If we're
// navigating to a different page, its query will fetch anew regardless.
if (vpc.name === updatedVpc.name) {
queryClient.invalidateQueries('vpcView')
}
addToast({ content: 'Your VPC has been updated' })
},
})

Expand Down
47 changes: 42 additions & 5 deletions app/pages/project/vpcs/VpcPage/VpcPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
*
* Copyright Oxide Computer Company
*/
import type { LoaderFunctionArgs } from 'react-router-dom'
import { useMemo } from 'react'
import { useNavigate, type LoaderFunctionArgs } from 'react-router-dom'

import { apiQueryClient, usePrefetchedApiQuery } from '@oxide/api'
import { apiQueryClient, useApiMutation, usePrefetchedApiQuery } from '@oxide/api'
import { Networking24Icon } from '@oxide/design-system/icons/react'

import { MoreActionsMenu } from '~/components/MoreActionsMenu'
import { RouteTabs, Tab } from '~/components/RouteTabs'
import { getVpcSelector, useVpcSelector } from '~/hooks'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { DescriptionCell } from '~/table/cells/DescriptionCell'
import { DateTime } from '~/ui/lib/DateTime'
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
Expand All @@ -27,17 +31,50 @@ VpcPage.loader = async ({ params }: LoaderFunctionArgs) => {
}

export function VpcPage() {
const navigate = useNavigate()
const vpcSelector = useVpcSelector()
const { project, vpc: vpcName } = vpcSelector
const { data: vpc } = usePrefetchedApiQuery('vpcView', {
path: { vpc: vpcSelector.vpc },
query: { project: vpcSelector.project },
path: { vpc: vpcName },
query: { project },
})

const { mutateAsync: deleteVpc } = useApiMutation('vpcDelete', {
onSuccess() {
navigate(pb.vpcs({ project }))
apiQueryClient.invalidateQueries('vpcList')
addToast({ content: 'VPC deleted' })
},
})

const actions = useMemo(
() => [
{
label: 'Edit',
onActivate() {
navigate(pb.vpcEdit(vpcSelector))
},
},
{
label: 'Delete',
onActivate: confirmDelete({
doDelete: () => deleteVpc({ path: { vpc: vpc.name }, query: { project } }),
label: vpc.name,
}),
className: 'destructive',
},
],
[deleteVpc, navigate, vpcSelector, project, vpc.name]
)

return (
<>
<PageHeader>
<PageTitle icon={<Networking24Icon />}>{vpc.name}</PageTitle>
<VpcDocsPopover />
<div className="inline-flex gap-2">
<VpcDocsPopover />
<MoreActionsMenu label="VPC actions" actions={actions} />
</div>
</PageHeader>
<PropertiesTable.Group className="mb-16">
<PropertiesTable>
Expand Down
4 changes: 3 additions & 1 deletion app/pages/project/vpcs/VpcsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Networking16Icon, Networking24Icon } from '@oxide/design-system/icons/r
import { DocsPopover } from '~/components/DocsPopover'
import { getProjectSelector, useProjectSelector, useQuickActions } from '~/hooks'
import { confirmDelete } from '~/stores/confirm-delete'
import { addToast } from '~/stores/toast'
import { SkeletonCell } from '~/table/cells/EmptyCell'
import { LinkCell, makeLinkCell } from '~/table/cells/LinkCell'
import { getActionsCol, type MenuAction } from '~/table/columns/action-col'
Expand Down Expand Up @@ -83,6 +84,7 @@ export function VpcsPage() {
const deleteVpc = useApiMutation('vpcDelete', {
onSuccess() {
queryClient.invalidateQueries('vpcList')
addToast({ content: 'VPC deleted' })
},
})

Expand Down Expand Up @@ -147,7 +149,7 @@ export function VpcsPage() {
<PageTitle icon={<Networking24Icon />}>VPCs</PageTitle>
<VpcDocsPopover />
</PageHeader>
<TableActions>
<TableActions className="!-mt-6">
<CreateLink to={pb.vpcsNew({ project })}>New Vpc</CreateLink>
</TableActions>
<Table columns={columns} emptyState={<EmptyState />} />
Expand Down
12 changes: 6 additions & 6 deletions app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,12 +348,6 @@ export const routes = createRoutesFromElements(
element={<CreateVpcSideModalForm />}
handle={{ crumb: 'New VPC' }}
/>
<Route
path="vpcs/:vpc/edit"
element={<EditVpcSideModalForm />}
loader={EditVpcSideModalForm.loader}
handle={{ crumb: 'Edit VPC' }}
/>
</Route>

<Route path="vpcs" handle={{ crumb: 'VPCs' }}>
Expand All @@ -365,6 +359,12 @@ export const routes = createRoutesFromElements(
loader={VpcFirewallRulesTab.loader}
/>
<Route element={<VpcFirewallRulesTab />} loader={VpcFirewallRulesTab.loader}>
<Route
path="edit"
element={<EditVpcSideModalForm />}
loader={EditVpcSideModalForm.loader}
handle={{ crumb: 'Edit VPC' }}
/>
<Route
path="firewall-rules"
handle={{ crumb: 'Firewall Rules' }}
Expand Down
29 changes: 29 additions & 0 deletions test/e2e/vpcs.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ test('can nav to VpcPage from /', async ({ page }) => {
await expect(page.getByRole('cell', { name: 'allow-icmp' })).toBeVisible()
})

test('can edit VPC', async ({ page }) => {
// update the VPC name, starting from the VPCs list page
await page.goto('/projects/mock-project/vpcs')
await expectRowVisible(page.getByRole('table'), { name: 'mock-vpc' })
await clickRowAction(page, 'mock-vpc', 'Edit')
await expect(page).toHaveURL('/projects/mock-project/vpcs/mock-vpc/edit')
await page.getByRole('textbox', { name: 'Name' }).first().fill('mock-vpc-2')
await page.getByRole('button', { name: 'Update VPC' }).click()
await expect(page).toHaveURL('/projects/mock-project/vpcs/mock-vpc-2/firewall-rules')
await expect(page.getByRole('heading', { name: 'mock-vpc-2' })).toBeVisible()

// now update the VPC description, starting from the VPC view page
await page.getByRole('button', { name: 'VPC actions' }).click()
await page.getByRole('menuitem', { name: 'Edit' }).click()
await expect(page).toHaveURL('/projects/mock-project/vpcs/mock-vpc-2/edit')
await page.getByRole('textbox', { name: 'Description' }).fill('updated description')
await page.getByRole('button', { name: 'Update VPC' }).click()
await expect(page).toHaveURL('/projects/mock-project/vpcs/mock-vpc-2/firewall-rules')

// go to the VPCs list page and verify the name and description change
await page.getByRole('link', { name: 'VPCs' }).click()
await expect(page.getByRole('table').locator('tbody >> tr')).toHaveCount(1)
await expectRowVisible(page.getByRole('table'), {
name: 'mock-vpc-2',
'DNS name': 'mock-vpc',
description: 'updated description',
})
})

test('can create and delete subnet', async ({ page }) => {
await page.goto('/projects/mock-project/vpcs/mock-vpc')
await page.getByRole('tab', { name: 'Subnets' }).click()
Expand Down

0 comments on commit 4b07857

Please sign in to comment.