Skip to content

Commit 1480b3d

Browse files
feat(zones): Add delete zones as a table action MAASENG-4308 (#5590)
1 parent b12b50b commit 1480b3d

File tree

13 files changed

+137
-227
lines changed

13 files changed

+137
-227
lines changed

src/app/store/utils/node/base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ const sidePanelTitleMap: Record<string, string> = {
213213
[SidePanelViews.DOWNLOAD_IMAGE[1]]: "Download image",
214214
[SidePanelViews.EDIT_INTERFACE[1]]: "Edit interface",
215215
[SidePanelViews.CREATE_ZONE[1]]: "Add AZ",
216+
[SidePanelViews.DELETE_ZONE[1]]: "Delete AZ",
216217
[SidePanelViews.EDIT_DISK[1]]: "Edit disk",
217218
[SidePanelViews.EDIT_PARTITION[1]]: "Edit partition",
218219
[SidePanelViews.EDIT_PHYSICAL[1]]: "Edit physical",

src/app/zones/constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import type { SidePanelContent } from "@/app/base/types";
44

55
export const ZoneActionSidePanelViews = {
66
CREATE_ZONE: ["zoneForm", "createZone"],
7+
DELETE_ZONE: ["zoneForm", "deleteZone"],
78
} as const;
89

910
export type ZoneSidePanelContent = SidePanelContent<
10-
ValueOf<typeof ZoneActionSidePanelViews>
11+
ValueOf<typeof ZoneActionSidePanelViews>,
12+
{ zoneId: number }
1113
>;

src/app/zones/views/ZoneDetails/ZoneDetailsHeader/DeleteConfirm/DeleteConfirm.test.tsx

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/app/zones/views/ZoneDetails/ZoneDetailsHeader/DeleteConfirm/DeleteConfirm.tsx

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/app/zones/views/ZoneDetails/ZoneDetailsHeader/DeleteConfirm/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/app/zones/views/ZoneDetails/ZoneDetailsHeader/ZoneDetailsHeader.test.tsx

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -57,44 +57,4 @@ describe("ZoneDetailsHeader", () => {
5757
);
5858
expect(await findByText("Availability zone not found")).toBeInTheDocument();
5959
});
60-
61-
it("shows delete az button when zone id isn't 1", async () => {
62-
renderWithBrowserRouter(<ZoneDetailsHeader id={2} />, {
63-
state,
64-
queryData,
65-
route: "/zone/2",
66-
});
67-
68-
expect(
69-
await screen.findByRole("button", { name: "Delete AZ" })
70-
).toBeInTheDocument();
71-
});
72-
73-
it("hides delete button when zone id is 1 (as this is the default)", () => {
74-
renderWithBrowserRouter(<ZoneDetailsHeader id={1} />, {
75-
state,
76-
queryData,
77-
route: "/zone/1",
78-
});
79-
80-
expect(screen.queryByTestId("delete-zone")).not.toBeInTheDocument();
81-
});
82-
83-
it("hides delete button for all zones when user isn't admin", () => {
84-
const nonAdminState = factory.rootState({
85-
user: factory.userState({
86-
auth: factory.authState({
87-
user: factory.user({ is_superuser: false }),
88-
}),
89-
}),
90-
});
91-
92-
renderWithBrowserRouter(<ZoneDetailsHeader id={2} />, {
93-
state: nonAdminState,
94-
queryData,
95-
route: "/zone/2",
96-
});
97-
98-
expect(screen.queryByTestId("delete-zone")).not.toBeInTheDocument();
99-
});
10060
});
Lines changed: 4 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
1-
import { useEffect, useState } from "react";
2-
3-
import { Button } from "@canonical/react-components";
4-
import { useSelector, useDispatch } from "react-redux";
5-
import { useNavigate } from "react-router-dom";
6-
7-
import DeleteConfirm from "./DeleteConfirm";
1+
import React from "react";
82

