Skip to content

Commit

Permalink
Merge branch 'master' of github.com:inmanta/web-console into issue/59…
Browse files Browse the repository at this point in the history
…86-header-colors
  • Loading branch information
matborowczyk committed Oct 29, 2024
2 parents cf4adc9 + 7a154ab commit c1b9033
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 38 deletions.
6 changes: 6 additions & 0 deletions changelogs/unreleased/5942-expert-button-banner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Add button to disable expert mode from the banner
issue-nr: 5942
change-type: minor
destination-branches: [master, iso7]
sections:
minor-improvement: "{{description}}"
5 changes: 5 additions & 0 deletions changelogs/unreleased/6014-dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
change-type: patch
description: "Build(deps): Bump http-proxy-middleware from 2.0.6 to 2.0.7"
destination-branches:
- master
sections: {}
12 changes: 2 additions & 10 deletions cypress/e2e/scenario-2.4-expert-mode.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,8 @@ if (Cypress.env("edition") === "iso") {
// expect to be redirected on the inventory page, and table to be empty
cy.get('[aria-label="ServiceInventory-Empty"]').should("to.be.visible");

// At the end go back to settings and turn expert mode off
cy.get(".pf-v5-c-nav__item").contains("Settings").click();
cy.get("button").contains("Configuration").click();
cy.get('[aria-label="Row-enable_lsm_expert_mode"]')
.find(".pf-v5-c-switch")
.click();
cy.get('[data-testid="Warning"]').should("exist");
cy.get('[aria-label="Row-enable_lsm_expert_mode"]')
.find('[aria-label="SaveAction"]')
.click();
// At the end turn expert mode off through the banner
cy.get("button").contains("Disable").click();
cy.get('[data-testid="Warning"]').should("not.exist");
cy.get("[id='expert-mode-banner']").should("not.exist");
});
Expand Down
12 changes: 2 additions & 10 deletions cypress/e2e/scenario-2.4-old-expert-mode.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -353,16 +353,8 @@ if (Cypress.env("edition") === "iso") {
cy.get("button").contains("Yes").click();
cy.get('[aria-label="ServiceInventory-Empty"').should("to.be.visible");

// At the end go back to settings and turn expert mode off
cy.get(".pf-v5-c-nav__item").contains("Settings").click();
cy.get("button").contains("Configuration").click();
cy.get('[aria-label="Row-enable_lsm_expert_mode"]')
.find(".pf-v5-c-switch")
.click();
cy.get('[data-testid="Warning"]').should("exist");
cy.get('[aria-label="Row-enable_lsm_expert_mode"]')
.find('[aria-label="SaveAction"]')
.click();
// At the end turn expert mode off through the banner
cy.get("button").contains("Disable").click();
cy.get('[data-testid="Warning"]').should("not.exist");
cy.get("[id='expert-mode-banner']").should("not.exist");
});
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@inmanta/web-console",
"version": "2.0.1",
"version": "2.1.0",
"description": "Web Console for Inmanta Orchestrator",
"exports": "./dist/index.js",
"repository": "https://github.com/inmanta/web-console.git",
Expand Down
1 change: 1 addition & 0 deletions src/Data/Managers/V2/POST/UpdateEnvConfig/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./useUpdateEnvConfig";
71 changes: 71 additions & 0 deletions src/Data/Managers/V2/POST/UpdateEnvConfig/useUpdateEnvConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
UseMutationResult,
useMutation,
useQueryClient,
} from "@tanstack/react-query";
import { ParsedNumber } from "@/Core";
import { PrimaryBaseUrlManager } from "@/UI";
import { Dict } from "@/UI/Components";
import { useFetchHelpers } from "../../helpers";

interface ConfigUpdate {
id: string;
value: string | boolean | ParsedNumber | Dict;
}

