Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [M3-6735] - VPC Edit drawer #9528

Merged
merged 11 commits into from
Aug 11, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

VPC Edit drawer ([#9528](https://github.com/linode/manager/pull/9528))
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToCo
import { useDeleteVPCMutation } from 'src/queries/vpcs';

interface Props {
id: number;
label: string;
id?: number;
label?: string;
onClose: () => void;
open: boolean;
}

export const VPCDeleteDialog = (props: Props) => {
const { id, label, onClose, open } = props;
const { enqueueSnackbar } = useSnackbar();
const { error, isLoading, mutateAsync: deleteVPC } = useDeleteVPCMutation(id);
const { error, isLoading, mutateAsync: deleteVPC } = useDeleteVPCMutation(
id ?? -1
);

const onDeleteVPC = () => {
deleteVPC().then(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';

import { vpcFactory } from 'src/factories/vpcs';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { VPCEditDrawer } from './VPCEditDrawer';

describe('Edit VPC Drawer', () => {
const props = {
onClose: jest.fn(),
open: true,
vpc: vpcFactory.build(),
};

it('Should render a title, label input, description input, and action buttons', () => {
const { getAllByTestId, getByTestId, getByText } = renderWithTheme(
<VPCEditDrawer {...props} />
);
const drawerTitle = getByText('Edit VPC');
expect(drawerTitle).toBeVisible();

const inputs = getAllByTestId('textfield-input');

const label = getByText('Label');
const labelInput = inputs[0];
expect(label).toBeVisible();
expect(labelInput).toBeEnabled();

const description = getByText('Description');
const descriptionInput = inputs[1];
expect(description).toBeVisible();
expect(descriptionInput).toBeEnabled();

const saveButton = getByTestId('save-button');
expect(saveButton).toBeVisible();

const cancelBtn = getByText(/Cancel/);
expect(cancelBtn).toBeEnabled();
expect(cancelBtn).toBeVisible();
});
});
97 changes: 97 additions & 0 deletions packages/manager/src/features/VPC/VPCLanding/VPCEditDrawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { UpdateVPCPayload, VPC } from '@linode/api-v4/lib/vpcs/types';
import { useFormik } from 'formik';
import * as React from 'react';

import { ActionsPanel } from 'src/components/ActionsPanel/ActionsPanel';
import { Drawer } from 'src/components/Drawer';
import { RegionSelect } from 'src/components/EnhancedSelect/variants/RegionSelect';
import { Notice } from 'src/components/Notice/Notice';
import { TextField } from 'src/components/TextField';
import { useRegionsQuery } from 'src/queries/regions';
import { useUpdateVPCMutation } from 'src/queries/vpcs';
import { getErrorMap } from 'src/utilities/errorUtils';

interface Props {
onClose: () => void;
open: boolean;
vpc?: VPC;
}

const REGION_HELPER_TEXT = 'Region cannot be changed during beta.';

export const VPCEditDrawer = (props: Props) => {
const { onClose, open, vpc } = props;

const {
error,
isLoading,
mutateAsync: updateVPC,
reset,
} = useUpdateVPCMutation(vpc?.id ?? -1);

const form = useFormik<UpdateVPCPayload>({
enableReinitialize: true,
initialValues: {
description: vpc?.description,
label: vpc?.label,
},
async onSubmit(values) {
await updateVPC(values);
onClose();
},
});

React.useEffect(() => {
if (open) {
form.resetForm();
reset();
}
}, [open]);

const { data: regionsData, error: regionsError } = useRegionsQuery();

const errorMap = getErrorMap(['label', 'description'], error);

return (
<Drawer onClose={onClose} open={open} title="Edit VPC">
{errorMap.none && <Notice error text={errorMap.none} />}
<form onSubmit={form.handleSubmit}>
<TextField
errorText={errorMap.label}
label="Label"
name="label"
onChange={form.handleChange}
value={form.values.label}
/>
<TextField
errorText={errorMap.description}
label="Description"
multiline
onChange={form.handleChange}
rows={1}
value={form.values.description}
/>
{regionsData && (
<RegionSelect
disabled // the Region field will not be editable during beta
errorText={(regionsError && regionsError[0].reason) || undefined}
handleSelection={() => null}
helperText={REGION_HELPER_TEXT}
regions={regionsData}
selectedID={vpc?.region ?? null}
/>
)}
<ActionsPanel
primaryButtonProps={{
'data-testid': 'save-button',
disabled: !form.dirty,
label: 'Save',
loading: isLoading,
type: 'submit',
}}
secondaryButtonProps={{ label: 'Cancel', onClick: onClose }}
/>
</form>
</Drawer>
);
};
44 changes: 25 additions & 19 deletions packages/manager/src/features/VPC/VPCLanding/VPCLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { usePagination } from 'src/hooks/usePagination';
import { useVPCsQuery } from 'src/queries/vpcs';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';

import { VPCEditDrawer } from './VPCEditDrawer';
import { VPCEmptyState } from './VPCEmptyState';
import { VPCRow } from './VPCRow';

Expand Down Expand Up @@ -51,25 +52,19 @@ const VPCLanding = () => {

const history = useHistory();

const [deleteDialog, setDeleteDialog] = React.useState({
id: -1,
label: '',
open: false,
});

const handleDeleteVPC = (id: number, label: string) => {
setDeleteDialog({
id,
label,
open: true,
});
const [selectedVPC, setSelectedVPC] = React.useState<VPC | undefined>();

const [editVPCDrawerOpen, setEditVPCDrawerOpen] = React.useState(false);
const [deleteVPCDialogOpen, setDeleteVPCDialogOpen] = React.useState(false);

const handleEditVPC = (vpc: VPC) => {
setSelectedVPC(vpc);
setEditVPCDrawerOpen(true);
};

const closeDeleteDialog = () => {
setDeleteDialog((deleteDialog) => ({
...deleteDialog,
open: false,
}));
const handleDeleteVPC = (vpc: VPC) => {
setSelectedVPC(vpc);
setDeleteVPCDialogOpen(true);
};

const createVPC = () => {
Expand Down Expand Up @@ -144,7 +139,8 @@ const VPCLanding = () => {
<TableBody>
{vpcs?.data.map((vpc: VPC) => (
<VPCRow
handleDeleteVPC={() => handleDeleteVPC(vpc.id, vpc.label)}
handleDeleteVPC={() => handleDeleteVPC(vpc)}
handleEditVPC={() => handleEditVPC(vpc)}
key={vpc.id}
vpc={vpc}
/>
Expand All @@ -159,7 +155,17 @@ const VPCLanding = () => {
page={pagination.page}
pageSize={pagination.pageSize}
/>
<VPCDeleteDialog {...deleteDialog} onClose={closeDeleteDialog} />
<VPCDeleteDialog
id={selectedVPC?.id}
label={selectedVPC?.label}
onClose={() => setDeleteVPCDialogOpen(false)}
open={deleteVPCDialogOpen}
/>
<VPCEditDrawer
onClose={() => setEditVPCDrawerOpen(false)}
open={editVPCDrawerOpen}
vpc={selectedVPC}
/>
</>
);
};
Expand Down
33 changes: 31 additions & 2 deletions packages/manager/src/features/VPC/VPCLanding/VPCRow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ describe('VPC Table Row', () => {
const vpc = vpcFactory.build();

const { getAllByText, getByText } = renderWithTheme(
wrapWithTableBody(<VPCRow handleDeleteVPC={jest.fn()} vpc={vpc} />)
wrapWithTableBody(
<VPCRow
handleDeleteVPC={jest.fn()}
handleEditVPC={jest.fn()}
vpc={vpc}
/>
)
);

// Check to see if the row rendered some data
Expand All @@ -27,10 +33,33 @@ describe('VPC Table Row', () => {
const vpc = vpcFactory.build();
const handleDelete = jest.fn();
const { getAllByRole } = renderWithTheme(
wrapWithTableBody(<VPCRow handleDeleteVPC={handleDelete} vpc={vpc} />)
wrapWithTableBody(
<VPCRow
handleDeleteVPC={handleDelete}
handleEditVPC={jest.fn()}
vpc={vpc}
/>
)
);
const deleteBtn = getAllByRole('button')[1];
fireEvent.click(deleteBtn);
expect(handleDelete).toHaveBeenCalled();
});

it('should have an edit button that calls the provided callback when clicked', () => {
const vpc = vpcFactory.build();
const handleEdit = jest.fn();
const { getAllByRole } = renderWithTheme(
wrapWithTableBody(
<VPCRow
handleDeleteVPC={jest.fn()}
handleEditVPC={handleEdit}
vpc={vpc}
/>
)
);
const editButton = getAllByRole('button')[0];
fireEvent.click(editButton);
expect(handleEdit).toHaveBeenCalled();
});
});
5 changes: 3 additions & 2 deletions packages/manager/src/features/VPC/VPCLanding/VPCRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import { useRegionsQuery } from 'src/queries/regions';

interface Props {
handleDeleteVPC: () => void;
handleEditVPC: () => void;
vpc: VPC;
}

export const VPCRow = ({ handleDeleteVPC, vpc }: Props) => {
export const VPCRow = ({ handleDeleteVPC, handleEditVPC, vpc }: Props) => {
const { id, label, subnets } = vpc;
const { data: regions } = useRegionsQuery();

Expand All @@ -26,7 +27,7 @@ export const VPCRow = ({ handleDeleteVPC, vpc }: Props) => {

const actions: Action[] = [
{
onClick: () => null,
onClick: handleEditVPC,
title: 'Edit',
},
{
Expand Down
3 changes: 3 additions & 0 deletions packages/manager/src/mocks/serverHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,9 @@ const vpc = [
rest.delete('*/vpcs/:vpcId', (req, res, ctx) => {
return res(ctx.json({}));
}),
rest.put('*/vpcs/:vpcId', (req, res, ctx) => {
return res(ctx.json(vpcFactory.build({ description: 'testing' })));
}),
];

const nanodeType = linodeTypeFactory.build({ id: 'g6-nanode-1' });
Expand Down