Skip to content

Commit

Permalink
feat: move forms to sidepanel
Browse files Browse the repository at this point in the history
  • Loading branch information
Jay-Topher committed Jan 9, 2024
1 parent 54d2134 commit 6d8ff61
Show file tree
Hide file tree
Showing 121 changed files with 2,769 additions and 2,383 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { HTMLProps, ReactNode } from "react";

import { Col, Row } from "@canonical/react-components";
import type { ColSize } from "@canonical/react-components";
import classNames from "classnames";

import NotificationList from "app/base/components/NotificationList";
import { COL_SIZES } from "app/base/constants";
Expand Down Expand Up @@ -35,7 +36,12 @@ const MainContentSection = ({
{sidebar}
</Col>
)}
<Col size={(sidebar ? TOTAL - SIDEBAR : TOTAL) as ColSize}>
<Col
className={classNames({
"u-nudge-down": !isNotificationListHidden,
})}
size={(sidebar ? TOTAL - SIDEBAR : TOTAL) as ColSize}
>
{!isNotificationListHidden && <NotificationList />}
{children}
</Col>
Expand Down
49 changes: 49 additions & 0 deletions src/app/base/components/ModelDeleteForm/ModelDeleteForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import ModelDeleteForm from "./ModelDeleteForm";

import { renderWithBrowserRouter, screen, userEvent } from "testing/utils";

it("renders", () => {
renderWithBrowserRouter(
<ModelDeleteForm
initialValues={{}}
modelType="machine"
onSubmit={jest.fn()}
submitLabel="Delete"
/>
);
expect(
screen.getByText("Are you sure you want to delete this machine?")
).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Delete" })).toBeInTheDocument();
});

it("can confirm", async () => {
const onSubmit = jest.fn();
renderWithBrowserRouter(
<ModelDeleteForm
initialValues={{}}
modelType="machine"
onSubmit={onSubmit}
submitLabel="Delete"
/>
);
const submitBtn = screen.getByRole("button", { name: /delete/i });
await userEvent.click(submitBtn);
expect(onSubmit).toHaveBeenCalled();
});

it("can cancel", async () => {
const onCancel = jest.fn();
renderWithBrowserRouter(
<ModelDeleteForm
cancelLabel="Cancel"
initialValues={{}}
modelType="machine"
onCancel={onCancel}
onSubmit={jest.fn()}
/>
);
const cancelBtn = screen.getByRole("button", { name: /cancel/i });
await userEvent.click(cancelBtn);
expect(onCancel).toHaveBeenCalled();
});
43 changes: 43 additions & 0 deletions src/app/base/components/ModelDeleteForm/ModelDeleteForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Col, Row } from "@canonical/react-components";

import type { Props as FormikFormProps } from "app/base/components/FormikForm/FormikForm";
import FormikForm from "app/base/components/FormikForm/FormikForm";
import type { EmptyObject } from "app/base/types";

type Props = {
modelType: string;
message?: string;
} & FormikFormProps<EmptyObject>;

const ModelDeleteForm = ({
modelType,
message,
submitAppearance = "negative",
submitLabel = "Delete",
initialValues = {},
...props
}: Props) => {
return (
<FormikForm
initialValues={initialValues}
submitAppearance={submitAppearance}
submitLabel={submitLabel}
{...props}
>
<Row>
<Col size={12}>
<p className="u-nudge-down--small">
{message
? message
: `Are you sure you want to delete this ${modelType}?`}
</p>
<span className="u-text--light">
This action is permanent and can not be undone.
</span>
</Col>
</Row>
</FormikForm>
);
};