/**
* React Query hook for updating environment configuration settings.
*
* @param {string} environment - The environment to use for creating headers.
* @returns {UseMutationResult<void, Error, ConfigUpdate, unknown>}- The mutation object from `useMutation` hook.
*/
export const useUpdateEnvConfig = (
environment: string,
): UseMutationResult<void, Error, ConfigUpdate, unknown> => {
const client = useQueryClient();

const baseUrlManager = new PrimaryBaseUrlManager(
globalThis.location.origin,
globalThis.location.pathname,
);
const { createHeaders, handleErrors } = useFetchHelpers();
const headers = createHeaders(environment);
const baseUrl = baseUrlManager.getBaseUrl(process.env.API_BASEURL);

/**
* Update the environment configuration setting.
*
* @param {ConfigUpdate} configUpdate - The info about the config setting to update
*
* @returns {Promise<void>} - The promise object of the fetch request.
* @throws {Error} If the response is not successful, an error with the error message is thrown.
*/
const updateConfig = async (configUpdate: ConfigUpdate): Promise<void> => {
const { id, value } = configUpdate;

const response = await fetch(
baseUrl + `/api/v2/environment_settings/${id}`,
{
method: "POST",
body: JSON.stringify({ value }),
headers,
},
);

await handleErrors(response);
};

return useMutation({
mutationFn: updateConfig,
mutationKey: ["update_env_config"],
onSuccess: () => {
client.invalidateQueries({
queryKey: ["get_env_config"], //for the future rework of the env getter
});
client.invalidateQueries({
queryKey: ["get_env_details"], //for the future rework of the env getter
});
document.dispatchEvent(new Event("settings-update"));
},
});
};
134 changes: 134 additions & 0 deletions src/UI/Components/ExpertBanner/ExpertBanner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React, { act } from "react";
import { MemoryRouter } from "react-router-dom";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { StoreProvider } from "easy-peasy";
import { HttpResponse, http } from "msw";
import { setupServer } from "msw/node";
import { getStoreInstance } from "@/Data";
import * as useUpdateEnvConfig from "@/Data/Managers/V2/POST/UpdateEnvConfig/useUpdateEnvConfig"; //import with that exact path is required for mock to work correctly
import { dependencies } from "@/Test";
import { DependencyProvider } from "@/UI/Dependency";
import { ExpertBanner } from "./ExpertBanner";

const setup = (flag: boolean) => {
const client = new QueryClient();

dependencies.environmentModifier.useIsExpertModeEnabled = jest.fn(() => flag);
const store = getStoreInstance();

return (
<MemoryRouter initialEntries={[{ search: "?env=aaa" }]}>
<DependencyProvider
dependencies={{
...dependencies,
}}
>
<QueryClientProvider client={client}>
<StoreProvider store={store}>
<ExpertBanner environmentId="aaa" />
</StoreProvider>
</QueryClientProvider>
</DependencyProvider>
</MemoryRouter>
);
};

describe("Given ExpertBanner", () => {
it("When expert_mode is set to true Then should render,", () => {
render(setup(true));

expect(
screen.getByText("LSM expert mode is enabled, proceed with caution."),
).toBeVisible();

expect(screen.getByText("Disable expert mode")).toBeVisible();
});

it("When expert_mode is set to true AND user clicks to disable expert mode it Then should fire mutation function", async () => {
const mutateSpy = jest.fn();
const spy = jest
.spyOn(useUpdateEnvConfig, "useUpdateEnvConfig")
.mockReturnValue({
data: undefined,
error: null,
failureCount: 0,
isError: false,
isIdle: false,
isSuccess: true,
isPending: false,
reset: jest.fn(),
isPaused: false,
context: undefined,
variables: {
id: "",
value: "",
},
failureReason: null,
submittedAt: 0,
mutateAsync: jest.fn(),
status: "success",
mutate: mutateSpy,
});

render(setup(true));

await act(async () => {
await userEvent.click(screen.getByText("Disable expert mode"));
});

expect(mutateSpy).toHaveBeenCalledWith({
id: "enable_lsm_expert_mode",
value: false,
});
spy.mockRestore();
});

it("When expert_mode is set to true AND user clicks to disable expert mode it AND something was wrong with the request Then AlertToast with error message should open", async () => {
const server = setupServer(
http.post(
"/api/v2/environment_settings/enable_lsm_expert_mode",
async () => {
return HttpResponse.json(
{
message: "Request or referenced resource does not exist",
},
{
status: 404,
},
);
},
),
);

server.listen();
render(setup(true));

await act(async () => {
await userEvent.click(screen.getByText("Disable expert mode"));
});

await waitFor(() => {
expect(screen.getByText("Something went wrong")).toBeVisible();
});
expect(
screen.getByText("Request or referenced resource does not exist"),
).toBeVisible();

expect(
screen.getByText("LSM expert mode is enabled, proceed with caution."),
).toBeVisible();
expect(screen.getByText("Disable expert mode")).toBeVisible();

server.close();
});

it("When expert_mode is set to false Then should not render,", () => {
render(setup(false));

expect(
screen.queryByText("LSM expert mode is enabled, proceed with caution."),
).toBeNull();
});
});
66 changes: 60 additions & 6 deletions src/UI/Components/ExpertBanner/ExpertBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,74 @@
import React, { useContext } from "react";
import { Banner } from "@patternfly/react-core";
import React, { useContext, useEffect, useState } from "react";
import { Banner, Button, Flex, Spinner } from "@patternfly/react-core";
import styled from "styled-components";
import { useUpdateEnvConfig } from "@/Data/Managers/V2/POST/UpdateEnvConfig";
import { DependencyContext } from "@/UI/Dependency";
import { words } from "@/UI/words";
import { ToastAlert } from "../ToastAlert";

