Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Aiven-Open/klaw into json-support
Browse files Browse the repository at this point in the history
  • Loading branch information
muralibasani committed Feb 28, 2024
2 parents a49a96b + ada600d commit c5845e7
Show file tree
Hide file tree
Showing 32 changed files with 953 additions and 580 deletions.
2 changes: 1 addition & 1 deletion coral/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
]
},
"dependencies": {
"@aivenio/aquarium": "^1.55.0",
"@aivenio/aquarium": "1.57.1",
"@hookform/resolvers": "^2.9.10",
"@monaco-editor/react": "^4.6.0",
"@tanstack/react-query": "^4.29.5",
Expand Down
523 changes: 26 additions & 497 deletions coral/pnpm-lock.yaml

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions coral/src/app/components/Clipboard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { render, screen, cleanup, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import ClipBoard from "src/app/components/Clipboard";

describe("ClipBoard", () => {
const mockCopyToClipboard = jest.fn();
Object.defineProperty(global.navigator, "clipboard", {
value: { writeText: mockCopyToClipboard },
writable: true,
});

const props = {
text: "Test text",
accessibleCopyDescription: "Copy to clipboard",
accessibleCopiedDescription: "Copied to clipboard",
description: "Test description",
};

beforeEach(() => {
render(<ClipBoard {...props} />);
});

afterEach(() => {
cleanup();
jest.clearAllMocks();
});

it("renders correctly", () => {
const button = screen.getByRole("button", {
name: props.accessibleCopyDescription,
});
expect(button).toBeVisible();
});

it("copies text to clipboard when button is clicked", async () => {
const button = screen.getByRole("button", {
name: props.accessibleCopyDescription,
});
await userEvent.click(button);
await waitFor(() =>
expect(mockCopyToClipboard).toHaveBeenCalledWith(props.text)
);
});

it("renders feedback tooltip after button is clicked", async () => {
const button = screen.getByRole("button", {
name: props.accessibleCopyDescription,
});
await userEvent.click(button);
await waitFor(() => expect(screen.getByText("Copied")).toBeVisible());
});

it("renders accessible feedback after button is clicked", async () => {
const button = screen.getByRole("button", {
name: props.accessibleCopyDescription,
});
await userEvent.click(button);
await waitFor(() =>
expect(
screen.getByLabelText(props.accessibleCopiedDescription)
).toBeInTheDocument()
);
});
});
85 changes: 85 additions & 0 deletions coral/src/app/components/Clipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Tooltip, PositionerPlacement, Button } from "@aivenio/aquarium";
import duplicate from "@aivenio/aquarium/icons/duplicate";
import { useRef, useState, useEffect, FormEvent } from "react";

const copyToClipboard = async (text: string): Promise<void> => {
try {
await navigator.clipboard.writeText(text);
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
};

const ClipBoard = ({
text,
accessibleCopyDescription,
accessibleCopiedDescription,
description,
}: {
text: string;
accessibleCopyDescription: string;
accessibleCopiedDescription: string;
description?: string;
}) => {
const feedbackTimerRef = useRef<number>();

const [showCopyFeedback, setShowCopyFeedback] = useState(false);

const clearTimeouts = () => {
window.clearTimeout(feedbackTimerRef.current);
};

useEffect(() => () => clearTimeouts(), []);

function handleCopy(event: FormEvent) {
event.preventDefault();
copyToClipboard(text);
setShowCopyFeedback(true);
feedbackTimerRef.current = window.setTimeout(
() => setShowCopyFeedback(false),
1000
);
}

if (showCopyFeedback) {
return (
<Tooltip
key="copied"
content="Copied"
isOpen
placement={PositionerPlacement.top}
>
<Button.SecondaryGhost
key="copy-button"
aria-label={accessibleCopiedDescription}
onClick={handleCopy}
icon={duplicate}
>
{description}
</Button.SecondaryGhost>
<div aria-live="polite" className={"visually-hidden"}>
{accessibleCopiedDescription}
</div>
</Tooltip>
);
}

return (
<Tooltip
key="copy-to-clipboard"
content="Copy"
placement={PositionerPlacement.top}
>
<Button.SecondaryGhost
key="copy-button"
aria-label={accessibleCopyDescription}
onClick={handleCopy}
icon={duplicate}
>
{description}
</Button.SecondaryGhost>
</Tooltip>
);
};

export default ClipBoard;
2 changes: 1 addition & 1 deletion coral/src/app/components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function Modal(props: ModalProps) {
flexDirection={"column"}
borderRadius={"4px"}
backgroundColor={"white"}
width={"6/12"}
width={"7/12"}
// value is arbitrary, it should prevent buttons overflowing
// the modal in a very small screen
//eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ describe("SearchClusterParamFilter.tsx", () => {

it("renders a search input", () => {
const searchInput = screen.getByRole("search", {
name: "Search Cluster params",
name: "Search Cluster parameters",
});

expect(searchInput).toBeEnabled();
});

it("shows a placeholder with an example search value", () => {
const searchInput = screen.getByRole<HTMLInputElement>("search", {
name: "Search Cluster params",
name: "Search Cluster parameters",
});

expect(searchInput.placeholder).toEqual("kafkaconnect");
});

it("shows a description", () => {
const searchInput = screen.getByRole<HTMLInputElement>("search", {
name: "Search Cluster params",
name: "Search Cluster parameters",
});

expect(searchInput).toHaveAccessibleDescription(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SearchFilter } from "src/app/features/components/filters/SearchFilter";
function SearchClusterParamFilter() {
return (
<SearchFilter
label={"Search Cluster params"}
label={"Search Cluster parameters"}
placeholder={"kafkaconnect"}
description={
"Partial match for: Cluster name, bootstrap server and protocol."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ describe("<AddNewClusterForm />", () => {
name: "Kafka Connect",
});
const schemaRegistryOption = screen.getByRole("radio", {
name: "Schema registry",
name: "Schema Registry",
});
const protocolSelect = screen.getByRole("combobox", {
name: "Protocol *",
Expand Down Expand Up @@ -280,7 +280,7 @@ describe("<AddNewClusterForm />", () => {
position: "bottom-left",
});
expect(mockedUsedNavigate).toHaveBeenCalledWith(
"/configuration/clusters?search=MyCluster"
"/configuration/clusters?search=MyCluster&showConnectHelp=true"
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
import { addNewCluster } from "src/domain/cluster";
import { parseErrorMsg } from "src/services/mutation-utils";
import { Routes } from "src/services/router-utils/types";
import { kafkaFlavorToString } from "src/services/formatter/kafka-flavor-formatter";
import { clusterTypeToString } from "src/services/formatter/cluster-type-formatter";

const AddNewClusterForm = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -54,7 +56,7 @@ const AddNewClusterForm = () => {

const addNewClusterMutation = useMutation(addNewCluster, {
onSuccess: () => {
navigate(`${Routes.CLUSTERS}?search=${clusterName}`);
navigate(`${Routes.CLUSTERS}?search=${clusterName}&showConnectHelp=true`);
toast({
message: "Cluster successfully added",
position: "bottom-left",
Expand Down Expand Up @@ -89,13 +91,13 @@ const AddNewClusterForm = () => {
required
>
<BaseRadioButton name="KAFKA" value="KAFKA">
Kafka
{clusterTypeToString["KAFKA"]}
</BaseRadioButton>
<BaseRadioButton name="KAFKA_CONNECT" value="KAFKA_CONNECT">
Kafka Connect
{clusterTypeToString["KAFKA_CONNECT"]}
</BaseRadioButton>
<BaseRadioButton name="SCHEMA_REGISTRY" value="SCHEMA_REGISTRY">
Schema registry
{clusterTypeToString["SCHEMA_REGISTRY"]}
</BaseRadioButton>
</RadioButtonGroup>
<TextInput<AddNewClusterFormSchema>
Expand Down Expand Up @@ -136,11 +138,17 @@ const AddNewClusterForm = () => {
placeholder="-- Please select --"
required
>
<Option value="APACHE_KAFKA">Apache Kafka</Option>
<Option value="AIVEN_FOR_APACHE_KAFKA">Aiven for Apache Kafka</Option>
<Option value="CONFLUENT">Confluent</Option>
<Option value="CONFLUENT_CLOUD">Confluent Cloud</Option>
<Option value="OTHERS">Others</Option>
<Option value="APACHE_KAFKA">
{kafkaFlavorToString["APACHE_KAFKA"]}
</Option>
<Option value="AIVEN_FOR_APACHE_KAFKA">
{kafkaFlavorToString["AIVEN_FOR_APACHE_KAFKA"]}
</Option>
<Option value="CONFLUENT">{kafkaFlavorToString["CONFLUENT"]}</Option>
<Option value="CONFLUENT_CLOUD">
{kafkaFlavorToString["CONFLUENT_CLOUD"]}
</Option>
<Option value="OTHERS">{kafkaFlavorToString["OTHERS"]}</Option>
</NativeSelect>

{kafkaFlavor === "AIVEN_FOR_APACHE_KAFKA" && clusterType === "KAFKA" && (
Expand Down
44 changes: 42 additions & 2 deletions coral/src/app/features/configuration/clusters/Clusters.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ describe("Clusters.tsx", () => {

it("renders a search field for cluster params", () => {
const search = screen.getByRole("search", {
name: "Search Cluster params",
name: "Search Cluster parameters",
});

expect(search).toBeEnabled();
Expand Down Expand Up @@ -289,7 +289,7 @@ describe("Clusters.tsx", () => {
const testSearchInput = "MyCluster";

const search = screen.getByRole("search", {
name: "Search Cluster params",
name: "Search Cluster parameters",
});

await user.type(search, testSearchInput);
Expand All @@ -304,4 +304,44 @@ describe("Clusters.tsx", () => {
);
});
});

describe("renders ClusterConnectHelpModal on first load when needed", () => {
beforeAll(async () => {
mockGetClustersPaginated.mockResolvedValue({
currentPage: 1,
totalPages: 1,
entries: [testCluster[0]],
});

customRender(<Clusters />, {
queryClient: true,
memoryRouter: true,
customRoutePath: `/configuration/clusters?search=${testCluster[0].clusterName}&showConnectHelp=true`,
});

await waitForElementToBeRemoved(screen.getByTestId("skeleton-table"));
});

afterAll(() => {
cleanup();
jest.resetAllMocks();
});

it("renders ClusterConnectHelpModal on first render if correct query params are set", () => {
const modal = screen.getByRole("dialog");
expect(modal).toBeVisible();
expect(within(modal).getByText("Connect cluster to Klaw")).toBeVisible();
});

it("remove showConnectHelp query param when closing ClusterConnectHelpModal ", async () => {
const modal = screen.getByRole("dialog");
expect(modal).toBeVisible();

const closeButton = within(modal).getByRole("button", { name: "Done" });

await userEvent.click(closeButton);

expect(window.location.search).not.toContain("showConnectHelp=true");
});
});
});
Loading

0 comments on commit c5845e7

Please sign in to comment.