93
import { useZoneById } from "@/app/api/query/zones";
104
import SectionHeader from "@/app/base/components/SectionHeader";
11-
import urls from "@/app/base/urls";
12-
import authSelectors from "@/app/store/auth/selectors";
13-
import type { RootState } from "@/app/store/root/types";
14-
import { zoneActions } from "@/app/store/zone";
15-
import { ZONE_ACTIONS } from "@/app/store/zone/constants";
16-
import zoneSelectors from "@/app/store/zone/selectors";
175

186
type Props = {
197
id: number;
208
};
219

22-
const ZoneDetailsHeader = ({ id }: Props): JSX.Element => {
23-
const [showConfirm, setShowConfirm] = useState(false);
24-
const deleteStatus = useSelector((state: RootState) =>
25-
zoneSelectors.getModelActionStatus(state, ZONE_ACTIONS.delete, id)
26-
);
10+
const ZoneDetailsHeader: React.FC<Props> = ({ id }) => {
2711
const zone = useZoneById(id);
28-
const dispatch = useDispatch();
29-
const navigate = useNavigate();
12+
3013
let title = "";
3114

3215
if (!zone.isPending) {
@@ -35,73 +18,13 @@ const ZoneDetailsHeader = ({ id }: Props): JSX.Element => {
3518
: "Availability zone not found";
3619
}
3720

38-
useEffect(() => {
39-
if (deleteStatus === "success") {
40-
dispatch(zoneActions.cleanup([ZONE_ACTIONS.delete]));
41-
navigate({ pathname: urls.zones.index });
42-
}
43-
}, [dispatch, deleteStatus, navigate]);
44-
45-
const isAdmin = useSelector(authSelectors.isAdmin);
46-
const isDefaultZone = id === 1;
47-
48-
const deleteZone = () => {
49-
if (isAdmin && !isDefaultZone) {
50-
dispatch(zoneActions.delete({ id }));
51-
}
52-
};
53-
54-
const closeExpanded = () => setShowConfirm(false);
55-
56-
let buttons: JSX.Element[] | null = [
57-
<Button
58-
data-testid="delete-zone"
59-
key="delete-zone"
60-
onClick={() => setShowConfirm(true)}
61-
>
62-
Delete AZ
63-
</Button>,
64-
];
65-
66-
if (showConfirm || isDefaultZone || !isAdmin) {
67-
buttons = null;
68-
}
69-
70-
let confirmDelete = null;
71-
72-
if (showConfirm && isAdmin && !isDefaultZone) {
73-
confirmDelete = (
74-
<>
75-
<hr />
76-
<DeleteConfirm
77-
closeExpanded={closeExpanded}
78-
confirmLabel="Delete AZ"
79-
deleting={deleteStatus === "loading"}
80-
message="Are you sure you want to delete this AZ?"
81-
onConfirm={deleteZone}
82-
/>
83-
</>
84-
);
85-
}
86-
8721
if (!zone.isPending && zone.data) {
8822
title = `Availability zone: ${zone.data.name}`;
8923
} else if (zone.isFetched) {
9024
title = "Availability zone not found";
91-
buttons = null;
9225
}
9326

94-
return (
95-
<>
96-
<SectionHeader
97-
buttons={buttons}
98-
loading={!zone.isFetched}
99-
title={title}
100-
/>
101-
102-
{confirmDelete}
103-
</>
104-
);
27+
return <SectionHeader loading={!zone.isFetched} title={title} />;
10528
};
10629

10730
export default ZoneDetailsHeader;

src/app/zones/views/ZonesList/ZonesList.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import React from "react";
2+
13
import ZonesListForm from "./ZonesListForm";
24
import ZonesListHeader from "./ZonesListHeader";
35
import ZonesListTable from "./ZonesListTable";
@@ -6,9 +8,11 @@ import { useZoneCount } from "@/app/api/query/zones";
68
import PageContent from "@/app/base/components/PageContent";
79
import { useWindowTitle } from "@/app/base/hooks";
810
import { useSidePanel } from "@/app/base/side-panel-context";
11+
import { getSidePanelTitle } from "@/app/store/utils/node/base";
912
import { ZoneActionSidePanelViews } from "@/app/zones/constants";
13+
import DeleteZone from "@/app/zones/views/ZonesList/ZonesListTable/DeleteZone";
1014

11-
const ZonesList = (): JSX.Element => {
15+
const ZonesList: React.FC = () => {
1216
const zonesCount = useZoneCount();
1317
const { sidePanelContent, setSidePanelContent } = useSidePanel();
1418

@@ -28,13 +32,29 @@ const ZonesList = (): JSX.Element => {
2832
key="add-zone-form"
2933
/>
3034
);
35+
} else if (
36+
sidePanelContent &&
37+
sidePanelContent.view === ZoneActionSidePanelViews.DELETE_ZONE
38+
) {
39+
const zoneId =
40+
sidePanelContent.extras && "zoneId" in sidePanelContent.extras
41+
? sidePanelContent.extras.zoneId
42+
: null;
43+
content = zoneId ? (
44+
<DeleteZone
45+
closeForm={() => {
46+
setSidePanelContent(null);
47+
}}
48+
id={zoneId as number}
49+
/>
50+
) : null;
3151
}
3252

3353
return (
3454
<PageContent
3555
header={<ZonesListHeader setSidePanelContent={setSidePanelContent} />}
3656
sidePanelContent={content}
37-
sidePanelTitle="Add AZ"
57+
sidePanelTitle={getSidePanelTitle("Zones", sidePanelContent)}
3858
>
3959
{zonesCount?.data && zonesCount.data > 0 && <ZonesListTable />}
4060
</PageContent>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Formik } from "formik";
2+
3+
import DeleteZone from "./DeleteZone";
4+
5+
import { userEvent, screen, renderWithBrowserRouter } from "@/testing/utils";
6+
7+
describe("DeleteZone", () => {
8+
it("calls closeForm on cancel click", async () => {
9+
const closeForm = vi.fn();
10+
renderWithBrowserRouter(
11+
<Formik initialValues={{ images: [] }} onSubmit={vi.fn()}>
12+
<DeleteZone closeForm={closeForm} id={2} />
13+
</Formik>
14+
);
15+
16+
await userEvent.click(screen.getByRole("button", { name: "Cancel" }));
17+
expect(closeForm).toHaveBeenCalled();
18+
});
19+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from "react";
2+
3+
import { useDispatch, useSelector } from "react-redux";
4+
5+
import ModelActionForm from "@/app/base/components/ModelActionForm";
6+
import type { RootState } from "@/app/store/root/types";
7+
import { zoneActions } from "@/app/store/zone";
8+
import { ZONE_ACTIONS } from "@/app/store/zone/constants";
9+
import zoneSelectors from "@/app/store/zone/selectors";
10+
11+
type DeleteZoneProps = {
12+
closeForm: () => void;
13+
id: number;
14+
};
15+
16+
const DeleteZone: React.FC<DeleteZoneProps> = ({ closeForm, id }) => {
17+
const dispatch = useDispatch();
18+
const deleteStatus = useSelector((state: RootState) =>
19+
zoneSelectors.getModelActionStatus(state, ZONE_ACTIONS.delete, id)
20+
);
21+
22+
return (
23+
<ModelActionForm
24+
aria-label="Confirm AZ deletion"
25+
initialValues={{}}
26+
message="Are you sure you want to delete this AZ?"
27+
modelType="zone"
28+
onCancel={closeForm}
29+
onSubmit={() => {
30+
dispatch(zoneActions.delete({ id }));
31+
}}
32+
onSuccess={() => {
33+
dispatch(zoneActions.cleanup([ZONE_ACTIONS.delete]));
34+
closeForm();
35+
}}
36+
saved={deleteStatus === "success"}
37+
saving={deleteStatus === "loading"}
38+
/>
39+
);
40+
};
41+
42+
export default DeleteZone;

0 commit comments

Comments
 (0)