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

WD-11593 - refactor: remove nested components from ActionLogs #1783

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import userEvent from "@testing-library/user-event";
import { add } from "date-fns";
import { vi } from "vitest";

import * as componentUtils from "components/utils";
import * as actionsHooks from "juju/api-hooks/actions";
import type { RootState } from "store/store";
import { rootStateFactory } from "testing/factories";
Expand All @@ -27,14 +26,6 @@ import { renderComponent } from "testing/utils";
import ActionLogs from "./ActionLogs";
import { Label, Output } from "./types";

vi.mock("components/utils", async () => {
const utils = await vi.importActual("components/utils");
return {
...utils,
copyToClipboard: vi.fn(),
};
});

const completed = new Date();
completed.setMonth(completed.getMonth() - 18);

Expand Down Expand Up @@ -364,16 +355,6 @@ describe("Action Logs", () => {
expect(modal).not.toBeInTheDocument();
});

it("can copy the action result", async () => {
renderComponent(<ActionLogs />, { path, url, state });
await userEvent.click(await screen.findByTestId("show-output"));
await userEvent.click(screen.getByRole("button", { name: Label.COPY }));
expect(componentUtils.copyToClipboard).toHaveBeenCalledWith(`{
"key1": "value1",
"test": 123
}`);
});

it("should show error when fetching action logs and refetch action logs", async () => {
const consoleError = console.error;
console.error = vi.fn();
Expand Down
51 changes: 4 additions & 47 deletions src/pages/EntityDetails/Model/Logs/ActionLogs/ActionLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import type {
} from "@canonical/jujulib/dist/api/facades/action/ActionV7";
import {
Button,
CodeSnippet,
CodeSnippetBlockAppearance,
ContextualMenu,
Icon,
Modal,
ModularTable,
} from "@canonical/react-components";
import type { ReactNode } from "react";
Expand All @@ -23,7 +20,6 @@ import LoadingSpinner from "components/LoadingSpinner/LoadingSpinner";
import RelativeDate from "components/RelativeDate";
import type { EntityDetailsRoute } from "components/Routes";
import Status from "components/Status";
import { copyToClipboard } from "components/utils";
import useInlineErrors from "hooks/useInlineErrors";
import { useQueryActionsList, useQueryOperationsList } from "juju/api-hooks";
import PanelInlineErrors from "panels/PanelInlineErrors";
Expand All @@ -33,6 +29,7 @@ import type { RootState } from "store/store";
import urls from "urls";

import "./_action-logs.scss";
import ActionPayloadModal from "./ActionPayloadModal";
import { Label, Output } from "./types";

type Operations = OperationResult[];
Expand Down Expand Up @@ -66,18 +63,6 @@ enum InlineErrors {
FETCH = "fetch",
}

function generateLinkToApp(
appName: string,
userName: string,
modelName: string,
) {
return (
<Link to={urls.model.app.index({ userName, modelName, appName })}>
{appName}
</Link>
);
}

function generateAppIcon(
application: ApplicationData | undefined,
appName: string,
Expand All @@ -91,43 +76,15 @@ function generateAppIcon(
return (
<>
<CharmIcon name={appName} charmId={application.charm} />
{generateLinkToApp(appName, userName, modelName)}
<Link to={urls.model.app.index({ userName, modelName, appName })}>
{appName}
</Link>
</>
);
}
return <>{appName}</>;
}

function ActionPayloadModal(props: {
payload: ActionResult["output"] | null;
onClose: () => void;
}) {
if (!props.payload) return <></>;
const json = JSON.stringify(props.payload, null, 2);
return (
<Modal
close={props.onClose}
title="Action result payload"
buttonRow={
<Button appearance="neutral" onClick={() => copyToClipboard(json)}>
{Label.COPY}
</Button>
}
data-testid="action-payload-modal"
>
<CodeSnippet
blocks={[
{
appearance: CodeSnippetBlockAppearance.NUMBERED,
wrapLines: true,
code: json,
},
]}
/>
</Modal>
);
}

const compare = (a: string | number, b: string | number) =>
a === b ? 0 : a > b ? 1 : -1;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi } from "vitest";

import * as componentUtils from "components/utils";
import { renderComponent } from "testing/utils";

import ActionPayloadModal from "./ActionPayloadModal";
import { Label } from "./types";

vi.mock("components/utils", async () => {
const utils = await vi.importActual("components/utils");
return {
...utils,
copyToClipboard: vi.fn(),
};
});

describe("ActionPayloadModal", () => {
const mockPayload = { key1: "value1", test: 123 };

it("should return null if payload is null", () => {
const {
result: { container },
} = renderComponent(
<ActionPayloadModal payload={null} onClose={vi.fn()} />,
);
expect(container.tagName).toBe("DIV");
expect(container.children.length).toBe(1);
expect(container.firstChild).toBeEmptyDOMElement();
});

it("should display action payload modal if payload is not null", async () => {
renderComponent(
<ActionPayloadModal payload={mockPayload} onClose={vi.fn()} />,
);
const actionPayloadModal = screen.getByTestId("action-payload-modal");
expect(actionPayloadModal).toBeInTheDocument();
expect(
within(actionPayloadModal).getByRole("dialog", { name: Label.TITLE }),
).toBeInTheDocument();
const codeSnippetLines = document.querySelectorAll(".p-code-snippet__line");
expect(codeSnippetLines).toHaveLength(4);
expect(codeSnippetLines[0]).toHaveTextContent("{");
expect(codeSnippetLines[1]).toHaveTextContent('"key1": "value1",');
expect(codeSnippetLines[2]).toHaveTextContent('"test": 123');
expect(codeSnippetLines[3]).toHaveTextContent("}");
});

it("should copy the payload to clipboard", async () => {
renderComponent(
<ActionPayloadModal payload={mockPayload} onClose={vi.fn()} />,
);
await userEvent.click(screen.getByRole("button", { name: Label.COPY }));
expect(componentUtils.copyToClipboard).toHaveBeenCalledWith(`{
"key1": "value1",
"test": 123
}`);
});

it("should close the modal", async () => {
const onClose = vi.fn();
renderComponent(
<ActionPayloadModal payload={mockPayload} onClose={onClose} />,
);
await userEvent.click(
screen.getByRole("button", { name: "Close active modal" }),
);
expect(onClose).toHaveBeenCalledOnce();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { ActionResult } from "@canonical/jujulib/dist/api/facades/action/ActionV7";
import {
Button,
CodeSnippet,
CodeSnippetBlockAppearance,
Modal,
} from "@canonical/react-components";

import { copyToClipboard } from "components/utils";

import { Label } from "./types";

type Props = {
payload: ActionResult["output"] | null;
onClose: () => void;
};

const ActionPayloadModal = ({
payload,
onClose,
}: Props): JSX.Element | null => {
if (!payload) {
return null;
}
const json = JSON.stringify(payload, null, 2);
return (
<Modal
close={onClose}
title={Label.TITLE}
buttonRow={
<Button appearance="neutral" onClick={() => copyToClipboard(json)}>
{Label.COPY}
</Button>
}
data-testid="action-payload-modal"
>
<CodeSnippet
blocks={[
{
appearance: CodeSnippetBlockAppearance.NUMBERED,
wrapLines: true,
code: json,
},
]}
/>
</Modal>
);
};

export default ActionPayloadModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./ActionPayloadModal";
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum Label {
TITLE = "Action result payload",
COPY = "Copy to clipboard",
}
1 change: 0 additions & 1 deletion src/pages/EntityDetails/Model/Logs/ActionLogs/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export enum Label {
OUTPUT = "Output",
COPY = "Copy to clipboard",
FETCH_ERROR = "Error while trying to fetch data.",
}

Expand Down