From b296b57d19318545199e71b43d9be20537498399 Mon Sep 17 00:00:00 2001 From: Vladimir Cucu <108150922+vladimir-cucu@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:00:22 +0300 Subject: [PATCH] WD-11666 - refactor: remove nested components in ConfigPanel (#1769) --- src/panels/ConfigPanel/ConfigPanel.test.tsx | 301 ++++------ src/panels/ConfigPanel/ConfigPanel.tsx | 214 +------- .../ConfirmationDialog.test.tsx | 514 ++++++++++++++++++ .../ConfirmationDialog/ConfirmationDialog.tsx | 201 +++++++ .../ConfigPanel/ConfirmationDialog/index.ts | 1 + .../ConfigPanel/ConfirmationDialog/types.ts | 18 + src/panels/ConfigPanel/types.ts | 30 +- 7 files changed, 867 insertions(+), 412 deletions(-) create mode 100644 src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx create mode 100644 src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx create mode 100644 src/panels/ConfigPanel/ConfirmationDialog/index.ts create mode 100644 src/panels/ConfigPanel/ConfirmationDialog/types.ts diff --git a/src/panels/ConfigPanel/ConfigPanel.test.tsx b/src/panels/ConfigPanel/ConfigPanel.test.tsx index 9d0ffb0e2..46df39fc9 100644 --- a/src/panels/ConfigPanel/ConfigPanel.test.tsx +++ b/src/panels/ConfigPanel/ConfigPanel.test.tsx @@ -34,7 +34,8 @@ import { rootStateFactory } from "testing/factories/root"; import { renderComponent } from "testing/utils"; import ConfigPanel from "./ConfigPanel"; -import { Label } from "./types"; +import { Label as ConfirmationDialogLabel } from "./ConfirmationDialog/types"; +import { Label as ConfigPanelLabel } from "./types"; vi.mock("juju/api-hooks/application", () => ({ useGetApplicationConfig: vi.fn(), @@ -154,7 +155,7 @@ describe("ConfigPanel", () => { ); renderComponent(, { state, path, url }); // Use findBy to wait for the async events to finish - await screen.findByText(Label.NONE); + await screen.findByText(ConfigPanelLabel.NONE); expect(document.querySelector(".config-panel__message")).toMatchSnapshot(); }); @@ -205,7 +206,7 @@ describe("ConfigPanel", () => { expect(email).toHaveTextContent("eggman@example.com"); expect(name).toHaveTextContent("not eggman"); await userEvent.click( - screen.getByRole("button", { name: Label.RESET_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.RESET_BUTTON }), ); expect(email).toHaveTextContent(""); expect(name).toHaveTextContent("eggman"); @@ -221,10 +222,10 @@ describe("ConfigPanel", () => { expect( within( screen.getByRole("dialog", { - name: Label.CANCEL_CONFIRM, + name: ConfirmationDialogLabel.CANCEL_CONFIRM, }), ).getByRole("heading", { - name: Label.CANCEL_CONFIRM, + name: ConfirmationDialogLabel.CANCEL_CONFIRM, }), ).toBeInTheDocument(); expect(router.state.location.search).toBe(`?${params.toString()}`); @@ -235,7 +236,7 @@ describe("ConfigPanel", () => { await userEvent.click(document.body); expect( within(screen.getByRole("dialog", { name: "" })).queryByRole("heading", { - name: Label.CANCEL_CONFIRM, + name: ConfirmationDialogLabel.CANCEL_CONFIRM, }), ).not.toBeInTheDocument(); expect(router.state.location.search).toBeFalsy(); @@ -248,15 +249,15 @@ describe("ConfigPanel", () => { "eggman@example.com", ); await userEvent.click( - screen.getByRole("button", { name: Label.CANCEL_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.CANCEL_BUTTON }), ); expect( within( screen.getByRole("dialog", { - name: Label.CANCEL_CONFIRM, + name: ConfirmationDialogLabel.CANCEL_CONFIRM, }), ).getByRole("heading", { - name: Label.CANCEL_CONFIRM, + name: ConfirmationDialogLabel.CANCEL_CONFIRM, }), ).toBeInTheDocument(); expect(router.state.location.search).toBe(`?${params.toString()}`); @@ -269,57 +270,27 @@ describe("ConfigPanel", () => { "eggman@example.com", ); await userEvent.click( - screen.getByRole("button", { name: Label.CANCEL_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.CANCEL_BUTTON }), ); - expect( - within( - screen.getByRole("dialog", { - name: Label.CANCEL_CONFIRM, - }), - ).getByRole("heading", { - name: Label.CANCEL_CONFIRM, - }), - ).toBeInTheDocument(); expect(router.state.location.search).toBe(`?${params.toString()}`); await userEvent.click( - screen.getByRole("button", { name: Label.CANCEL_CONFIRM_CONFIRM_BUTTON }), - ); - expect(router.state.location.search).toBeFalsy(); - }); - - it("can cancel the cancel confirmation", async () => { - const { router } = renderComponent(, { state, path, url }); - await userEvent.type( - within(await screen.findByTestId("email")).getByRole("textbox"), - "eggman@example.com", - ); - await userEvent.click( - screen.getByRole("button", { name: Label.CANCEL_BUTTON }), - ); - expect( - within( - screen.getByRole("dialog", { - name: Label.CANCEL_CONFIRM, - }), - ).getByRole("heading", { - name: Label.CANCEL_CONFIRM, + screen.getByRole("button", { + name: ConfirmationDialogLabel.CANCEL_CONFIRM_CONFIRM_BUTTON, }), - ).toBeInTheDocument(); - expect(router.state.location.search).toBe(`?${params.toString()}`); - await userEvent.click( - screen.getByRole("button", { name: Label.CANCEL_CONFIRM_CANCEL_BUTTON }), ); - expect(router.state.location.search).toBe(`?${params.toString()}`); + expect(router.state.location.search).toBeFalsy(); }); it("closes when cancelling and there are no unsaved changes", async () => { const { router } = renderComponent(, { state, path, url }); await userEvent.click( - await screen.findByRole("button", { name: Label.CANCEL_BUTTON }), + await screen.findByRole("button", { + name: ConfigPanelLabel.CANCEL_BUTTON, + }), ); expect( within(screen.getByRole("dialog", { name: "" })).queryByRole("heading", { - name: Label.CANCEL_CONFIRM, + name: ConfirmationDialogLabel.CANCEL_CONFIRM, }), ).not.toBeInTheDocument(); expect(router.state.location.search).toBeFalsy(); @@ -331,63 +302,18 @@ describe("ConfigPanel", () => { within(await screen.findByTestId("email")).getByRole("textbox"), "eggman@example.com", ); - await userEvent.type( - within(await screen.findByTestId("name")).getByRole("textbox"), - "noteggman", - ); - await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), - ); - expect( - within( - screen.getByRole("dialog", { - name: Label.SAVE_CONFIRM, - }), - ).getByRole("heading", { - name: Label.SAVE_CONFIRM, - }), - ).toBeInTheDocument(); - }); - - it("can cancel the save confirmation", async () => { - const setApplicationConfig = vi - .fn() - .mockImplementation(() => Promise.resolve()); - vi.spyOn(applicationHooks, "useSetApplicationConfig").mockImplementation( - () => setApplicationConfig, - ); - renderComponent(, { state, path, url }); - await userEvent.type( - within(await screen.findByTestId("email")).getByRole("textbox"), - "eggman@example.com", - ); - await userEvent.type( - within(await screen.findByTestId("name")).getByRole("textbox"), - "noteggman", - ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); expect( within( screen.getByRole("dialog", { - name: Label.SAVE_CONFIRM, + name: ConfirmationDialogLabel.SAVE_CONFIRM, }), ).getByRole("heading", { - name: Label.SAVE_CONFIRM, + name: ConfirmationDialogLabel.SAVE_CONFIRM, }), ).toBeInTheDocument(); - await userEvent.click( - within( - screen.getByRole("dialog", { - name: Label.SAVE_CONFIRM, - }), - ).getByRole("button", { - name: Label.SAVE_CONFIRM_CANCEL_BUTTON, - }), - ); - expect(screen.queryByRole("dialog", { name: "" })).not.toBeInTheDocument(); - expect(setApplicationConfig).not.toHaveBeenCalled(); }); it("can save changes", async () => { @@ -408,10 +334,12 @@ describe("ConfigPanel", () => { "noteggman", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); expect(setApplicationConfig).toHaveBeenCalledWith("easyrsa", { email: configFactory.build({ @@ -443,15 +371,13 @@ describe("ConfigPanel", () => { within(await screen.findByTestId("email")).getByRole("textbox"), "eggman@example.com", ); - await userEvent.type( - within(await screen.findByTestId("name")).getByRole("textbox"), - "noteggman", - ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); expect(screen.getByText("That's not a name")).toBeInTheDocument(); expect(getApplicationConfig).toHaveBeenCalledTimes(1); @@ -470,13 +396,16 @@ describe("ConfigPanel", () => { expect(getApplicationConfig).toHaveBeenCalledTimes(1); await waitFor(() => { expect(console.error).toHaveBeenCalledWith( - Label.GET_CONFIG_ERROR, + ConfigPanelLabel.GET_CONFIG_ERROR, new Error("Error while calling getApplicationConfig"), ); }); - const configErrorNotification = screen.getByText(Label.GET_CONFIG_ERROR, { - exact: false, - }); + const configErrorNotification = screen.getByText( + ConfigPanelLabel.GET_CONFIG_ERROR, + { + exact: false, + }, + ); expect(configErrorNotification).toBeInTheDocument(); expect(configErrorNotification.childElementCount).toBe(1); const refetchButton = configErrorNotification.children[0]; @@ -513,36 +442,24 @@ describe("ConfigPanel", () => { within(await screen.findByTestId("email")).getByRole("textbox"), "eggman@example.com", ); - await userEvent.type( - within(await screen.findByTestId("name")).getByRole("textbox"), - "noteggman", - ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), - ); - expect(setApplicationConfig).toHaveBeenCalledWith("easyrsa", { - email: configFactory.build({ - name: "email", - default: "", - newValue: "eggman@example.com", - }), - name: configFactory.build({ - name: "name", - default: "eggman", - newValue: "noteggman", + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, }), - }); + ); await waitFor(() => expect(console.error).toHaveBeenCalledWith( - Label.SUBMIT_TO_JUJU_ERROR, + ConfirmationDialogLabel.SUBMIT_TO_JUJU_ERROR, new Error("Error while trying to save"), ), ); expect( - screen.getByText(Label.SUBMIT_TO_JUJU_ERROR, { exact: false }), + screen.getByText(ConfirmationDialogLabel.SUBMIT_TO_JUJU_ERROR, { + exact: false, + }), ).toBeInTheDocument(); }); @@ -561,14 +478,16 @@ describe("ConfigPanel", () => { "secret:aabbccdd", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); expect( screen.getByRole("dialog", { - name: Label.GRANT_CONFIRM, + name: ConfirmationDialogLabel.GRANT_CONFIRM, }), ).toBeInTheDocument(); }); @@ -601,14 +520,16 @@ describe("ConfigPanel", () => { "secret:aabbccdd", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); expect( screen.getByRole("dialog", { - name: Label.GRANT_CONFIRM, + name: ConfirmationDialogLabel.GRANT_CONFIRM, }), ).toBeInTheDocument(); }); @@ -620,14 +541,16 @@ describe("ConfigPanel", () => { "notasecret", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); expect( screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + name: ConfirmationDialogLabel.GRANT_CONFIRM, }), ).not.toBeInTheDocument(); }); @@ -654,14 +577,16 @@ describe("ConfigPanel", () => { "secret:aabbccdd", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); expect( screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + name: ConfirmationDialogLabel.GRANT_CONFIRM, }), ).not.toBeInTheDocument(); }); @@ -686,48 +611,18 @@ describe("ConfigPanel", () => { "secret:aabbccdd", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), - ); - expect( - screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, }), - ).not.toBeInTheDocument(); - }); - - it("can cancel the grant confirmation", async () => { - state.juju.secrets = secretsStateFactory.build({ - abc123: modelSecretsFactory.build({ - items: [ - listSecretResultFactory.build({ access: [], uri: "secret:aabbccdd" }), - ], - loaded: true, - }), - }); - const { router } = renderComponent(, { state, path, url }); - expect(router.state.location.search).toBe(`?${params.toString()}`); - await userEvent.type( - within(await screen.findByTestId("email")).getByRole("textbox"), - "secret:aabbccdd", - ); - await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), - ); - await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), - ); - await userEvent.click( - screen.getByRole("button", { name: Label.GRANT_CANCEL_BUTTON }), ); expect( screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + name: ConfirmationDialogLabel.GRANT_CONFIRM, }), ).not.toBeInTheDocument(); - expect(router.state.location.search).toBe(""); }); it("can grant secrets", async () => { @@ -757,19 +652,18 @@ describe("ConfigPanel", () => { "secret:eeffgghh", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); await userEvent.click( - screen.getByRole("button", { name: Label.GRANT_CONFIRM_BUTTON }), - ); - expect( - screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + screen.getByRole("button", { + name: ConfirmationDialogLabel.GRANT_CONFIRM_BUTTON, }), - ).not.toBeInTheDocument(); + ); expect(grantSecret).toHaveBeenCalledWith("secret:aabbccdd", ["easyrsa"]); expect(grantSecret).toHaveBeenCalledWith("secret:eeffgghh", ["easyrsa"]); expect(router.state.location.search).toBe(""); @@ -802,17 +696,21 @@ describe("ConfigPanel", () => { "secret:aabbccdd", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); await userEvent.click( - screen.getByRole("button", { name: Label.GRANT_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.GRANT_CONFIRM_BUTTON, + }), ); expect( screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + name: ConfirmationDialogLabel.GRANT_CONFIRM, }), ).not.toBeInTheDocument(); expect(grantSecret).toHaveBeenCalledTimes(1); @@ -841,24 +739,23 @@ describe("ConfigPanel", () => { "secret:aabbccdd", ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ); await userEvent.click( - screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + screen.getByRole("button", { + name: ConfirmationDialogLabel.SAVE_CONFIRM_CONFIRM_BUTTON, + }), ); await userEvent.click( - screen.getByRole("button", { name: Label.GRANT_CONFIRM_BUTTON }), - ); - expect( - screen.queryByRole("dialog", { - name: Label.GRANT_CONFIRM, + screen.getByRole("button", { + name: ConfirmationDialogLabel.GRANT_CONFIRM_BUTTON, }), - ).not.toBeInTheDocument(); + ); expect(router.state.location.search).toBe(`?${params.toString()}`); await waitFor(() => { expect( document.querySelector(".p-notification--negative"), - ).toHaveTextContent(Label.GRANT_ERROR); + ).toHaveTextContent(ConfirmationDialogLabel.GRANT_ERROR); }); }); @@ -889,16 +786,16 @@ describe("ConfigPanel", () => { "textbox", ); await userEvent.type(input, "notasecret:aabbccdd"); - expect(screen.getByText(Label.SECRET_PREFIX_ERROR)).toHaveClass( + expect(screen.getByText(ConfigPanelLabel.SECRET_PREFIX_ERROR)).toHaveClass( "p-form-validation__message", ); expect( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ).toBeDisabled(); await userEvent.clear(input); await userEvent.type(input, "secret:aabbccdd"); expect( - screen.queryByText(Label.SECRET_PREFIX_ERROR), + screen.queryByText(ConfigPanelLabel.SECRET_PREFIX_ERROR), ).not.toBeInTheDocument(); }); @@ -916,16 +813,16 @@ describe("ConfigPanel", () => { "textbox", ); await userEvent.type(input, "secret:nothing"); - expect(screen.getByText(Label.INVALID_SECRET_ERROR)).toHaveClass( + expect(screen.getByText(ConfigPanelLabel.INVALID_SECRET_ERROR)).toHaveClass( "p-form-validation__message", ); expect( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ).toBeDisabled(); await userEvent.clear(input); await userEvent.type(input, "secret:aabbccdd"); expect( - screen.queryByText(Label.INVALID_SECRET_ERROR), + screen.queryByText(ConfigPanelLabel.INVALID_SECRET_ERROR), ).not.toBeInTheDocument(); }); @@ -947,11 +844,11 @@ describe("ConfigPanel", () => { within(await screen.findByTestId("email")).getByRole("textbox"), "secret:aabbccdd", ); - expect(screen.getByText(Label.INVALID_SECRET_ERROR)).toHaveClass( + expect(screen.getByText(ConfigPanelLabel.INVALID_SECRET_ERROR)).toHaveClass( "p-form-validation__message", ); expect( - screen.getByRole("button", { name: Label.SAVE_BUTTON }), + screen.getByRole("button", { name: ConfigPanelLabel.SAVE_BUTTON }), ).toBeDisabled(); }); }); diff --git a/src/panels/ConfigPanel/ConfigPanel.tsx b/src/panels/ConfigPanel/ConfigPanel.tsx index c9cd047e5..7940968ae 100644 --- a/src/panels/ConfigPanel/ConfigPanel.tsx +++ b/src/panels/ConfigPanel/ConfigPanel.tsx @@ -1,34 +1,21 @@ import type { ListSecretResult } from "@canonical/jujulib/dist/api/facades/secrets/SecretsV2"; -import { - ActionButton, - Button, - ConfirmationModal, -} from "@canonical/react-components"; +import { ActionButton, Button } from "@canonical/react-components"; import classnames from "classnames"; import cloneDeep from "clone-deep"; import type { MouseEvent } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; -import usePortal from "react-useportal"; import FadeIn from "animations/FadeIn"; import CharmIcon from "components/CharmIcon"; import Panel from "components/Panel"; import type { EntityDetailsRoute } from "components/Routes"; -import SecretLabel from "components/secrets/SecretLabel"; import { isSet } from "components/utils"; import useAnalytics from "hooks/useAnalytics"; -import useCanManageSecrets from "hooks/useCanManageSecrets"; import useInlineErrors, { type SetError } from "hooks/useInlineErrors"; -import { - useListSecrets, - useGrantSecret, - useSetApplicationConfig, - useGetApplicationConfig, -} from "juju/api-hooks"; +import { useListSecrets, useGetApplicationConfig } from "juju/api-hooks"; import PanelInlineErrors from "panels/PanelInlineErrors"; import { usePanelQueryParams } from "panels/hooks"; -import type { ConfirmTypes as DefaultConfirmTypes } from "panels/types"; import { ConfirmType as DefaultConfirmType } from "panels/types"; import bulbImage from "static/images/bulb.svg"; import boxImage from "static/images/no-config-params.svg"; @@ -38,40 +25,22 @@ import { useAppSelector, useAppDispatch } from "store/store"; import { secretIsAppOwned } from "utils"; import BooleanConfig from "./BooleanConfig"; -import ChangedKeyValues from "./ChangedKeyValues"; import type { SetNewValue, SetSelectedConfig } from "./ConfigField"; +import ConfirmationDialog from "./ConfirmationDialog"; import NumberConfig from "./NumberConfig"; import TextAreaConfig from "./TextAreaConfig"; +import type { ConfigQueryParams, ConfirmTypes } from "./types"; import { + InlineErrors, Label, TestId, type Config, type ConfigData, type ConfigValue, } from "./types"; -import { getRequiredGrants } from "./utils"; import "./_config-panel.scss"; -enum InlineErrors { - FORM = "form", - GET_CONFIG = "get-config", - SUBMIT_TO_JUJU = "submit-to-juju", -} - -enum ConfigConfirmType { - GRANT = "grant", -} - -type ConfirmTypes = DefaultConfirmTypes | ConfigConfirmType; - -type ConfigQueryParams = { - panel: string | null; - charm: string | null; - entity: string | null; - modelUUID: string | null; -}; - const hasChangedFields = (newConfig: Config): boolean => { return Object.keys(newConfig).some( (key) => @@ -113,7 +82,6 @@ export default function ConfigPanel(): JSX.Element { }); const scrollArea = useRef(null); const sendAnalytics = useAnalytics(); - const { Portal } = usePortal(); const updateConfig = useCallback((newConfig: Config) => { setConfig(newConfig); checkAllDefaults(newConfig); @@ -135,11 +103,8 @@ export default function ConfigPanel(): JSX.Element { const wsControllerURL = useAppSelector((state) => getModelByUUID(state, modelUUID), )?.wsControllerURL; - const canManageSecrets = useCanManageSecrets(); - const grantSecret = useGrantSecret(userName, modelName); const listSecrets = useListSecrets(userName, modelName); const getApplicationConfig = useGetApplicationConfig(userName, modelName); - const setApplicationConfig = useSetApplicationConfig(userName, modelName); useEffect(() => { listSecrets(); @@ -224,164 +189,6 @@ export default function ConfigPanel(): JSX.Element { setEnableSave(fieldChanged); } - async function _submitToJuju() { - if (!modelUUID || !appName) { - return; - } - setSavingConfig(true); - const response = await setApplicationConfig(appName, config); - const errors = response?.results?.reduce((collection, result) => { - if (result.error) { - collection.push(result.error.message); - } - return collection; - }, []); - setSavingConfig(false); - setEnableSave(false); - setConfirmType(null); - if (errors?.length) { - setInlineError(InlineErrors.FORM, errors); - return; - } - sendAnalytics({ - category: "User", - action: "Config values updated", - }); - if ( - canManageSecrets && - getRequiredGrants(appName, config, secrets)?.length - ) { - setConfirmType(ConfigConfirmType.GRANT); - } else { - handleRemovePanelQueryParams(); - } - } - - function generateConfirmationDialog(): JSX.Element | null { - if (confirmType && appName) { - if (confirmType === DefaultConfirmType.SUBMIT) { - // Render the submit confirmation modal. - return ( - - - You can revert back to the applications default settings by - clicking the “Reset all values” button; or reset each edited - field by clicking “Use default”. -

- } - cancelButtonLabel={Label.SAVE_CONFIRM_CANCEL_BUTTON} - confirmButtonLabel={Label.SAVE_CONFIRM_CONFIRM_BUTTON} - confirmButtonAppearance="positive" - onConfirm={() => { - setConfirmType(null); - // Clear the form errors if there were any from a previous submit. - setInlineError(InlineErrors.FORM, null); - _submitToJuju().catch((error) => { - setInlineError( - InlineErrors.SUBMIT_TO_JUJU, - Label.SUBMIT_TO_JUJU_ERROR, - ); - console.error(Label.SUBMIT_TO_JUJU_ERROR, error); - }); - }} - close={() => setConfirmType(null)} - > - -
-
- ); - } - if (confirmType === ConfigConfirmType.GRANT) { - // Render the grant confirmation modal. - const requiredGrants = getRequiredGrants(appName, config, secrets); - return ( - - { - setConfirmType(null); - // Clear the form errors if there were any from a previous submit. - setInlineError(InlineErrors.FORM, null); - if (!appName || !requiredGrants) { - // It is not possible to get to this point if these - // variables aren't set. - return; - } - void (async () => { - try { - for (const secretURI of requiredGrants) { - await grantSecret(secretURI, [appName]); - } - setConfirmType(null); - handleRemovePanelQueryParams(); - } catch (error) { - setInlineError( - InlineErrors.SUBMIT_TO_JUJU, - Label.GRANT_ERROR, - ); - console.error(Label.GRANT_ERROR, error); - } - })(); - }} - close={() => { - setConfirmType(null); - handleRemovePanelQueryParams(); - }} - > -

- Would you like to grant access to this application for the - following secrets? -

-
    - {requiredGrants?.map((secretURI) => { - const secret = secrets?.find(({ uri }) => uri === secretURI); - return ( -
  • - {secret ? : secretURI} -
  • - ); - })} -
-
-
- ); - } - if (confirmType === "cancel") { - // Render the cancel confirmation modal. - return ( - - { - setConfirmType(null); - handleRemovePanelQueryParams(); - }} - close={() => setConfirmType(null)} - > - - - - ); - } - } - return null; - } - function checkCanClose(event: KeyboardEvent | MouseEvent) { if (!("code" in event)) { const target = event.target as HTMLElement; @@ -503,7 +310,16 @@ export default function ConfigPanel(): JSX.Element { secrets, )} - {generateConfirmationDialog()} + ) : ( diff --git a/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx new file mode 100644 index 000000000..082914a24 --- /dev/null +++ b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.test.tsx @@ -0,0 +1,514 @@ +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import type { Mock } from "vitest"; +import { vi } from "vitest"; + +import * as useCanManageSecrets from "hooks/useCanManageSecrets"; +import * as applicationHooks from "juju/api-hooks/application"; +import * as secretHooks from "juju/api-hooks/secrets"; +import { ConfirmType as DefaultConfirmType } from "panels/types"; +import type { RootState } from "store/store"; +import { configFactory } from "testing/factories/juju/Application"; +import { + secretsStateFactory, + listSecretResultFactory, + modelSecretsFactory, +} from "testing/factories/juju/juju"; +import { rootStateFactory } from "testing/factories/root"; +import { renderComponent } from "testing/utils"; + +import type { Config } from "../types"; +import { ConfigConfirmType } from "../types"; + +import ConfirmationDialog from "./ConfirmationDialog"; +import { InlineErrors, Label, Label as ConfirmationDialogLabel } from "./types"; + +describe("ConfirmationDialog", () => { + let state: RootState; + const params = new URLSearchParams({ + entity: "easyrsa", + charm: "cs:easyrsa", + modelUUID: "abc123", + panel: "config", + }); + const url = `/models/eggman@external/hadoopspark?${params.toString()}`; + const consoleError = console.error; + let mockSetConfirmType: Mock; + let mockSetInlineError: Mock; + let mockHandleRemovePanelQueryParams: Mock; + + const mockConfig = { + email: { + default: "", + description: + "Base64 encoded Certificate Authority (CA) bundle. Setting this config\n" + + "allows container runtimes to pull images from registries with TLS\n" + + "certificates signed by an external CA.\n", + source: "default", + type: "string", + value: "", + name: "email", + newValue: "secret:aabbccdd", + error: null, + }, + name: { + default: "eggman", + description: + "Base64 encoded Certificate Authority (CA) bundle. Setting this config\n" + + "allows container runtimes to pull images from registries with TLS\n" + + "certificates signed by an external CA.\n", + source: "default", + type: "string", + value: "", + name: "name", + }, + } as Config; + const mockQueryParams = { + panel: "config", + charm: "cs:easyrsa", + entity: "easyrsa", + modelUUID: "abc123", + }; + + beforeEach(() => { + console.error = vi.fn(); + mockSetConfirmType = vi.fn(); + mockSetInlineError = vi.fn(); + mockHandleRemovePanelQueryParams = vi.fn(); + vi.resetModules(); + state = rootStateFactory.build(); + const setApplicationConfig = vi + .fn() + .mockImplementation(() => Promise.resolve()); + vi.spyOn(applicationHooks, "useSetApplicationConfig").mockImplementation( + () => setApplicationConfig, + ); + }); + + afterEach(() => { + console.error = consoleError; + vi.restoreAllMocks(); + }); + + it("should display submit confirmation dialog and can cancel submit", async () => { + renderComponent( + , + { + url, + state, + }, + ); + expect( + screen.getByRole("dialog", { name: Label.SAVE_CONFIRM }), + ).toBeInTheDocument(); + const cancelButton = screen.getByRole("button", { + name: Label.SAVE_CONFIRM_CANCEL_BUTTON, + }); + expect(cancelButton).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + ).toBeInTheDocument(); + // Check that the application name is displayed in the confirmation message. + expect( + screen.getByText( + "You have edited the following values to the easyrsa configuration:", + ), + ).toBeInTheDocument(); + // Check that the changed values are displayed. + expect( + screen.getByText("email") && screen.getByText("secret:aabbccdd"), + ).toBeInTheDocument(); + await userEvent.click(cancelButton); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + }); + + it("should submit successfully and remove panel query params", async () => { + const setApplicationConfig = vi + .fn() + .mockImplementation(() => Promise.resolve()); + vi.spyOn(applicationHooks, "useSetApplicationConfig").mockImplementation( + () => setApplicationConfig, + ); + renderComponent( + , + { + url, + state, + }, + ); + await userEvent.click( + screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + ); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(setApplicationConfig).toHaveBeenCalledWith("easyrsa", { + email: configFactory.build({ + error: null, + name: "email", + default: "", + newValue: "secret:aabbccdd", + }), + name: configFactory.build({ + name: "name", + default: "eggman", + }), + }); + expect(console.error).not.toHaveBeenCalled(); + expect(mockHandleRemovePanelQueryParams).toHaveBeenCalledOnce(); + }); + + it("should submit successfully and open up grant confirmation dialog", async () => { + const setApplicationConfig = vi + .fn() + .mockImplementation(() => Promise.resolve()); + vi.spyOn(applicationHooks, "useSetApplicationConfig").mockImplementation( + () => setApplicationConfig, + ); + vi.spyOn(useCanManageSecrets, "default").mockImplementation(() => true); + state.juju.secrets = secretsStateFactory.build({ + abc123: modelSecretsFactory.build({ + items: [ + listSecretResultFactory.build({ access: [], uri: "secret:aabbccdd" }), + ], + loaded: true, + }), + }); + renderComponent( + , + { + url, + state, + }, + ); + await userEvent.click( + screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + ); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(setApplicationConfig).toHaveBeenCalledWith("easyrsa", { + email: configFactory.build({ + error: null, + name: "email", + default: "", + newValue: "secret:aabbccdd", + }), + name: configFactory.build({ + name: "name", + default: "eggman", + }), + }); + expect(console.error).not.toHaveBeenCalled(); + expect(mockSetConfirmType).toHaveBeenCalledWith(ConfigConfirmType.GRANT); + }); + + it("should console error when trying to submit", async () => { + const setApplicationConfig = vi + .fn() + .mockImplementation(() => + Promise.reject(new Error("Error while trying to save")), + ); + vi.spyOn(applicationHooks, "useSetApplicationConfig").mockImplementation( + () => setApplicationConfig, + ); + renderComponent( + , + { + url, + state, + }, + ); + await userEvent.click( + screen.getByRole("button", { name: Label.SAVE_CONFIRM_CONFIRM_BUTTON }), + ); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(mockSetInlineError).toHaveBeenCalledWith(InlineErrors.FORM, null); + expect(setApplicationConfig).toHaveBeenCalledWith("easyrsa", { + email: configFactory.build({ + error: null, + name: "email", + default: "", + newValue: "secret:aabbccdd", + }), + name: configFactory.build({ + name: "name", + default: "eggman", + }), + }); + await waitFor(() => + expect(console.error).toHaveBeenCalledWith( + Label.SUBMIT_TO_JUJU_ERROR, + new Error("Error while trying to save"), + ), + ); + expect(mockSetInlineError).toHaveBeenCalledWith( + InlineErrors.SUBMIT_TO_JUJU, + Label.SUBMIT_TO_JUJU_ERROR, + ); + }); + + it("should display grant confirmation dialog and can cancel grant", async () => { + state.juju.secrets = secretsStateFactory.build({ + abc123: modelSecretsFactory.build({ + items: [ + listSecretResultFactory.build({ access: [], uri: "secret:aabbccdd" }), + ], + loaded: true, + }), + }); + renderComponent( + , + { + url, + state, + }, + ); + + expect( + screen.getByRole("dialog", { + name: Label.GRANT_CONFIRM, + }), + ).toBeInTheDocument(); + const cancelButton = screen.getByRole("button", { + name: Label.GRANT_CANCEL_BUTTON, + }); + expect(cancelButton).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: Label.GRANT_CONFIRM_BUTTON }), + ).toBeInTheDocument(); + expect(screen); + expect( + screen.getByText( + "Would you like to grant access to this application for the following secrets?", + ), + ).toBeInTheDocument(); + // Check that the secret is displayed. + expect(screen.getByText("aabbccdd")).toBeInTheDocument(); + await userEvent.click(cancelButton); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(mockHandleRemovePanelQueryParams).toHaveBeenCalledOnce(); + }); + + it("should grant secret successfully and remove panel query params", async () => { + const grantSecret = vi.fn().mockImplementation(() => Promise.resolve()); + vi.spyOn(secretHooks, "useGrantSecret").mockImplementation( + () => grantSecret, + ); + state.juju.secrets = secretsStateFactory.build({ + abc123: modelSecretsFactory.build({ + items: [ + listSecretResultFactory.build({ access: [], uri: "secret:aabbccdd" }), + ], + loaded: true, + }), + }); + renderComponent( + , + { + url, + state, + }, + ); + await userEvent.click( + screen.getByRole("button", { + name: ConfirmationDialogLabel.GRANT_CONFIRM_BUTTON, + }), + ); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(mockSetInlineError).toHaveBeenCalledWith(InlineErrors.FORM, null); + expect(grantSecret).toHaveBeenCalledWith("secret:aabbccdd", ["easyrsa"]); + expect(console.error).not.toHaveBeenCalled(); + expect(mockHandleRemovePanelQueryParams).toHaveBeenCalledOnce(); + }); + + it("should console error when trying to grant access", async () => { + const grantSecret = vi + .fn() + .mockImplementation(() => Promise.reject(new Error("Caught error"))); + vi.spyOn(secretHooks, "useGrantSecret").mockImplementation( + () => grantSecret, + ); + state.juju.secrets = secretsStateFactory.build({ + abc123: modelSecretsFactory.build({ + items: [ + listSecretResultFactory.build({ access: [], uri: "secret:aabbccdd" }), + ], + loaded: true, + }), + }); + renderComponent( + , + { + url, + state, + }, + ); + await userEvent.click( + screen.getByRole("button", { + name: ConfirmationDialogLabel.GRANT_CONFIRM_BUTTON, + }), + ); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(mockSetInlineError).toHaveBeenCalledWith(InlineErrors.FORM, null); + expect(grantSecret).toHaveBeenCalledWith("secret:aabbccdd", ["easyrsa"]); + await waitFor(() => + expect(console.error).toHaveBeenCalledWith( + Label.GRANT_ERROR, + new Error("Caught error"), + ), + ); + expect(mockSetInlineError).toHaveBeenCalledWith( + InlineErrors.SUBMIT_TO_JUJU, + Label.GRANT_ERROR, + ); + }); + + it("should display cancel confirmation dialog and can cancel the cancelation", async () => { + renderComponent( + , + { + url, + state, + }, + ); + await userEvent.click( + screen.getByRole("button", { name: Label.CANCEL_CONFIRM_CONFIRM_BUTTON }), + ); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + expect(mockHandleRemovePanelQueryParams).toHaveBeenCalledOnce(); + }); + + it("should cancel successfully", async () => { + renderComponent( + , + { + url, + state, + }, + ); + expect( + screen.getByRole("dialog", { name: Label.CANCEL_CONFIRM }), + ).toBeInTheDocument(); + const cancelButton = screen.getByRole("button", { + name: Label.CANCEL_CONFIRM_CANCEL_BUTTON, + }); + expect(cancelButton).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: Label.CANCEL_CONFIRM_CONFIRM_BUTTON }), + ).toBeInTheDocument(); + // Check that the application name is displayed in the confirmation message. + expect( + screen.getByText( + "You have edited the following values to the easyrsa configuration:", + ), + ).toBeInTheDocument(); + // Check that the changed values are displayed. + expect( + screen.getByText("email") && screen.getByText("secret:aabbccdd"), + ).toBeInTheDocument(); + await userEvent.click(cancelButton); + expect(mockSetConfirmType).toHaveBeenCalledWith(null); + }); + + it("should display nothing if no application name is provided", () => { + const { + result: { container }, + } = renderComponent( + , + { + url, + state, + }, + ); + expect(container.tagName).toBe("DIV"); + expect(container.children.length).toBe(1); + expect(container.firstChild).toBeEmptyDOMElement(); + }); +}); diff --git a/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx new file mode 100644 index 000000000..8cd74f0d0 --- /dev/null +++ b/src/panels/ConfigPanel/ConfirmationDialog/ConfirmationDialog.tsx @@ -0,0 +1,201 @@ +import { ConfirmationModal } from "@canonical/react-components"; +import { useParams } from "react-router-dom"; +import usePortal from "react-useportal"; + +import type { EntityDetailsRoute } from "components/Routes"; +import SecretLabel from "components/secrets/SecretLabel"; +import useAnalytics from "hooks/useAnalytics"; +import useCanManageSecrets from "hooks/useCanManageSecrets"; +import { type SetError } from "hooks/useInlineErrors"; +import { useGrantSecret, useSetApplicationConfig } from "juju/api-hooks"; +import type { usePanelQueryParams } from "panels/hooks"; +import { ConfirmType as DefaultConfirmType } from "panels/types"; +import { getModelSecrets } from "store/juju/selectors"; +import { useAppSelector } from "store/store"; + +import ChangedKeyValues from "../ChangedKeyValues"; +import type { Config, ConfigQueryParams, ConfirmTypes } from "../types"; +import { ConfigConfirmType } from "../types"; +import { getRequiredGrants } from "../utils"; + +import { InlineErrors, Label } from "./types"; + +type Props = { + confirmType: ConfirmTypes; + queryParams: ConfigQueryParams; + setEnableSave: React.Dispatch>; + setSavingConfig: React.Dispatch>; + setConfirmType: React.Dispatch>; + setInlineError: SetError; + config: Config; + handleRemovePanelQueryParams: ReturnType[2]; +}; + +const ConfirmationDialog = ({ + confirmType, + queryParams, + setEnableSave, + setSavingConfig, + setConfirmType, + setInlineError, + config, + handleRemovePanelQueryParams, +}: Props): JSX.Element | null => { + const { Portal } = usePortal(); + const { userName, modelName } = useParams(); + const { entity: appName, modelUUID } = queryParams; + const grantSecret = useGrantSecret(userName, modelName); + const setApplicationConfig = useSetApplicationConfig(userName, modelName); + const canManageSecrets = useCanManageSecrets(); + const sendAnalytics = useAnalytics(); + const secrets = useAppSelector((state) => getModelSecrets(state, modelUUID)); + + async function _submitToJuju(appName: string) { + setSavingConfig(true); + const response = await setApplicationConfig(appName, config); + const errors = response?.results?.reduce((collection, result) => { + if (result.error) { + collection.push(result.error.message); + } + return collection; + }, []); + setSavingConfig(false); + setEnableSave(false); + setConfirmType(null); + if (errors?.length) { + setInlineError(InlineErrors.FORM, errors); + return; + } + sendAnalytics({ + category: "User", + action: "Config values updated", + }); + if ( + canManageSecrets && + getRequiredGrants(appName, config, secrets)?.length + ) { + setConfirmType(ConfigConfirmType.GRANT); + } else { + handleRemovePanelQueryParams(); + } + } + if (!appName) { + return null; + } else if (confirmType === DefaultConfirmType.SUBMIT) { + // Render the submit confirmation modal. + return ( + + + You can revert back to the applications default settings by + clicking the “Reset all values” button; or reset each edited field + by clicking “Use default”. +

+ } + cancelButtonLabel={Label.SAVE_CONFIRM_CANCEL_BUTTON} + confirmButtonLabel={Label.SAVE_CONFIRM_CONFIRM_BUTTON} + confirmButtonAppearance="positive" + onConfirm={() => { + setConfirmType(null); + // Clear the form errors if there were any from a previous submit. + setInlineError(InlineErrors.FORM, null); + _submitToJuju(appName).catch((error) => { + setInlineError( + InlineErrors.SUBMIT_TO_JUJU, + Label.SUBMIT_TO_JUJU_ERROR, + ); + console.error(Label.SUBMIT_TO_JUJU_ERROR, error); + }); + }} + close={() => setConfirmType(null)} + > + +
+
+ ); + } else if (confirmType === ConfigConfirmType.GRANT) { + // Render the grant confirmation modal. + const requiredGrants = getRequiredGrants(appName, config, secrets); + return ( + + { + setConfirmType(null); + // Clear the form errors if there were any from a previous submit. + setInlineError(InlineErrors.FORM, null); + if (!requiredGrants) { + // It is not possible to get to this point if these + // variables aren't set. + return; + } + void (async () => { + try { + for (const secretURI of requiredGrants) { + await grantSecret(secretURI, [appName]); + } + setConfirmType(null); + handleRemovePanelQueryParams(); + } catch (error) { + setInlineError(InlineErrors.SUBMIT_TO_JUJU, Label.GRANT_ERROR); + console.error(Label.GRANT_ERROR, error); + } + })(); + }} + close={() => { + setConfirmType(null); + handleRemovePanelQueryParams(); + }} + > +

+ Would you like to grant access to this application for the following + secrets? +

+
    + {requiredGrants?.map((secretURI) => { + const secret = secrets?.find(({ uri }) => uri === secretURI); + return ( +
  • + {secret ? : secretURI} +
  • + ); + })} +
+
+
+ ); + } else if (confirmType === DefaultConfirmType.CANCEL) { + // Render the cancel confirmation modal. + return ( + + { + setConfirmType(null); + handleRemovePanelQueryParams(); + }} + close={() => setConfirmType(null)} + > + + + + ); + } + return null; +}; + +export default ConfirmationDialog; diff --git a/src/panels/ConfigPanel/ConfirmationDialog/index.ts b/src/panels/ConfigPanel/ConfirmationDialog/index.ts new file mode 100644 index 000000000..20bf0713d --- /dev/null +++ b/src/panels/ConfigPanel/ConfirmationDialog/index.ts @@ -0,0 +1 @@ +export { default } from "./ConfirmationDialog"; diff --git a/src/panels/ConfigPanel/ConfirmationDialog/types.ts b/src/panels/ConfigPanel/ConfirmationDialog/types.ts new file mode 100644 index 000000000..659b32132 --- /dev/null +++ b/src/panels/ConfigPanel/ConfirmationDialog/types.ts @@ -0,0 +1,18 @@ +export enum Label { + CANCEL_CONFIRM = "Are you sure you wish to cancel?", + CANCEL_CONFIRM_CANCEL_BUTTON = "Continue editing", + CANCEL_CONFIRM_CONFIRM_BUTTON = "Yes, I'm sure", + GRANT_CANCEL_BUTTON = "No", + GRANT_CONFIRM = "Grant secrets?", + GRANT_CONFIRM_BUTTON = "Yes", + GRANT_ERROR = "Unable to grant application access to secrets.", + SAVE_CONFIRM = "Are you sure you wish to apply these changes?", + SAVE_CONFIRM_CANCEL_BUTTON = "Cancel", + SAVE_CONFIRM_CONFIRM_BUTTON = "Yes, apply changes", + SUBMIT_TO_JUJU_ERROR = "Unable to submit config changes to Juju.", +} + +export enum InlineErrors { + FORM = "form", + SUBMIT_TO_JUJU = "submit-to-juju", +} diff --git a/src/panels/ConfigPanel/types.ts b/src/panels/ConfigPanel/types.ts index 241a6e609..62e103c74 100644 --- a/src/panels/ConfigPanel/types.ts +++ b/src/panels/ConfigPanel/types.ts @@ -1,3 +1,5 @@ +import type { ConfirmTypes as DefaultConfirmTypes } from "panels/types"; + export type ConfigValue = string | number | boolean | undefined; export type ConfigOption = { @@ -22,25 +24,31 @@ export type Config = { export enum Label { CANCEL_BUTTON = "Cancel", - CANCEL_CONFIRM = "Are you sure you wish to cancel?", - CANCEL_CONFIRM_CANCEL_BUTTON = "Continue editing", - CANCEL_CONFIRM_CONFIRM_BUTTON = "Yes, I'm sure", - GRANT_CANCEL_BUTTON = "No", - GRANT_CONFIRM = "Grant secrets?", - GRANT_CONFIRM_BUTTON = "Yes", - GRANT_ERROR = "Unable to grant application access to secrets.", INVALID_SECRET_ERROR = "This is an invalid secret URI.", SECRET_PREFIX_ERROR = "A secret URI must begin with the 'secret:' prefix.", NONE = "This application doesn't have any configuration parameters", RESET_BUTTON = "Reset all values", SAVE_BUTTON = "Save and apply", - SAVE_CONFIRM = "Are you sure you wish to apply these changes?", - SAVE_CONFIRM_CANCEL_BUTTON = "Cancel", - SAVE_CONFIRM_CONFIRM_BUTTON = "Yes, apply changes", GET_CONFIG_ERROR = "Unable to get application config.", - SUBMIT_TO_JUJU_ERROR = "Unable to submit config changes to Juju.", } export enum TestId { PANEL = "config-panel", } + +export enum InlineErrors { + GET_CONFIG = "get-config", +} + +export enum ConfigConfirmType { + GRANT = "grant", +} + +export type ConfirmTypes = DefaultConfirmTypes | ConfigConfirmType; + +export type ConfigQueryParams = { + panel: string | null; + charm: string | null; + entity: string | null; + modelUUID: string | null; +};