export default ModelDeleteForm;
1 change: 1 addition & 0 deletions src/app/base/components/ModelDeleteForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./ModelDeleteForm";
38 changes: 0 additions & 38 deletions src/app/base/components/NetworkActionRow/NetworkActionRow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
rootState as rootStateFactory,
} from "testing/factories";
import {
userEvent,
screen,
renderWithBrowserRouter,
expectTooltipOnHover,
Expand All @@ -37,7 +36,6 @@ describe("NetworkActionRow", () => {
const store = mockStore(state);
renderWithBrowserRouter(
<NetworkActionRow
expanded={null}
extraActions={[
{
disabled: [[false]],
Expand All @@ -54,31 +52,11 @@ describe("NetworkActionRow", () => {
});

describe("add physical", () => {
it("sets the state to show the form when clicking the button", async () => {
const store = mockStore(state);
const setExpanded = jest.fn();
renderWithBrowserRouter(
<NetworkActionRow
expanded={null}
node={state.machine.items[0]}
setExpanded={setExpanded}
/>,
{ route: "/machine/abc123", store }
);
await userEvent.click(
screen.getByRole("button", { name: "Add interface" })
);
expect(setExpanded).toHaveBeenCalledWith({
content: ExpandedState.ADD_PHYSICAL,
});
});

it("disables the button when networking is disabled", async () => {
state.machine.items[0].status = NodeStatus.DEPLOYED;
const store = mockStore(state);
renderWithBrowserRouter(
<NetworkActionRow
expanded={null}
node={state.machine.items[0]}
setExpanded={jest.fn()}
/>,
Expand All @@ -93,21 +71,5 @@ describe("NetworkActionRow", () => {
"Network can't be modified for this machine."
);
});

it("disables the button when the form is expanded", () => {
state.machine.items[0].status = NodeStatus.DEPLOYED;
const store = mockStore(state);
renderWithBrowserRouter(
<NetworkActionRow
expanded={{ content: ExpandedState.ADD_PHYSICAL }}
node={state.machine.items[0]}
setExpanded={jest.fn()}
/>,
{ route: "/machine/abc123", store }
);
expect(
screen.getByRole("button", { name: "Add interface" })
).toBeDisabled();
});
});
});
54 changes: 43 additions & 11 deletions src/app/base/components/NetworkActionRow/NetworkActionRow.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { ReactNode } from "react";

import { Button, Col, List, Row, Tooltip } from "@canonical/react-components";
import { useLocation } from "react-router-dom";

import type { Expanded, SetExpanded } from "../NodeNetworkTab/NodeNetworkTab";
import type { SetExpanded } from "../NodeNetworkTab/NodeNetworkTab";
import { ExpandedState } from "../NodeNetworkTab/NodeNetworkTab";

import type {
Selected,
SetSelected,
} from "app/base/components/node/networking/types";
import { useIsAllNetworkingDisabled } from "app/base/hooks";
import { useSidePanel } from "app/base/side-panel-context";
import { DeviceSidePanelViews } from "app/devices/constants";
import { MachineSidePanelViews } from "app/machines/constants";
import type { Node } from "app/store/types/node";

type Action = {
Expand All @@ -15,38 +23,64 @@ type Action = {
};

type Props = {
expanded: Expanded | null;
extraActions?: Action[];
node: Node;
rightContent?: ReactNode;
setExpanded: SetExpanded;
selected?: Selected[];
setSelected?: SetSelected;
};

export const NETWORK_DISABLED_MESSAGE =
"Network can't be modified for this machine.";

const NetworkActionRow = ({
expanded,
extraActions,
node,
rightContent,
setExpanded,
selected,
setSelected,
}: Props): JSX.Element | null => {
const isAllNetworkingDisabled = useIsAllNetworkingDisabled(node);
const { setSidePanelContent } = useSidePanel();
const { pathname } = useLocation();
const isMachinesPage = pathname.startsWith("/machine");

const actions: Action[] = [
{
disabled: [
[isAllNetworkingDisabled, NETWORK_DISABLED_MESSAGE],
// Disable the button when the form is visible.
[expanded?.content === ExpandedState.ADD_PHYSICAL],
],
disabled: [[isAllNetworkingDisabled, NETWORK_DISABLED_MESSAGE]],
label: "Add interface",
state: ExpandedState.ADD_PHYSICAL,
},
...(extraActions || []),
];

const handleButtonClick = (state: ExpandedState) => {
if (state === ExpandedState.ADD_PHYSICAL) {
if (isMachinesPage) {
setSidePanelContent({
view: MachineSidePanelViews.ADD_INTERFACE,
extras: { systemId: node.system_id },
});
} else {
setSidePanelContent({ view: DeviceSidePanelViews.ADD_INTERFACE });
}
} else if (state === ExpandedState.ADD_BOND) {
setSidePanelContent({
view: MachineSidePanelViews.ADD_BOND,
extras: { systemId: node.system_id, selected: selected, setSelected },
});
} else if (state === ExpandedState.ADD_BRIDGE) {
setSidePanelContent({
view: MachineSidePanelViews.ADD_BRIDGE,
extras: { systemId: node.system_id, selected: selected, setSelected },
});
} else {
setExpanded({ content: state });
}
};

const buttons = actions.map((item) => {
// Check if there is any reason to disable the button.
const [disabled, tooltip] =
Expand All @@ -55,9 +89,7 @@ const NetworkActionRow = ({
<Button
data-testid={item.state}
disabled={disabled}
onClick={() => {
setExpanded({ content: item.state });
}}
onClick={() => handleButtonClick(item.state)}
>
{item.label}
</Button>
Expand Down
2 changes: 1 addition & 1 deletion src/app/base/components/SSHKeyForm/SSHKeyForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const SSHKeyForm = ({ cols, ...props }: Props): JSX.Element => {
validationSchema={SSHKeySchema}
{...props}
>
<SSHKeyFormFields cols={cols} />
<SSHKeyFormFields />
</FormikForm>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
import { ExternalLink } from "@canonical/maas-react-components";
import { Col, Row, Select, Textarea } from "@canonical/react-components";
import type { ColSize } from "@canonical/react-components";
import { useFormikContext } from "formik";

import type { SSHKeyFormValues } from "../types";

import FormikField from "app/base/components/FormikField";
import TooltipButton from "app/base/components/TooltipButton";
import { COL_SIZES } from "app/base/constants";
import docsUrls from "app/base/docsUrls";

type Props = {
cols?: number;
};

export const SSHKeyFormFields = ({
cols = COL_SIZES.TOTAL,
}: Props): JSX.Element => {
export const SSHKeyFormFields = (): JSX.Element => {
const { values } = useFormikContext<SSHKeyFormValues>();
const { protocol } = values;
const uploadSelected = protocol === "upload";
const colSize = cols / 2;

return (
<>
<Row>
<Col size={Math.ceil(colSize) as ColSize}>
<Col size={12}>
<FormikField
component={Select}
label="Source"
Expand Down Expand Up @@ -67,7 +58,7 @@ export const SSHKeyFormFields = ({
/>
)}
</Col>
<Col size={Math.floor(colSize) as ColSize}>
<Col size={12}>
<p className="form-card__help">
Before you can deploy a machine you must import at least one public
SSH key into MAAS, so the deployed machine can be accessed.
Expand Down
10 changes: 7 additions & 3 deletions src/app/base/components/TableActions/TableActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type Props = {
copyValue?: string;
deleteDisabled?: boolean;
deleteTooltip?: string | null;
deletePath?: string;
editDisabled?: boolean;
editPath?: string;
editTooltip?: string | null;
Expand All @@ -22,6 +23,7 @@ const TableActions = ({
clearTooltip,
copyValue,
deleteDisabled,
deletePath,
deleteTooltip,
editDisabled,
editPath,
Expand All @@ -48,16 +50,18 @@ const TableActions = ({
</Button>
</Tooltip>
)}
{onDelete && (
{(onDelete || deletePath) && (
<Tooltip message={deleteTooltip} position="left">
<Button
appearance="base"
className="is-dense u-table-cell-padding-overlap"
data-testid="table-actions-delete"
disabled={deleteDisabled}
element={deletePath ? Link : undefined}
hasIcon
onClick={() => onDelete()}
type="button"
onClick={() => (onDelete ? onDelete() : null)}
to={deletePath || ""}
type={deletePath ? undefined : "button"}
>
<i className="p-icon--delete">Delete</i>
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,6 @@ const AvailableStorageTable = ({
)}
{isMachine && canEditStorage && (
<BulkActions
bulkAction={bulkAction}
selected={selected}
setBulkAction={setBulkAction}
systemId={node.system_id}
Expand Down
Loading

0 comments on commit 6d8ff61

Please sign in to comment.