export const ExpertBanner = () => {
interface Props {
environmentId: string;
}

/**
* A React component that displays a banner when the expert mode is enabled.
*
* @props {object} props - The properties passed to the component.
* @prop {string} environmentId -The ID of the environment.
* @returns { React.FC<Props> | null} The rendered banner if the expert mode is enabled, otherwise null.
*/
export const ExpertBanner: React.FC<Props> = ({ environmentId }) => {
const [errorMessage, setMessage] = useState<string | undefined>(undefined);
const { environmentModifier } = useContext(DependencyContext);
const { mutate, isError, error } = useUpdateEnvConfig(environmentId);
const [isLoading, setIsLoading] = useState(false); // isLoading is to indicate the asynchronous operation is in progress, as we need to wait until setting will be updated, getters are still in the V1 - task https://github.com/inmanta/web-console/issues/5999

useEffect(() => {
if (isError) {
setMessage(error.message);
setIsLoading(false);
}
}, [isError, error]);

return environmentModifier.useIsExpertModeEnabled() ? (
<React.Fragment>
<>
{isError && errorMessage && (
<ToastAlert
data-testid="ToastAlert"
title={words("error")}
message={errorMessage}
setMessage={setMessage}
/>
)}
<Banner
isSticky
variant="red"
id="expert-mode-banner"
aria-label="expertModeActive"
>
LSM expert mode is enabled, proceed with caution.
<Flex
justifyContent={{ default: "justifyContentCenter" }}
gap={{ default: "gapXs" }}
>
{words("banner.expertMode")}
<Button
variant="link"
isInline
onClick={() => {
setIsLoading(true);
mutate({ id: "enable_lsm_expert_mode", value: false });
}}
>
{words("banner.disableExpertMode")}
</Button>
{isLoading && <StyledSpinner size="sm" />}
</Flex>
</Banner>
</React.Fragment>
</>
) : null;
};

const StyledSpinner = styled(Spinner)`
margin-left: 0.5rem;
--pf-v5-c-spinner--Color: white;
`;
6 changes: 4 additions & 2 deletions src/UI/Components/LicenseBanner/LicenseBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useContext } from "react";
import { Banner } from "@patternfly/react-core";
import { Banner, Flex } from "@patternfly/react-core";
import { DependencyContext } from "@/UI/Dependency";
import { words } from "@/UI/words";

Expand All @@ -16,7 +16,9 @@ export const LicenseBanner: React.FC = () => {

return expirationMessage ? (
<Banner isSticky variant="red" aria-label="licenceExpired">
{expirationMessage}
<Flex justifyContent={{ default: "justifyContentCenter" }}>
{expirationMessage}
</Flex>
</Banner>
) : null;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface Props {
isOptional: boolean;
isDisabled?: boolean;
handleInputChange: (value) => void;
alreadySelected: string[];
alreadySelected: string[] | null;
multi?: boolean;
}

Expand Down
9 changes: 5 additions & 4 deletions src/UI/Components/UpdateBanner/UpdateBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useContext, useState } from "react";
import { Banner } from "@patternfly/react-core";
import { Banner, Flex } from "@patternfly/react-core";
import { ApiHelper } from "@/Core";
import { GetVersionFileQueryManager } from "@/Data/Managers/GetVersionFile/OnteTimeQueryManager";
import { DependencyContext } from "@/UI/Dependency";
import { words } from "@/UI/words";

interface Props {
apiHelper: ApiHelper;
Expand All @@ -25,9 +26,9 @@ export const UpdateBanner: React.FunctionComponent<Props> = (props) => {
const banner = (
<React.Fragment>
<Banner isSticky variant="gold" aria-label="newVersionAvailable">
You are running {currentVersion}, a new version is available! Please
hard-reload (Ctrl+F5 | Cmd + Shift + R) your page to load the new
version.
<Flex justifyContent={{ default: "justifyContentCenter" }}>
{words("banner.updateBanner")(currentVersion)}
</Flex>
</Banner>
</React.Fragment>
);
Expand Down
Loading

0 comments on commit c1b9033

Please sign in to comment.