Skip to content

Commit

Permalink
refactor(web): improve access to "Install", "Download logs", and "Ins…
Browse files Browse the repository at this point in the history
…taller options" actions (#1690)

## Problem

The `Install` button is kind of hidden in the `Overview` page, where
users have to come back after fixing installation issues (if any) for
start the installation. Twice effort for users: to guess where such a
button is and to come back to hit it once the installer is happy and
ready to proceed.

Additionally, the `Download logs` and `Installer options` actions are
mounted in the _application sidebar_, which is not always available,
making impossible to access them in screens such as _Product Selection_
or _Installation Finished_.

## Solution

Move all these actions to the top bar, which is always mounted, visible,
and accessible. To do so, many changes were needed here and there to.
See the related commits to know more.


## Notes

*  The`About` component has been deleted too since it was kind of useless at
this moment.
* Commit f10153b, which
removed the _Instalaltion.jsx_ component, might fix #1616
* All _.js_ and _.jsx_ touched files were migrated to TypeScript, which
made needed to add a _custom.d.ts_ file to avoid TypeScript complaints
at the time to import icons from `@material-symbols/svg-400` package.
See e02c3a2
and https://webpack.js.org/guides/typescript/#importing-other-assets for more details
* All copyright dates of touched files were properly updated.
* As said in the [commit](d14ff33),
**the warning issues link is kind of temporary until it gets improved too**
  • Loading branch information
dgdavid authored Oct 28, 2024
2 parents 56b8a8d + 956ca58 commit a099d97
Show file tree
Hide file tree
Showing 42 changed files with 954 additions and 829 deletions.
15 changes: 8 additions & 7 deletions web/src/components/core/Installation.jsx → web/custom.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2023] SUSE LLC
* Copyright (c) [2024] SUSE LLC
*
* All Rights Reserved.
*
Expand All @@ -20,11 +20,12 @@
* find current contact information at www.suse.com.
*/

import React from "react";
import { InstallationProgress, InstallationFinished } from "~/components/core";

