From 30e7a5f38a431cb2568d214ce126f39c9e2da315 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Wed, 5 Jan 2022 17:00:09 -0600 Subject: [PATCH 1/9] first draft at create subnet SideModal --- app/pages/project/instances/InstancesPage.tsx | 2 +- .../VpcPage/modals/create-subnet.tsx | 108 ++++++++++++++++++ .../networking/VpcPage/tabs/VpcSubnetsTab.tsx | 51 ++++++--- libs/ui/lib/side-modal/SideModal.tsx | 15 ++- 4 files changed, 158 insertions(+), 18 deletions(-) create mode 100644 app/pages/project/networking/VpcPage/modals/create-subnet.tsx diff --git a/app/pages/project/instances/InstancesPage.tsx b/app/pages/project/instances/InstancesPage.tsx index 027cc0f27..ec3b3f9eb 100644 --- a/app/pages/project/instances/InstancesPage.tsx +++ b/app/pages/project/instances/InstancesPage.tsx @@ -49,7 +49,7 @@ export const InstancesPage = () => { to={`/orgs/${orgName}/projects/${projectName}/instances/new`} className={buttonStyle({ size: 'xs', variant: 'dim' })} > - new instance + New Instance diff --git a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx b/app/pages/project/networking/VpcPage/modals/create-subnet.tsx new file mode 100644 index 000000000..6eceeb6a5 --- /dev/null +++ b/app/pages/project/networking/VpcPage/modals/create-subnet.tsx @@ -0,0 +1,108 @@ +import React from 'react' +import { Formik, Form } from 'formik' + +import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' +import { useApiMutation, useApiQueryClient } from '@oxide/api' + +type Props = { + isOpen: boolean + onDismiss: () => void + orgName: string + projectName: string + vpcName: string +} + +export function CreateVpcSubnetModal({ + isOpen, + onDismiss, + orgName, + projectName, + vpcName, +}: Props) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + const createSubnet = useApiMutation('vpcSubnetsPost', { + onSuccess() { + queryClient.invalidateQueries('vpcSubnetsGet', parentIds) + onDismiss() + }, + }) + const formId = 'create-vpc-subnet-form' + return ( + + { + createSubnet.mutate({ + ...parentIds, + // XXX body is optional. useApiMutation should be smarter and require body when it's required + body: { + name, + description, + // TODO: validate these client-side using the patterns. sadly non-trivial + ipv4Block: ipv4Block || null, + ipv6Block: ipv6Block || null, + }, + }) + }} + > +
+ +
+ + IPv6 block + + +
+
+ + IPv6 block + + +
+
+ +
+ + Name + + +
+
+ + Description (optional) + {/* TODO: optional should be lighter font */} + + +
+
+ +
+ + + + +
+ ) +} diff --git a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx index f5e173efc..c9b05f87c 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx @@ -1,29 +1,52 @@ -import React from 'react' +import React, { useState } from 'react' import { useParams } from '../../../../../hooks' import { useQueryTable, TwoLineCell, DateCell } from '@oxide/table' +import { Button } from '@oxide/ui' +import { CreateVpcSubnetModal } from '../modals/create-subnet' export const VpcSubnetsTab = () => { - const { orgName: organizationName, ...other } = useParams( + const { orgName, projectName, vpcName } = useParams( 'orgName', 'projectName', 'vpcName' ) const { Table, Column } = useQueryTable('vpcSubnetsGet', { - organizationName, - ...other, + organizationName: orgName, + projectName, + vpcName, }) + const [createModalOpen, setCreateModalOpen] = useState(false) + return ( -
- - [vpc.ipv4_block, vpc.ipv6_block]} - cell={TwoLineCell} - /> - -
+ <> +
+ + setCreateModalOpen(false)} + /> +
+ + + [vpc.ipv4_block, vpc.ipv6_block]} + cell={TwoLineCell} + /> + +
+ ) } diff --git a/libs/ui/lib/side-modal/SideModal.tsx b/libs/ui/lib/side-modal/SideModal.tsx index 8a3d7a793..5d1d2f822 100644 --- a/libs/ui/lib/side-modal/SideModal.tsx +++ b/libs/ui/lib/side-modal/SideModal.tsx @@ -5,6 +5,7 @@ import { Button } from '../button/Button' import { pluckFirstOfType } from '@oxide/util' import type { ChildrenProp } from '@oxide/util' import { Close12Icon } from '../icons' +import cn from 'classnames' export interface SideModalProps extends DialogProps, ChildrenProp { id: string @@ -27,7 +28,7 @@ export function SideModal({ id={id} onDismiss={onDismiss} {...dialogProps} - className="absolute right-0 top-0 bottom-0 w-[32rem] p-0 m-0 flex flex-col justify-between bg-gray-500 border-l border-gray-400" + className="absolute right-0 top-0 bottom-0 w-[32rem] p-0 m-0 flex flex-col justify-between bg-black border-l border-gray-400" aria-labelledby={titleId} >
( -
{children}
+SideModal.Section = ({ + children, + className, +}: { + children: React.ReactNode + className?: string +}) => ( +
+ {children} +
) SideModal.Docs = ({ children }: ChildrenProp) => ( From b8060b4c23acad05ea3ee80d12802c41d7eeee8f Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 6 Jan 2022 10:54:14 -0600 Subject: [PATCH 2/9] stub test for create subnet modal --- .../networking/VpcPage/VpcPage.spec.ts | 32 +++++++++++++++++++ .../VpcPage/modals/create-subnet.tsx | 5 ++- app/test-utils.tsx | 1 + package.json | 4 ++- yarn.lock | 31 ++++++++++++++++++ 5 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 app/pages/project/networking/VpcPage/VpcPage.spec.ts diff --git a/app/pages/project/networking/VpcPage/VpcPage.spec.ts b/app/pages/project/networking/VpcPage/VpcPage.spec.ts new file mode 100644 index 000000000..777f61d11 --- /dev/null +++ b/app/pages/project/networking/VpcPage/VpcPage.spec.ts @@ -0,0 +1,32 @@ +import { + fireEvent, + // logRoles, + renderAppAt, + screen, + // userEvent, +} from '../../../../test-utils' + +describe('VpcPage', () => { + describe('subnets tab', () => { + it('does something', async () => { + renderAppAt('/orgs/maze-war/projects/prod-online/vpcs/default') + screen.getByText('Subnets') + const newSubnet = screen.getByRole('button', { name: 'New Subnet' }) + expect(screen.queryByRole('button', { name: 'Create subnet' })).toBeNull() + fireEvent.click(newSubnet) + + // const ipv4 = screen.getByRole('textbox', { name: 'IPv4 block' }) + // const ipv6 = screen.getByRole('textbox', { name: 'IPv6 block' }) + // const name = screen.getByRole('textbox', { name: 'Name' }) + + // this pisses off testing library, "formik change outside of act()". ugh + // userEvent.type(ipv4, 'new-subnet') + + // const form = document.getElementById('create-vpc-subnet-form')! + // screen.debug(form, 10000) + // logRoles(form) + + // const submit = screen.getByRole('button', { name: 'Create subnet' }) + }) + }) +}) diff --git a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx b/app/pages/project/networking/VpcPage/modals/create-subnet.tsx index 6eceeb6a5..7e07ce211 100644 --- a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx +++ b/app/pages/project/networking/VpcPage/modals/create-subnet.tsx @@ -64,7 +64,7 @@ export function CreateVpcSubnetModal({
- IPv6 block + IPv4 block
@@ -87,8 +87,7 @@ export function CreateVpcSubnetModal({ htmlFor="subnet-description" tip="A description for the subnet" > - Description (optional) - {/* TODO: optional should be lighter font */} + Description {/* TODO: indicate optional */}
diff --git a/app/test-utils.tsx b/app/test-utils.tsx index a19fa573b..2da931a4f 100644 --- a/app/test-utils.tsx +++ b/app/test-utils.tsx @@ -38,4 +38,5 @@ export const lastPostBody = (mock: FetchMockStatic): any => JSON.parse(mock.lastOptions(undefined, 'POST')?.body as unknown as string) export * from '@testing-library/react' +export { default as userEvent } from '@testing-library/user-event' export { customRender as render } diff --git a/package.json b/package.json index 8df5e81af..c8ded9583 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "vite build", "build-for-nexus": "yarn install && API_URL='' vite build", "ci": "yarn tsc && yarn lint && yarn test", - "test": "jest", + "test": "DEBUG_PRINT_LIMIT=0 jest", "lint": "eslint --ext .js,.ts,.tsx,.json .", "fmt": "prettier --write .", "gen": "plop", @@ -70,9 +70,11 @@ "@storybook/manager-webpack5": "^6.4.0-rc.1", "@storybook/react": "^6.4.0-rc.1", "@storybook/theming": "^6.4.0-rc.1", + "@testing-library/dom": "^8.11.1", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", "@testing-library/react-hooks": "^7.0.1", + "@testing-library/user-event": "^13.5.0", "@types/jest": "^26.0.23", "@types/jscodeshift": "^0.11.2", "@types/mousetrap": "^1.6.8", diff --git a/yarn.lock b/yarn.lock index ef4d5719c..b98894fdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3201,6 +3201,20 @@ lz-string "^1.4.4" pretty-format "^27.0.2" +"@testing-library/dom@^8.11.1": + version "8.11.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.1.tgz#03fa2684aa09ade589b460db46b4c7be9fc69753" + integrity sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + "@testing-library/jest-dom@^5.14.1": version "5.14.1" resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz" @@ -3235,6 +3249,13 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^8.0.0" +"@testing-library/user-event@^13.5.0": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== + dependencies: + "@babel/runtime" "^7.12.5" + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" @@ -4360,6 +4381,11 @@ aria-query@^4.2.2: "@babel/runtime" "^7.10.2" "@babel/runtime-corejs3" "^7.10.2" +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz" @@ -6384,6 +6410,11 @@ dom-accessibility-api@^0.5.6: resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz" integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw== +dom-accessibility-api@^0.5.9: + version "0.5.10" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz#caa6d08f60388d0bb4539dd75fe458a9a1d0014c" + integrity sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g== + dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" From d0f758bc64270d4108559e45302cfc5c1aeec911 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 6 Jan 2022 15:31:42 -0600 Subject: [PATCH 3/9] working edit modal. wild! --- .../networking/VpcPage/modals/edit-subnet.tsx | 110 ++++++++++++++++++ .../networking/VpcPage/tabs/VpcSubnetsTab.tsx | 21 +++- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 app/pages/project/networking/VpcPage/modals/edit-subnet.tsx diff --git a/app/pages/project/networking/VpcPage/modals/edit-subnet.tsx b/app/pages/project/networking/VpcPage/modals/edit-subnet.tsx new file mode 100644 index 000000000..6674e3ec8 --- /dev/null +++ b/app/pages/project/networking/VpcPage/modals/edit-subnet.tsx @@ -0,0 +1,110 @@ +import React from 'react' +import { Formik, Form } from 'formik' + +import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' +import type { VpcSubnet } from '@oxide/api' +import { useApiMutation, useApiQueryClient } from '@oxide/api' + +type Props = { + onDismiss: () => void + orgName: string + projectName: string + vpcName: string + originalSubnet: VpcSubnet | null +} + +export function EditVpcSubnetModal({ + onDismiss, + orgName, + projectName, + vpcName, + originalSubnet, +}: Props) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + const updateSubnet = useApiMutation('vpcSubnetsPutSubnet', { + onSuccess() { + queryClient.invalidateQueries('vpcSubnetsGet', parentIds) + onDismiss() + }, + }) + + if (!originalSubnet) return null + + const formId = 'edit-vpc-subnet-form' + return ( + + { + updateSubnet.mutate({ + ...parentIds, + subnetName: originalSubnet.identity.name, + body: { + name, + description, + // TODO: validate these client-side using the patterns. sadly non-trivial + ipv4Block: ipv4Block || null, + ipv6Block: ipv6Block || null, + }, + }) + }} + > +
+ +
+ + IPv4 block + + +
+
+ + IPv6 block + + +
+
+ +
+ + Name + + +
+
+ + Description {/* TODO: indicate optional */} + + +
+
+
+
+ + + + +
+ ) +} diff --git a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx index c9b05f87c..de22e150b 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx @@ -1,8 +1,11 @@ import React, { useState } from 'react' import { useParams } from '../../../../../hooks' +import type { MenuAction } from '@oxide/table' import { useQueryTable, TwoLineCell, DateCell } from '@oxide/table' import { Button } from '@oxide/ui' import { CreateVpcSubnetModal } from '../modals/create-subnet' +import { EditVpcSubnetModal } from '../modals/edit-subnet' +import type { VpcSubnet } from '@oxide/api' export const VpcSubnetsTab = () => { const { orgName, projectName, vpcName } = useParams( @@ -18,6 +21,15 @@ export const VpcSubnetsTab = () => { }) const [createModalOpen, setCreateModalOpen] = useState(false) + const [editingSubnet, setEditingSubnet] = useState(null) + + const actions = (subnet: VpcSubnet): MenuAction[] => [ + { + label: 'Edit', + onActivate: () => setEditingSubnet(subnet), + // TODO: disable for default? can you edit it? + }, + ] return ( <> @@ -36,8 +48,15 @@ export const VpcSubnetsTab = () => { isOpen={createModalOpen} onDismiss={() => setCreateModalOpen(false)} /> + setEditingSubnet(null)} + /> - +
Date: Thu, 6 Jan 2022 17:53:57 -0600 Subject: [PATCH 4/9] extremely basic server errors --- .../VpcPage/modals/create-subnet.tsx | 18 +++++++++++++++--- .../networking/VpcPage/modals/edit-subnet.tsx | 19 ++++++++++++++++--- libs/api/index.ts | 9 ++++++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx b/app/pages/project/networking/VpcPage/modals/create-subnet.tsx index 7e07ce211..791133e9f 100644 --- a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx +++ b/app/pages/project/networking/VpcPage/modals/create-subnet.tsx @@ -3,6 +3,7 @@ import { Formik, Form } from 'formik' import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' import { useApiMutation, useApiQueryClient } from '@oxide/api' +import { getServerError } from '../../../../../util/errors' type Props = { isOpen: boolean @@ -25,10 +26,16 @@ export function CreateVpcSubnetModal({ vpcName, } const queryClient = useApiQueryClient() + + function dismiss() { + createSubnet.reset() + onDismiss() + } + const createSubnet = useApiMutation('vpcSubnetsPost', { onSuccess() { queryClient.invalidateQueries('vpcSubnetsGet', parentIds) - onDismiss() + dismiss() }, }) const formId = 'create-vpc-subnet-form' @@ -37,7 +44,7 @@ export function CreateVpcSubnetModal({ id="create-vpc-subnet-modal" title="Create subnet" isOpen={isOpen} - onDismiss={onDismiss} + onDismiss={dismiss} > + +
+ {getServerError(createSubnet.error)} +
+
- + + + + ) +} diff --git a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx b/app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx similarity index 99% rename from app/pages/project/networking/VpcPage/modals/create-subnet.tsx rename to app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx index 791133e9f..3449f5c9d 100644 --- a/app/pages/project/networking/VpcPage/modals/create-subnet.tsx +++ b/app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx @@ -38,7 +38,9 @@ export function CreateVpcSubnetModal({ dismiss() }, }) + const formId = 'create-vpc-subnet-form' + return ( void + orgName: string + projectName: string + vpcName: string + originalRouter: VpcRouter | null +} + +// this will get a lot more interesting once the API is updated to allow us to +// put rules in the router, which is the whole point of a router + +export function EditVpcRouterModal({ + onDismiss, + orgName, + projectName, + vpcName, + originalRouter, +}: Props) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + + function dismiss() { + updateRouter.reset() + onDismiss() + } + + const updateRouter = useApiMutation('vpcRoutersPutRouter', { + onSuccess() { + queryClient.invalidateQueries('vpcRoutersGet', parentIds) + updateRouter.reset() + dismiss() + }, + }) + + if (!originalRouter) return null + + const formId = 'edit-vpc-router-form' + return ( + + { + updateRouter.mutate({ + ...parentIds, + routerName: originalRouter.identity.name, + body: { name, description }, + }) + }} + > +
+ +
+ + Name + + +
+
+ + Description {/* TODO: indicate optional */} + + +
+
+ +
+ {getServerError(updateRouter.error)} +
+
+ +
+ + + + +
+ ) +} diff --git a/app/pages/project/networking/VpcPage/modals/edit-subnet.tsx b/app/pages/project/networking/VpcPage/modals/EditVpcSubnetModal.tsx similarity index 100% rename from app/pages/project/networking/VpcPage/modals/edit-subnet.tsx rename to app/pages/project/networking/VpcPage/modals/EditVpcSubnetModal.tsx diff --git a/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx index 89564e3f3..c23cd6f8c 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx @@ -1,29 +1,70 @@ -import React from 'react' +import React, { useState } from 'react' import { useParams } from '../../../../../hooks' +import { Button } from '@oxide/ui' +import type { MenuAction } from '@oxide/table' import { useQueryTable, DateCell, LabelCell } from '@oxide/table' +import type { VpcRouter } from '@oxide/api' +import { CreateVpcRouterModal } from '../modals/CreateVpcRouterModal' +import { EditVpcRouterModal } from '../modals/EditVpcRouterModal' export const VpcRoutersTab = () => { - const { orgName: organizationName, ...other } = useParams( + const { orgName, projectName, vpcName } = useParams( 'orgName', 'projectName', 'vpcName' ) const { Table, Column } = useQueryTable('vpcRoutersGet', { - organizationName, - ...other, + organizationName: orgName, + projectName, + vpcName, }) + const [createModalOpen, setCreateModalOpen] = useState(false) + const [editing, setEditing] = useState(null) + + const actions = (router: VpcRouter): MenuAction[] => [ + { + label: 'Edit', + onActivate: () => setEditing(router), + }, + ] + return ( -
- - - -
+ <> +
+ + setCreateModalOpen(false)} + /> + setEditing(null)} + /> +
+ + + + +
+ ) } diff --git a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx index de22e150b..6ba8b0e14 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx @@ -3,8 +3,8 @@ import { useParams } from '../../../../../hooks' import type { MenuAction } from '@oxide/table' import { useQueryTable, TwoLineCell, DateCell } from '@oxide/table' import { Button } from '@oxide/ui' -import { CreateVpcSubnetModal } from '../modals/create-subnet' -import { EditVpcSubnetModal } from '../modals/edit-subnet' +import { CreateVpcSubnetModal } from '../modals/CreateVpcSubnetModal' +import { EditVpcSubnetModal } from '../modals/EditVpcSubnetModal' import type { VpcSubnet } from '@oxide/api' export const VpcSubnetsTab = () => { @@ -21,13 +21,12 @@ export const VpcSubnetsTab = () => { }) const [createModalOpen, setCreateModalOpen] = useState(false) - const [editingSubnet, setEditingSubnet] = useState(null) + const [editing, setEditing] = useState(null) const actions = (subnet: VpcSubnet): MenuAction[] => [ { label: 'Edit', - onActivate: () => setEditingSubnet(subnet), - // TODO: disable for default? can you edit it? + onActivate: () => setEditing(subnet), }, ] @@ -39,7 +38,7 @@ export const VpcSubnetsTab = () => { variant="dim" onClick={() => setCreateModalOpen(true)} > - New Subnet + New subnet { orgName={orgName} projectName={projectName} vpcName={vpcName} - originalSubnet={editingSubnet} // modal is open if this is non-null - onDismiss={() => setEditingSubnet(null)} + originalSubnet={editing} // modal is open if this is non-null + onDismiss={() => setEditing(null)} /> From 9e90a67a2220e2a38c8dbf143ae57840bd429c8c Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 7 Jan 2022 12:28:35 -0600 Subject: [PATCH 7/9] extract form shared between edit and create, save 60 lines --- .../VpcPage/modals/CreateVpcRouterModal.tsx | 98 -------- .../VpcPage/modals/CreateVpcSubnetModal.tsx | 121 ---------- .../VpcPage/modals/EditVpcRouterModal.tsx | 104 --------- .../VpcPage/modals/EditVpcSubnetModal.tsx | 123 ----------- .../networking/VpcPage/modals/vpc-routers.tsx | 178 +++++++++++++++ .../networking/VpcPage/modals/vpc-subnets.tsx | 209 ++++++++++++++++++ .../networking/VpcPage/tabs/VpcRoutersTab.tsx | 3 +- .../networking/VpcPage/tabs/VpcSubnetsTab.tsx | 3 +- 8 files changed, 389 insertions(+), 450 deletions(-) delete mode 100644 app/pages/project/networking/VpcPage/modals/CreateVpcRouterModal.tsx delete mode 100644 app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx delete mode 100644 app/pages/project/networking/VpcPage/modals/EditVpcRouterModal.tsx delete mode 100644 app/pages/project/networking/VpcPage/modals/EditVpcSubnetModal.tsx create mode 100644 app/pages/project/networking/VpcPage/modals/vpc-routers.tsx create mode 100644 app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx diff --git a/app/pages/project/networking/VpcPage/modals/CreateVpcRouterModal.tsx b/app/pages/project/networking/VpcPage/modals/CreateVpcRouterModal.tsx deleted file mode 100644 index 6d38d6837..000000000 --- a/app/pages/project/networking/VpcPage/modals/CreateVpcRouterModal.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React from 'react' -import { Formik, Form } from 'formik' - -import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' -import { useApiMutation, useApiQueryClient } from '@oxide/api' -import { getServerError } from '../../../../../util/errors' - -type Props = { - isOpen: boolean - onDismiss: () => void - orgName: string - projectName: string - vpcName: string -} - -export function CreateVpcRouterModal({ - isOpen, - onDismiss, - orgName, - projectName, - vpcName, -}: Props) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } - const queryClient = useApiQueryClient() - - function dismiss() { - createRouter.reset() - onDismiss() - } - - const createRouter = useApiMutation('vpcRoutersPost', { - onSuccess() { - queryClient.invalidateQueries('vpcRoutersGet', parentIds) - dismiss() - }, - }) - - const formId = 'create-vpc-router-form' - - return ( - - { - createRouter.mutate({ - ...parentIds, - body: { name, description }, - }) - }} - > -
- -
- - Name - - -
-
- - Description {/* TODO: indicate optional */} - - -
-
- -
- {getServerError(createRouter.error)} -
-
- -
- - - - -
- ) -} diff --git a/app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx b/app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx deleted file mode 100644 index 3449f5c9d..000000000 --- a/app/pages/project/networking/VpcPage/modals/CreateVpcSubnetModal.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react' -import { Formik, Form } from 'formik' - -import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' -import { useApiMutation, useApiQueryClient } from '@oxide/api' -import { getServerError } from '../../../../../util/errors' - -type Props = { - isOpen: boolean - onDismiss: () => void - orgName: string - projectName: string - vpcName: string -} - -export function CreateVpcSubnetModal({ - isOpen, - onDismiss, - orgName, - projectName, - vpcName, -}: Props) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } - const queryClient = useApiQueryClient() - - function dismiss() { - createSubnet.reset() - onDismiss() - } - - const createSubnet = useApiMutation('vpcSubnetsPost', { - onSuccess() { - queryClient.invalidateQueries('vpcSubnetsGet', parentIds) - dismiss() - }, - }) - - const formId = 'create-vpc-subnet-form' - - return ( - - { - createSubnet.mutate({ - ...parentIds, - // XXX body is optional. useApiMutation should be smarter and require body when it's required - body: { - name, - description, - // TODO: validate these client-side using the patterns. sadly non-trivial - ipv4Block: ipv4Block || null, - ipv6Block: ipv6Block || null, - }, - }) - }} - > -
- -
- - IPv4 block - - -
-
- - IPv6 block - - -
-
- -
- - Name - - -
-
- - Description {/* TODO: indicate optional */} - - -
-
- -
- {getServerError(createSubnet.error)} -
-
- -
- - - - -
- ) -} diff --git a/app/pages/project/networking/VpcPage/modals/EditVpcRouterModal.tsx b/app/pages/project/networking/VpcPage/modals/EditVpcRouterModal.tsx deleted file mode 100644 index 85332e49c..000000000 --- a/app/pages/project/networking/VpcPage/modals/EditVpcRouterModal.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react' -import { Formik, Form } from 'formik' - -import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' -import type { VpcRouter } from '@oxide/api' -import { useApiMutation, useApiQueryClient } from '@oxide/api' -import { getServerError } from '../../../../../util/errors' - -type Props = { - onDismiss: () => void - orgName: string - projectName: string - vpcName: string - originalRouter: VpcRouter | null -} - -// this will get a lot more interesting once the API is updated to allow us to -// put rules in the router, which is the whole point of a router - -export function EditVpcRouterModal({ - onDismiss, - orgName, - projectName, - vpcName, - originalRouter, -}: Props) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } - const queryClient = useApiQueryClient() - - function dismiss() { - updateRouter.reset() - onDismiss() - } - - const updateRouter = useApiMutation('vpcRoutersPutRouter', { - onSuccess() { - queryClient.invalidateQueries('vpcRoutersGet', parentIds) - updateRouter.reset() - dismiss() - }, - }) - - if (!originalRouter) return null - - const formId = 'edit-vpc-router-form' - return ( - - { - updateRouter.mutate({ - ...parentIds, - routerName: originalRouter.identity.name, - body: { name, description }, - }) - }} - > -
- -
- - Name - - -
-
- - Description {/* TODO: indicate optional */} - - -
-
- -
- {getServerError(updateRouter.error)} -
-
- -
- - - - -
- ) -} diff --git a/app/pages/project/networking/VpcPage/modals/EditVpcSubnetModal.tsx b/app/pages/project/networking/VpcPage/modals/EditVpcSubnetModal.tsx deleted file mode 100644 index 348b3c86b..000000000 --- a/app/pages/project/networking/VpcPage/modals/EditVpcSubnetModal.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react' -import { Formik, Form } from 'formik' - -import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' -import type { VpcSubnet } from '@oxide/api' -import { useApiMutation, useApiQueryClient } from '@oxide/api' -import { getServerError } from '../../../../../util/errors' - -type Props = { - onDismiss: () => void - orgName: string - projectName: string - vpcName: string - originalSubnet: VpcSubnet | null -} - -export function EditVpcSubnetModal({ - onDismiss, - orgName, - projectName, - vpcName, - originalSubnet, -}: Props) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } - const queryClient = useApiQueryClient() - - function dismiss() { - updateSubnet.reset() - onDismiss() - } - - const updateSubnet = useApiMutation('vpcSubnetsPutSubnet', { - onSuccess() { - queryClient.invalidateQueries('vpcSubnetsGet', parentIds) - updateSubnet.reset() - dismiss() - }, - }) - - if (!originalSubnet) return null - - const formId = 'edit-vpc-subnet-form' - return ( - - { - updateSubnet.mutate({ - ...parentIds, - subnetName: originalSubnet.identity.name, - body: { - name, - description, - // TODO: validate these client-side using the patterns. sadly non-trivial - ipv4Block: ipv4Block || null, - ipv6Block: ipv6Block || null, - }, - }) - }} - > -
- -
- - IPv4 block - - -
-
- - IPv6 block - - -
-
- -
- - Name - - -
-
- - Description {/* TODO: indicate optional */} - - -
-
- -
- {getServerError(updateSubnet.error)} -
-
- -
- - - - -
- ) -} diff --git a/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx b/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx new file mode 100644 index 000000000..6e6ecd3f2 --- /dev/null +++ b/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx @@ -0,0 +1,178 @@ +import React from 'react' +import { Formik, Form } from 'formik' + +import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' +import type { VpcRouter, ErrorResponse } from '@oxide/api' +import { useApiMutation, useApiQueryClient } from '@oxide/api' +import { getServerError } from '../../../../../util/errors' + +// this will get a lot more interesting once the API is updated to allow us to +// put rules in the router, which is the whole point of a router + +type FormProps = { + error: ErrorResponse | null + id: string +} + +// the moment the two forms diverge, inline them rather than introducing BS +// props here +const CommonForm = ({ error, id }: FormProps) => ( +
+ +
+ + Name + + +
+
+ + Description {/* TODO: indicate optional */} + + +
+
+ +
{getServerError(error)}
+
+ +) + +type CreateProps = { + isOpen: boolean + onDismiss: () => void + orgName: string + projectName: string + vpcName: string +} + +export function CreateVpcRouterModal({ + isOpen, + onDismiss, + orgName, + projectName, + vpcName, +}: CreateProps) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + + function dismiss() { + createRouter.reset() + onDismiss() + } + + const createRouter = useApiMutation('vpcRoutersPost', { + onSuccess() { + queryClient.invalidateQueries('vpcRoutersGet', parentIds) + dismiss() + }, + }) + + const formId = 'create-vpc-router-form' + + return ( + + { + createRouter.mutate({ + ...parentIds, + body: { name, description }, + }) + }} + > + + + + + + + + ) +} + +type EditProps = { + onDismiss: () => void + orgName: string + projectName: string + vpcName: string + originalRouter: VpcRouter | null +} + +export function EditVpcRouterModal({ + onDismiss, + orgName, + projectName, + vpcName, + originalRouter, +}: EditProps) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + + function dismiss() { + updateRouter.reset() + onDismiss() + } + + const updateRouter = useApiMutation('vpcRoutersPutRouter', { + onSuccess() { + queryClient.invalidateQueries('vpcRoutersGet', parentIds) + dismiss() + }, + }) + + if (!originalRouter) return null + + const formId = 'edit-vpc-router-form' + return ( + + { + updateRouter.mutate({ + ...parentIds, + routerName: originalRouter.identity.name, + body: { name, description }, + }) + }} + > + + + + + + + + ) +} diff --git a/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx b/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx new file mode 100644 index 000000000..dbd32c18f --- /dev/null +++ b/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx @@ -0,0 +1,209 @@ +import React from 'react' +import { Formik, Form } from 'formik' + +import { Button, FieldTitle, SideModal, TextField } from '@oxide/ui' +import type { VpcSubnet, ErrorResponse } from '@oxide/api' +import { useApiMutation, useApiQueryClient } from '@oxide/api' +import { getServerError } from '../../../../../util/errors' + +type FormProps = { + error: ErrorResponse | null + id: string +} + +// the moment the two forms diverge, inline them rather than introducing BS +// props here +const CommonForm = ({ id, error }: FormProps) => ( +
+ +
+ + IPv4 block + + +
+
+ + IPv6 block + + +
+
+ +
+ + Name + + +
+
+ + Description {/* TODO: indicate optional */} + + +
+
+ +
{getServerError(error)}
+
+ +) + +type CreateProps = { + isOpen: boolean + onDismiss: () => void + orgName: string + projectName: string + vpcName: string +} + +export function CreateVpcSubnetModal({ + isOpen, + onDismiss, + orgName, + projectName, + vpcName, +}: CreateProps) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + + function dismiss() { + createSubnet.reset() + onDismiss() + } + + const createSubnet = useApiMutation('vpcSubnetsPost', { + onSuccess() { + queryClient.invalidateQueries('vpcSubnetsGet', parentIds) + dismiss() + }, + }) + + const formId = 'create-vpc-subnet-form' + + return ( + + { + createSubnet.mutate({ + ...parentIds, + // XXX body is optional. useApiMutation should be smarter and require body when it's required + body: { + name, + description, + // TODO: validate these client-side using the patterns. sadly non-trivial + ipv4Block: ipv4Block || null, + ipv6Block: ipv6Block || null, + }, + }) + }} + > + + + + + + + + ) +} + +type EditProps = { + onDismiss: () => void + orgName: string + projectName: string + vpcName: string + originalSubnet: VpcSubnet | null +} + +export function EditVpcSubnetModal({ + onDismiss, + orgName, + projectName, + vpcName, + originalSubnet, +}: EditProps) { + const parentIds = { + organizationName: orgName, + projectName, + vpcName, + } + const queryClient = useApiQueryClient() + + function dismiss() { + updateSubnet.reset() + onDismiss() + } + + const updateSubnet = useApiMutation('vpcSubnetsPutSubnet', { + onSuccess() { + queryClient.invalidateQueries('vpcSubnetsGet', parentIds) + dismiss() + }, + }) + + if (!originalSubnet) return null + + const formId = 'edit-vpc-subnet-form' + return ( + + { + updateSubnet.mutate({ + ...parentIds, + subnetName: originalSubnet.identity.name, + body: { + name, + description, + // TODO: validate these client-side using the patterns. sadly non-trivial + ipv4Block: ipv4Block || null, + ipv6Block: ipv6Block || null, + }, + }) + }} + > + + + + + + + + ) +} diff --git a/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx index c23cd6f8c..f4cc03fd5 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx @@ -4,8 +4,7 @@ import { Button } from '@oxide/ui' import type { MenuAction } from '@oxide/table' import { useQueryTable, DateCell, LabelCell } from '@oxide/table' import type { VpcRouter } from '@oxide/api' -import { CreateVpcRouterModal } from '../modals/CreateVpcRouterModal' -import { EditVpcRouterModal } from '../modals/EditVpcRouterModal' +import { CreateVpcRouterModal, EditVpcRouterModal } from '../modals/vpc-routers' export const VpcRoutersTab = () => { const { orgName, projectName, vpcName } = useParams( diff --git a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx index 6ba8b0e14..11e1a1fcf 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx @@ -3,8 +3,7 @@ import { useParams } from '../../../../../hooks' import type { MenuAction } from '@oxide/table' import { useQueryTable, TwoLineCell, DateCell } from '@oxide/table' import { Button } from '@oxide/ui' -import { CreateVpcSubnetModal } from '../modals/CreateVpcSubnetModal' -import { EditVpcSubnetModal } from '../modals/EditVpcSubnetModal' +import { CreateVpcSubnetModal, EditVpcSubnetModal } from '../modals/vpc-subnets' import type { VpcSubnet } from '@oxide/api' export const VpcSubnetsTab = () => { From 057bb053c7697cd6764a4b3ec0d4e54964ba3661 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Fri, 7 Jan 2022 15:29:28 -0600 Subject: [PATCH 8/9] got full page create subnet integration test to work --- .../networking/VpcPage/VpcPage.spec.ts | 85 +++++++++++++++---- .../project/networking/VpcPage/VpcPage.tsx | 24 ++++-- libs/api-mocks/index.ts | 1 + libs/api-mocks/vpc.ts | 41 +++++++++ 4 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 libs/api-mocks/vpc.ts diff --git a/app/pages/project/networking/VpcPage/VpcPage.spec.ts b/app/pages/project/networking/VpcPage/VpcPage.spec.ts index ac08c9ab7..93e78d9e2 100644 --- a/app/pages/project/networking/VpcPage/VpcPage.spec.ts +++ b/app/pages/project/networking/VpcPage/VpcPage.spec.ts @@ -1,32 +1,85 @@ import { fireEvent, - // logRoles, + lastPostBody, renderAppAt, screen, - // userEvent, + userEvent, + waitForElementToBeRemoved, } from '../../../../test-utils' +import fetchMock from 'fetch-mock' +import { + org, + project, + vpc, + vpcSubnet, + vpcSubnet2, + vpcSubnets, +} from '@oxide/api-mocks' + +const vpcUrl = `/api/organizations/${org.name}/projects/${project.name}/vpcs/default` +const subnetsUrl = `${vpcUrl}/subnets` +const getSubnetsUrl = `${subnetsUrl}?limit=10` describe('VpcPage', () => { describe('subnets tab', () => { - it('does something', async () => { - renderAppAt('/orgs/maze-war/projects/prod-online/vpcs/default') + it('creating a subnet works', async () => { + fetchMock.get(vpcUrl, { status: 200, body: vpc }) + fetchMock.getOnce(getSubnetsUrl, { status: 200, body: vpcSubnets }) + const postMock = fetchMock.postOnce(subnetsUrl, { + status: 201, + body: vpcSubnet2, + }) + + renderAppAt('/orgs/mock-org/projects/mock-project/vpcs/default') screen.getByText('Subnets') - const newSubnet = screen.getByRole('button', { name: 'New subnet' }) - expect(screen.queryByRole('button', { name: 'Create subnet' })).toBeNull() - fireEvent.click(newSubnet) - // const ipv4 = screen.getByRole('textbox', { name: 'IPv4 block' }) - // const ipv6 = screen.getByRole('textbox', { name: 'IPv6 block' }) - // const name = screen.getByRole('textbox', { name: 'Name' }) + // wait for subnet to show up in the table + await screen.findByRole('cell', { name: vpcSubnet.identity.name }) + + // modal is not already open + expect(screen.queryByRole('dialog', { name: 'Create subnet' })).toBeNull() + + // click button to open modal + fireEvent.click(screen.getByRole('button', { name: 'New subnet' })) + + // modal is open + screen.getByRole('dialog', { name: 'Create subnet' }) + + const ipv4 = screen.getByRole('textbox', { name: 'IPv4 block' }) + userEvent.type(ipv4, '1.1.1.2/24') + + const name = screen.getByRole('textbox', { name: 'Name' }) + userEvent.type(name, 'mock-subnet-2') + + // override the subnets GET to include both subnets + fetchMock.getOnce( + getSubnetsUrl, + { + status: 200, + body: { items: [vpcSubnet, vpcSubnet2] }, + }, + { overwriteRoutes: true } + ) + + // submit the form + fireEvent.click(screen.getByRole('button', { name: 'Create subnet' })) - // this pisses off testing library, "formik change outside of act()". ugh - // userEvent.type(ipv4, 'new-subnet') + // wait for modal to close + await waitForElementToBeRemoved(() => + screen.queryByRole('dialog', { name: 'Create subnet' }) + ) - // const form = document.getElementById('create-vpc-subnet-form')! - // screen.debug(form, 10000) - // logRoles(form) + // it posted the form + expect(lastPostBody(postMock)).toEqual({ + ipv4Block: '1.1.1.2/24', + ipv6Block: null, + name: 'mock-subnet-2', + description: '', + }) - // const submit = screen.getByRole('button', { name: 'Create subnet' }) + // table should refetch and now include second subnet + screen.getByRole('cell', { name: vpcSubnet.identity.name }) + screen.getByRole('cell', { name: vpcSubnet2.identity.name }) }) }) }) diff --git a/app/pages/project/networking/VpcPage/VpcPage.tsx b/app/pages/project/networking/VpcPage/VpcPage.tsx index 5fd8b40e9..c63afd672 100644 --- a/app/pages/project/networking/VpcPage/VpcPage.tsx +++ b/app/pages/project/networking/VpcPage/VpcPage.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { format } from 'date-fns' import { Networking24Icon, PageHeader, @@ -11,9 +12,22 @@ import { VpcSystemRoutesTab } from './tabs/VpcSystemRoutesTab' import { VpcRoutersTab } from './tabs/VpcRoutersTab' import { useParams } from '../../../../hooks' import { VpcFirewallRulesTab } from './tabs/VpcFirewallRulesTab' +import { useApiQuery } from '@oxide/api' + +const formatDateTime = (s: string) => format(new Date(s), 'MMM d, yyyy H:mm aa') export const VpcPage = () => { - const { vpcName } = useParams('vpcName') + const { orgName, projectName, vpcName } = useParams( + 'orgName', + 'projectName', + 'vpcName' + ) + const { data: vpc } = useApiQuery('projectVpcsGetVpc', { + organizationName: orgName, + projectName, + vpcName, + }) + return ( <> @@ -25,18 +39,18 @@ export const VpcPage = () => { - Default network for the project + {vpc?.description} - frontend-production-vpc + {vpc?.dnsName} - Default network for the project + {vpc?.timeCreated && formatDateTime(vpc.timeCreated)} - Default network for the project + {vpc?.timeModified && formatDateTime(vpc.timeModified)} diff --git a/libs/api-mocks/index.ts b/libs/api-mocks/index.ts index 3a64ad4f9..2bfdd793c 100644 --- a/libs/api-mocks/index.ts +++ b/libs/api-mocks/index.ts @@ -2,3 +2,4 @@ export * from './instance' export * from './org' export * from './project' export * from './users' +export * from './vpc' diff --git a/libs/api-mocks/vpc.ts b/libs/api-mocks/vpc.ts new file mode 100644 index 000000000..5321c6891 --- /dev/null +++ b/libs/api-mocks/vpc.ts @@ -0,0 +1,41 @@ +import { project } from './project' +import type { Vpc, VpcSubnet, VpcSubnetResultsPage } from '@oxide/api' + +export const vpc: Vpc = { + id: 'vpc-id', + name: 'mock-vpc', + description: 'a fake vpc', + dnsName: 'mock-vpc', + timeCreated: new Date(2021, 0, 1).toISOString(), + timeModified: new Date(2021, 0, 2).toISOString(), + projectId: project.id, + systemRouterId: 'router-id', // ??? +} + +export const vpcSubnet: VpcSubnet = { + // this is supposed to be flattened into the top level. will fix in API + identity: { + id: 'vpc-subnet-id', + name: 'mock-subnet', + description: 'a fake subnet', + timeCreated: new Date(2021, 0, 1).toISOString(), + timeModified: new Date(2021, 0, 2).toISOString(), + }, + // supposed to be camelcase, will fix in API + vpc_id: vpc.id, + ipv4_block: '1.1.1.1/24', +} + +export const vpcSubnet2: VpcSubnet = { + identity: { + ...vpcSubnet.identity, + id: 'vpc-subnet-id-2', + name: 'mock-subnet-2', + }, + vpc_id: vpc.id, + ipv4_block: '1.1.1.2/24', +} + +export const vpcSubnets: VpcSubnetResultsPage = { + items: [vpcSubnet], +} From dd3c1e5680eafabc3b2ee3aa2edf8c7a06d0344f Mon Sep 17 00:00:00 2001 From: David Crespo Date: Sat, 8 Jan 2022 23:38:18 -0600 Subject: [PATCH 9/9] cut 50 lines updating everything new with orgName --- .../project/networking/VpcPage/VpcPage.tsx | 14 +++---------- .../networking/VpcPage/modals/vpc-routers.tsx | 12 ++--------- .../networking/VpcPage/modals/vpc-subnets.tsx | 12 ++--------- .../networking/VpcPage/tabs/VpcRoutersTab.tsx | 20 ++++--------------- .../networking/VpcPage/tabs/VpcSubnetsTab.tsx | 20 ++++--------------- 5 files changed, 15 insertions(+), 63 deletions(-) diff --git a/app/pages/project/networking/VpcPage/VpcPage.tsx b/app/pages/project/networking/VpcPage/VpcPage.tsx index c63afd672..dddd2d1a0 100644 --- a/app/pages/project/networking/VpcPage/VpcPage.tsx +++ b/app/pages/project/networking/VpcPage/VpcPage.tsx @@ -17,22 +17,14 @@ import { useApiQuery } from '@oxide/api' const formatDateTime = (s: string) => format(new Date(s), 'MMM d, yyyy H:mm aa') export const VpcPage = () => { - const { orgName, projectName, vpcName } = useParams( - 'orgName', - 'projectName', - 'vpcName' - ) - const { data: vpc } = useApiQuery('projectVpcsGetVpc', { - organizationName: orgName, - projectName, - vpcName, - }) + const vpcParams = useParams('orgName', 'projectName', 'vpcName') + const { data: vpc } = useApiQuery('projectVpcsGetVpc', vpcParams) return ( <> }> - {vpcName} + {vpcParams.vpcName} diff --git a/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx b/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx index 6e6ecd3f2..035732654 100644 --- a/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx +++ b/app/pages/project/networking/VpcPage/modals/vpc-routers.tsx @@ -56,11 +56,7 @@ export function CreateVpcRouterModal({ projectName, vpcName, }: CreateProps) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } + const parentIds = { orgName, projectName, vpcName } const queryClient = useApiQueryClient() function dismiss() { @@ -122,11 +118,7 @@ export function EditVpcRouterModal({ vpcName, originalRouter, }: EditProps) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } + const parentIds = { orgName, projectName, vpcName } const queryClient = useApiQueryClient() function dismiss() { diff --git a/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx b/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx index dbd32c18f..e917f6e9a 100644 --- a/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx +++ b/app/pages/project/networking/VpcPage/modals/vpc-subnets.tsx @@ -67,11 +67,7 @@ export function CreateVpcSubnetModal({ projectName, vpcName, }: CreateProps) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } + const parentIds = { orgName, projectName, vpcName } const queryClient = useApiQueryClient() function dismiss() { @@ -145,11 +141,7 @@ export function EditVpcSubnetModal({ vpcName, originalSubnet, }: EditProps) { - const parentIds = { - organizationName: orgName, - projectName, - vpcName, - } + const parentIds = { orgName, projectName, vpcName } const queryClient = useApiQueryClient() function dismiss() { diff --git a/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx index e2bdebbcf..e2eca6c21 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcRoutersTab.tsx @@ -7,17 +7,9 @@ import type { VpcRouter } from '@oxide/api' import { CreateVpcRouterModal, EditVpcRouterModal } from '../modals/vpc-routers' export const VpcRoutersTab = () => { - const { orgName, projectName, vpcName } = useParams( - 'orgName', - 'projectName', - 'vpcName' - ) + const vpcParams = useParams('orgName', 'projectName', 'vpcName') - const { Table, Column } = useQueryTable('vpcRoutersGet', { - orgName, - projectName, - vpcName, - }) + const { Table, Column } = useQueryTable('vpcRoutersGet', vpcParams) const [createModalOpen, setCreateModalOpen] = useState(false) const [editing, setEditing] = useState(null) @@ -40,16 +32,12 @@ export const VpcRoutersTab = () => { New router setCreateModalOpen(false)} /> setEditing(null)} /> diff --git a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx index 787afada9..45fb2ea7f 100644 --- a/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx +++ b/app/pages/project/networking/VpcPage/tabs/VpcSubnetsTab.tsx @@ -7,17 +7,9 @@ import { CreateVpcSubnetModal, EditVpcSubnetModal } from '../modals/vpc-subnets' import type { VpcSubnet } from '@oxide/api' export const VpcSubnetsTab = () => { - const { orgName, projectName, vpcName } = useParams( - 'orgName', - 'projectName', - 'vpcName' - ) + const vpcParams = useParams('orgName', 'projectName', 'vpcName') - const { Table, Column } = useQueryTable('vpcSubnetsGet', { - orgName, - projectName, - vpcName, - }) + const { Table, Column } = useQueryTable('vpcSubnetsGet', vpcParams) const [createModalOpen, setCreateModalOpen] = useState(false) const [editing, setEditing] = useState(null) @@ -40,16 +32,12 @@ export const VpcSubnetsTab = () => { New subnet setCreateModalOpen(false)} /> setEditing(null)} />