Skip to content

Commit

Permalink
WD-11664 - refactor: remove nested components in ActionsPanel (#1786)
Browse files Browse the repository at this point in the history
  • Loading branch information
vladimir-cucu authored Jul 3, 2024
1 parent 524d693 commit 5abdb50
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 123 deletions.
59 changes: 20 additions & 39 deletions src/panels/ActionsPanel/ActionsPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
import { renderComponent } from "testing/utils";

import ActionsPanel from "./ActionsPanel";
import { Label } from "./types";
import { ConfirmationDialogLabel } from "./ConfirmationDialog";
import { Label as ActionsPanelLabel } from "./types";

const mockResponse = applicationsCharmActionsResultsFactory.build({
results: [
Expand Down Expand Up @@ -120,11 +121,13 @@ describe("ActionsPanel", () => {
state,
});
expect(await screen.findByRole("heading")).toHaveTextContent(
Label.NO_UNITS_SELECTED,
ActionsPanelLabel.NO_UNITS_SELECTED,
);
expect(
await screen.findByTestId("actions-panel-unit-list"),
).toHaveTextContent(`Run action on: ${Label.NO_UNITS_SELECTED}`);
).toHaveTextContent(
`Run action on: ${ActionsPanelLabel.NO_UNITS_SELECTED}`,
);
});

it("disables the submit button if no units are selected", async () => {
Expand Down Expand Up @@ -262,7 +265,9 @@ describe("ActionsPanel", () => {
await screen.findByRole("button", { name: "Run action" }),
);
await userEvent.click(
await screen.findByRole("button", { name: Label.CONFIRM_BUTTON }),
await screen.findByRole("button", {
name: ConfirmationDialogLabel.CONFIRM_BUTTON,
}),
);
const call = executeActionOnUnitsSpy.mock.calls[0];
expect(call[0]).toEqual(["ceph/0", "ceph/1"]);
Expand Down Expand Up @@ -293,7 +298,9 @@ describe("ActionsPanel", () => {
await screen.findByRole("button", { name: "Run action" }),
);
await userEvent.click(
await screen.findByRole("button", { name: Label.CONFIRM_BUTTON }),
await screen.findByRole("button", {
name: ConfirmationDialogLabel.CONFIRM_BUTTON,
}),
);
const call = executeActionOnUnitsSpy.mock.calls[0];
expect(call[0]).toEqual(["ceph/0", "ceph/1"]);
Expand All @@ -305,32 +312,6 @@ describe("ActionsPanel", () => {
executeActionOnUnitsSpy.mockRestore();
});

it("should cancel the run selected action confirmation modal", async () => {
renderComponent(<ActionsPanel />, { path, url, state });
await userEvent.click(
await screen.findByRole("radio", { name: "add-disk" }),
);
await userEvent.type(
await screen.findByRole("textbox", { name: "osd-devices" }),
"new device",
);
expect(
await screen.findByRole("button", { name: "Run action" }),
).not.toBeDisabled();
await userEvent.click(
await screen.findByRole("button", { name: "Run action" }),
);
expect(
screen.queryByRole("dialog", { name: "Run add-disk?" }),
).toBeInTheDocument();
await userEvent.click(
await screen.findByRole("button", { name: Label.CANCEL_BUTTON }),
);
expect(
screen.queryByRole("dialog", { name: "Run add-disk?" }),
).not.toBeInTheDocument();
});

it("should display error when trying to get actions for application", async () => {
const getActionsForApplicationSpy = vi
.fn()
Expand All @@ -348,12 +329,12 @@ describe("ActionsPanel", () => {
expect(getActionsForApplicationSpy).toHaveBeenCalledTimes(1),
);
expect(console.error).toHaveBeenCalledWith(
Label.GET_ACTIONS_ERROR,
ActionsPanelLabel.GET_ACTIONS_ERROR,
new Error("Error while trying to get actions for application!"),
);
await waitFor(() =>
expect(
screen.getByText(Label.GET_ACTIONS_ERROR, { exact: false }),
screen.getByText(ActionsPanelLabel.GET_ACTIONS_ERROR, { exact: false }),
).toBeInTheDocument(),
);
await userEvent.click(
Expand Down Expand Up @@ -387,18 +368,18 @@ describe("ActionsPanel", () => {
await screen.findByRole("button", { name: "Run action" }),
);
await userEvent.click(
await screen.findByRole("button", { name: Label.CONFIRM_BUTTON }),
await screen.findByRole("button", {
name: ConfirmationDialogLabel.CONFIRM_BUTTON,
}),
);
await waitFor(() =>
expect(executeActionOnUnitsSpy).toHaveBeenCalledTimes(1),
);
expect(console.error).toHaveBeenCalledWith(
Label.EXECUTE_ACTION_ERROR,
new Error("Error while trying to execute action on units!"),
);
await waitFor(() =>
expect(
screen.getByText(Label.EXECUTE_ACTION_ERROR, { exact: false }),
screen.getByText(ConfirmationDialogLabel.EXECUTE_ACTION_ERROR, {
exact: false,
}),
).toBeInTheDocument(),
);
});
Expand Down
99 changes: 18 additions & 81 deletions src/panels/ActionsPanel/ActionsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import {
ActionButton,
Button,
ConfirmationModal,
} from "@canonical/react-components";
import { ActionButton, Button } from "@canonical/react-components";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router-dom";
import usePortal from "react-useportal";

import CharmIcon from "components/CharmIcon";
import LoadingHandler from "components/LoadingHandler";
import Panel from "components/Panel";
import RadioInputBox from "components/RadioInputBox";
import type { EntityDetailsRoute } from "components/Routes";
import useInlineErrors from "hooks/useInlineErrors";
import {
useExecuteActionOnUnits,
useGetActionsForApplication,
} from "juju/api-hooks";
import { useGetActionsForApplication } from "juju/api-hooks";
import PanelInlineErrors from "panels/PanelInlineErrors";
import { usePanelQueryParams } from "panels/hooks";
import { ConfirmType, type ConfirmTypes } from "panels/types";
Expand All @@ -27,12 +19,13 @@ import type { RootState } from "store/store";
import { useAppStore } from "store/store";

import ActionOptions from "./ActionOptions";
import ConfirmationDialog from "./ConfirmationDialog";
import type {
ActionData,
ActionOptionValue,
ActionOptionValues,
} from "./types";
import { Label, TestId } from "./types";
import { InlineErrors, Label, TestId } from "./types";
import { enableSubmit, onValuesChange } from "./utils";

type SetSelectedAction = (actionName: string) => void;
Expand All @@ -42,11 +35,6 @@ type ActionsQueryParams = {
units?: string[];
};

enum InlineErrors {
GET_ACTION = "get-action",
EXECUTE_ACTION = "execute-action",
}

export default function ActionsPanel(): JSX.Element {
const appStore = useAppStore();
const appState = appStore.getState();
Expand All @@ -66,7 +54,6 @@ export default function ActionsPanel(): JSX.Element {
userName,
modelName,
);
const executeActionOnUnits = useExecuteActionOnUnits(userName, modelName);
const [selectedAction, setSelectedAction]: [
string | undefined,
SetSelectedAction,
Expand All @@ -89,7 +76,6 @@ export default function ActionsPanel(): JSX.Element {
});
const [isExecutingAction, setIsExecutingAction] = useState<boolean>(false);
const scrollArea = useRef<HTMLDivElement>(null);
const { Portal } = usePortal();

const actionOptionsValues = useRef<ActionOptionValues>({});

Expand Down Expand Up @@ -152,16 +138,6 @@ export default function ActionsPanel(): JSX.Element {
);
};

const executeAction = async () => {
// You shouldn't be able to get this far without this defined but jic.
if (!selectedAction || !modelUUID) return;
await executeActionOnUnits(
selectedUnits,
selectedAction,
actionOptionsValues.current[selectedAction],
);
};

const handleSubmit = () => {
setConfirmType(ConfirmType.SUBMIT);
};
Expand Down Expand Up @@ -194,58 +170,6 @@ export default function ActionsPanel(): JSX.Element {
[actionData, selectedUnits],
);

const generateConfirmationModal = () => {
if (confirmType && selectedAction) {
// Allow for adding more confirmation types, like for cancel
// if inputs have been changed.
if (confirmType === ConfirmType.SUBMIT) {
const unitNames = selectedUnits.reduce((acc, unitName) => {
return `${acc}, ${unitName.split("/")[1]}`;
});
// Render the submit confirmation modal.
return (
<Portal>
<ConfirmationModal
title={`Run ${selectedAction}?`}
cancelButtonLabel={Label.CANCEL_BUTTON}
confirmButtonLabel={Label.CONFIRM_BUTTON}
confirmButtonAppearance="positive"
onConfirm={() => {
setConfirmType(null);
setIsExecutingAction(true);
executeAction()
.then(() => {
handleRemovePanelQueryParams();
return;
})
.catch((error) => {
setInlineErrors(
InlineErrors.EXECUTE_ACTION,
Label.EXECUTE_ACTION_ERROR,
);
setIsExecutingAction(false);
console.error(Label.EXECUTE_ACTION_ERROR, error);
});
}}
close={() => setConfirmType(null)}
>
<h4 className="p-muted-heading u-no-margin--bottom">
UNIT COUNT
</h4>
<p data-testid="confirmation-modal-unit-count">
{selectedUnits.length}
</p>
<h4 className="p-muted-heading u-no-margin--bottom u-no-padding--top">
UNIT NAME
</h4>
<p data-testid="confirmation-modal-unit-names">{unitNames}</p>
</ConfirmationModal>
</Portal>
);
}
}
};

const data = Object.keys(actionData).length > 0 ? actionData : null;

return (
Expand Down Expand Up @@ -298,7 +222,20 @@ export default function ActionsPanel(): JSX.Element {
</RadioInputBox>
))}
</LoadingHandler>
{generateConfirmationModal()}
{selectedAction && confirmType ? (
<ConfirmationDialog
confirmType={confirmType}
selectedAction={selectedAction}
selectedUnits={selectedUnits}
setConfirmType={setConfirmType}
setIsExecutingAction={setIsExecutingAction}
selectedActionOptionValue={
actionOptionsValues.current[selectedAction]
}
handleRemovePanelQueryParams={handleRemovePanelQueryParams}
setInlineErrors={setInlineErrors}
/>
) : null}
</Panel>
);
}
Loading

0 comments on commit 5abdb50

Please sign in to comment.