function Installation({ isBusy }) {
return isBusy ? <InstallationProgress /> : <InstallationFinished />;
declare module "*.svg" {
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default content;
}

export default Installation;
declare module "*.svg?component" {
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default content;
}
6 changes: 6 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Mon Oct 28 19:26:29 UTC 2024 - David Diaz <[email protected]>

- Make some general actions more accessible
(gh#agama-project/agama#1690).

-------------------------------------------------------------------
Wed Oct 23 16:26:29 UTC 2024 - David Diaz <[email protected]>

Expand Down
46 changes: 30 additions & 16 deletions web/src/App.test.jsx → web/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2022-2023] SUSE LLC
* Copyright (c) [2022-2024] SUSE LLC
*
* All Rights Reserved.
*
Expand All @@ -23,10 +23,10 @@
import React from "react";
import { screen } from "@testing-library/react";
import { installerRender } from "~/test-utils";

import App from "./App";
import { createClient } from "~/client";
import { InstallationPhase } from "./types/status";
import { createClient } from "~/client";
import { Product } from "./types/software";

jest.mock("~/client");

Expand All @@ -39,9 +39,12 @@ jest.mock("~/api/l10n", () => ({
updateConfig: jest.fn(),
}));

const tumbleweed: Product = { id: "openSUSE", name: "openSUSE Tumbleweed" };
const microos: Product = { id: "Leap Micro", name: "openSUSE Micro" };

// list of available products
let mockProducts;
let mockSelectedProduct;
let mockProducts: Product[];
let mockSelectedProduct: Product;

jest.mock("~/queries/software", () => ({
...jest.requireActual("~/queries/software"),
Expand All @@ -62,6 +65,7 @@ jest.mock("~/queries/l10n", () => ({
jest.mock("~/queries/issues", () => ({
...jest.requireActual("~/queries/issues"),
useIssuesChanges: () => jest.fn(),
useAllIssues: () => ({ isEmtpy: true }),
}));

jest.mock("~/queries/storage", () => ({
Expand All @@ -88,22 +92,18 @@ jest.mock("~/context/installer", () => ({
// Mock some components,
// See https://www.chakshunyu.com/blog/how-to-mock-a-react-component-in-jest/#default-export
jest.mock("~/components/questions/Questions", () => () => <div>Questions Mock</div>);
jest.mock("~/components/core/Installation", () => () => <div>Installation Mock</div>);
jest.mock("~/components/layout/Loading", () => () => <div>Loading Mock</div>);
jest.mock("~/components/product/ProductSelectionProgress", () => () => <div>Product progress</div>);

describe("App", () => {
beforeEach(() => {
// setting the language through a cookie
document.cookie = "agamaLang=en-us; path=/;";
createClient.mockImplementation(() => {
(createClient as jest.Mock).mockImplementation(() => {
return {};
});

mockProducts = [
{ id: "openSUSE", name: "openSUSE Tumbleweed" },
{ id: "Leap Micro", name: "openSUSE Micro" },
];
mockProducts = [tumbleweed, microos];
});

afterEach(() => {
Expand Down Expand Up @@ -142,7 +142,7 @@ describe("App", () => {
describe("if the service is busy", () => {
beforeEach(() => {
mockClientStatus.isBusy = true;
mockSelectedProduct = { id: "Tumbleweed" };
mockSelectedProduct = tumbleweed;
});

it("redirects to product selection progress", async () => {
Expand All @@ -163,15 +163,29 @@ describe("App", () => {
});
});

describe("on the installaiton phase", () => {
describe("on the busy installaiton phase", () => {
beforeEach(() => {
mockClientStatus.phase = InstallationPhase.Install;
mockClientStatus.isBusy = true;
mockSelectedProduct = tumbleweed;
});

it("navigates to installation progress", async () => {
installerRender(<App />, { withL10n: true });
await screen.findByText("Navigating to /installation/progress");
});
});

describe("on the idle installaiton phase", () => {
beforeEach(() => {
mockClientStatus.phase = InstallationPhase.Install;
mockSelectedProduct = { id: "Fake product" };
mockClientStatus.isBusy = false;
mockSelectedProduct = tumbleweed;
});

it("renders the application content", async () => {
it("navigates to installation finished", async () => {
installerRender(<App />, { withL10n: true });
await screen.findByText("Installation Mock");
await screen.findByText("Navigating to /installation/finished");
});
});
});
54 changes: 29 additions & 25 deletions web/src/App.jsx → web/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2022-2023] SUSE LLC
* Copyright (c) [2022-2024] SUSE LLC
*
* All Rights Reserved.
*
Expand All @@ -22,26 +22,22 @@

import React from "react";
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { Loading } from "./components/layout";
import { ServerError } from "~/components/core";
import { Loading, PlainLayout } from "~/components/layout";
import { Questions } from "~/components/questions";
import { ServerError, Installation } from "~/components/core";
import { useInstallerL10n } from "./context/installerL10n";
import { useInstallerL10n } from "~/context/installerL10n";
import { useInstallerClientStatus } from "~/context/installer";
import { useProduct, useProductChanges } from "./queries/software";
import { useProduct, useProductChanges } from "~/queries/software";
import { useL10nConfigChanges } from "~/queries/l10n";
import { useIssuesChanges } from "./queries/issues";
import { useInstallerStatus, useInstallerStatusChanges } from "./queries/status";
import { useDeprecatedChanges } from "./queries/storage";
import { PATHS as PRODUCT_PATHS } from "./routes/products";
import SimpleLayout from "./SimpleLayout";
import { InstallationPhase } from "./types/status";
import { useIssuesChanges } from "~/queries/issues";
import { useInstallerStatus, useInstallerStatusChanges } from "~/queries/status";
import { useDeprecatedChanges } from "~/queries/storage";
import { PATHS as PRODUCT_PATHS } from "~/routes/products";
import { PATHS as ROOT_PATHS } from "~/router";
import { InstallationPhase } from "~/types/status";

/**
* Main application component.
*
* @param {object} props
* @param {number} [props.max_attempts=3] - Connection attempts before displaying an
* error (3 by default). The component will keep trying to connect.
*/
function App() {
const location = useLocation();
Expand All @@ -56,25 +52,33 @@ function App() {
useDeprecatedChanges();

const Content = () => {
if (error) return <ServerError />;
if (error)
return (
<PlainLayout>
<ServerError />
</PlainLayout>
);

if (phase === InstallationPhase.Install && isBusy) {
return <Navigate to={ROOT_PATHS.installationProgress} />;
}

if (phase === InstallationPhase.Install) {
return <Installation isBusy={isBusy} />;
if (phase === InstallationPhase.Install && !isBusy) {
return <Navigate to={ROOT_PATHS.installationFinished} />;
}

if (!products || !connected)
if (!products || !connected) return <Loading />;

if (phase === InstallationPhase.Startup && isBusy) {
return (
<SimpleLayout showOutlet={false}>
<PlainLayout>
<Loading />
</SimpleLayout>
</PlainLayout>
);

if (phase === InstallationPhase.Startup && isBusy) {
return <Loading />;
}

if (selectedProduct === undefined && location.pathname !== PRODUCT_PATHS.root) {
return <Navigate to="/products" />;
return <Navigate to={PRODUCT_PATHS.root} />;
}

if (
Expand Down
62 changes: 0 additions & 62 deletions web/src/SimpleLayout.jsx

This file was deleted.

25 changes: 23 additions & 2 deletions web/src/assets/styles/patternfly-overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,28 @@
--pf-v5-c-button--m-secondary--Color: var(--color-link-hover);
}

.pf-v5-c-modal-box__body {
// Redefine style for primary buttons placed at top bar
.pf-v5-c-masthead__content {
.pf-v5-c-button.pf-m-primary {
--pf-v5-c-button--FontSize: 120%;
--pf-v5-c-button--m-primary--BackgroundColor: var(--color-button-primary-hover);
letter-spacing: 1px;
}

.pf-v5-c-button.pf-m-primary:hover {
--pf-v5-c-button--m-primary--BackgroundColor: #1ea064; // var(--color-button-primary);
}

.pf-v5-c-button.pf-m-primary:focus:hover {
--pf-v5-c-button--m-primary--BackgroundColor: #1ea064; // var(--color-button-primary);
}

.pf-v5-c-button.pf-m-warning {
color: var(--color-button-primary);
}
}

.pf-v5-c-button.pf-m-primary .pf-v5-c-modal-box__body {
padding-block: var(--pf-v5-c-modal-box__body--PaddingTop);
}

Expand Down Expand Up @@ -272,7 +293,7 @@

// Allows the pf-m-current directly in the a element instead of li.
// Needed because setting the pf-m-current in ReactRouter/NavLink (the one
// that knowst that link "isActive")
// that know the link "isActive")

.pf-v5-c-tabs__link.pf-m-current {
--pf-v5-c-tabs__link--after--BorderColor: var(
Expand Down
Loading

0 comments on commit a099d97

Please sign in to comment.