Skip to content
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
1 change: 0 additions & 1 deletion web/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jest.mock("~/client");
// Mock some components,
// See https://www.chakshunyu.com/blog/how-to-mock-a-react-component-in-jest/#default-export
jest.mock("~/components/layout/Loading", () => () => <div>Loading Mock</div>);
jest.mock("~/components/product/ProductSelectionProgress", () => () => <div>Product progress</div>);

it.todo("adapt to new api/hooks, adding needed mocking");
describe.skip("App", () => {
Expand Down
1 change: 0 additions & 1 deletion web/src/components/core/InstallButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe("InstallButton", () => {
["overview (full route)", ROOT.overview],
["login", ROOT.login],
["product selection", PRODUCT.changeProduct],
["product selection progress", PRODUCT.progress],
["installation progress", ROOT.installationProgress],
["installation finished", ROOT.installationFinished],
["installation exit", ROOT.installationExit],
Expand Down
61 changes: 53 additions & 8 deletions web/src/components/core/InstallationProgress.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2022-2024] SUSE LLC
* Copyright (c) [2022-2026] SUSE LLC
*
* All Rights Reserved.
*
Expand All @@ -21,16 +21,61 @@
*/

import React from "react";
import { Flex, Grid, GridItem, HelperText, HelperTextItem, Title } from "@patternfly/react-core";
import Page from "~/components/core/Page";
import ProgressReport from "~/components/core/ProgressReport";
import Icon from "~/components/layout/Icon";
import ProductLogo from "../product/ProductLogo";
import { useProductInfo } from "~/hooks/model/config/product";
import { _ } from "~/i18n";
import ProgressReport from "./ProgressReport";
import Page from "./Page";

function InstallationProgress() {
import textStyles from "@patternfly/react-styles/css/utilities/Text/text";
import alignmentStyles from "@patternfly/react-styles/css/utilities/Alignment/alignment";

export default function InstallationProgress() {
const product = useProductInfo();

return (
<Page showInstallerOptions={false}>
<ProgressReport title={_("Installing the system, please wait...")} />;
<Page showInstallerOptions={false} hideProgressMonitor>
<Page.Content>
<Grid hasGutter style={{ height: "100%", placeContent: "center" }}>
<GridItem sm={12} md={5} style={{ alignSelf: "center" }}>
<Flex
direction={{ default: "column" }}
alignItems={{ default: "alignItemsCenter", md: "alignItemsFlexEnd" }}
alignContent={{ default: "alignContentCenter", md: "alignContentFlexEnd" }}
alignSelf={{ default: "alignSelfCenter" }}
>
<Icon name="deployed_code_update" width="3rem" height="3rem" />
<Title
headingLevel="h1"
style={{ textWrap: "balance" }}
className={[textStyles.fontSize_3xl, alignmentStyles.textAlignEndOnMd].join(" ")}
>
<ProductLogo product={product} width="1.25em" /> {product?.name}
</Title>

<HelperText>
<HelperTextItem>{_("Installation in progress")}</HelperTextItem>
</HelperText>
</Flex>
</GridItem>
<GridItem sm={12} md={7}>
<Flex
gap={{ default: "gapMd" }}
alignItems={{ default: "alignItemsCenter" }}
style={{
minBlockSize: "30dvh",
boxShadow: "-1px 0 0 var(--pf-t--global--border--color--default)",
paddingInlineStart: "var(--pf-t--global--spacer--md)",
marginBlockStart: "var(--pf-t--global--spacer--xl)",
}}
>
<ProgressReport />
</Flex>
</GridItem>
</Grid>
</Page.Content>
</Page>
);
}

export default InstallationProgress;
3 changes: 1 addition & 2 deletions web/src/components/core/InstallerOptions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { Keymap, Locale } from "~/model/system/l10n";
import { Progress, Stage } from "~/model/status";
import { System } from "~/model/system/network";
import * as utils from "~/utils";
import { PRODUCT, ROOT } from "~/routes/paths";
import { ROOT } from "~/routes/paths";
import InstallerOptions, { InstallerOptionsProps } from "./InstallerOptions";
import { useStatus } from "~/hooks/model/status";

Expand Down Expand Up @@ -135,7 +135,6 @@ describe("InstallerOptions", () => {

describe.each([
["login", ROOT.login],
["product selection progress", PRODUCT.progress],
["installation progress", ROOT.installationProgress],
["installation finished", ROOT.installationFinished],
])(`when the installer is rendering the %s screen`, (_, path) => {
Expand Down
8 changes: 3 additions & 5 deletions web/src/components/core/InstallerOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2022-2025] SUSE LLC
* Copyright (c) [2022-2026] SUSE LLC
*
* All Rights Reserved.
*
Expand Down Expand Up @@ -51,7 +51,7 @@ import { useInstallerL10n } from "~/context/installerL10n";
import { localConnection } from "~/utils";
import { _ } from "~/i18n";
import supportedLanguages from "~/languages.json";
import { PRODUCT, ROOT, L10N } from "~/routes/paths";
import { ROOT, L10N } from "~/routes/paths";
import { useProductInfo } from "~/hooks/model/config/product";
import { useSystem } from "~/hooks/model/system";
import { useStatus } from "~/hooks/model/status";
Expand Down Expand Up @@ -575,9 +575,7 @@ export default function InstallerOptions({
stage === "installing" ||
// FIXME: below condition could be a problem for a question appearing while
// product progress
[ROOT.login, ROOT.installationProgress, ROOT.installationFinished, PRODUCT.progress].includes(
location.pathname,
);
[ROOT.login, ROOT.installationProgress, ROOT.installationFinished].includes(location.pathname);

if (skip) return;

Expand Down
10 changes: 10 additions & 0 deletions web/src/components/core/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ interface StandardLayoutProps {
showQuestions?: boolean;
/** Whether to show installer options in the header */
showInstallerOptions?: boolean;
/** Whether the progress monitor must not be mounted */
hideProgressMonitor?: boolean;
/** Page content */
children?: React.ReactNode;
}
Expand All @@ -349,6 +351,7 @@ const StandardLayout = ({
title,
showQuestions = true,
showInstallerOptions = false,
hideProgressMonitor = false,
}: StandardLayoutProps) => {
return (
<PFPage
Expand All @@ -358,6 +361,7 @@ const StandardLayout = ({
title={title}
breadcrumbs={breadcrumbs}
showInstallerOptions={showInstallerOptions}
hideProgressMonitor={hideProgressMonitor}
/>
}
>
Expand All @@ -380,6 +384,8 @@ interface BasePageProps {
progress?: ProgressBackdropProps;
/** Whether to show the Questions component at the bottom of the page */
showQuestions?: boolean;
/** Whether the progress monitor must not be mounted */
hideProgressMonitor?: boolean;
/** Page content */
children?: React.ReactNode;
}
Expand Down Expand Up @@ -410,6 +416,8 @@ interface MinimalPageProps extends BasePageProps {
breadcrumbs?: never;
/** Installer options not available in minimal variant */
showInstallerOptions?: never;
/** Whether the progress monitor must not be mounted */
hideProgressMonitor?: never;
}

/**
Expand Down Expand Up @@ -477,6 +485,7 @@ const Page = ({
variant = "standard",
showQuestions = true,
showInstallerOptions = false,
hideProgressMonitor = false,
children,
}: PageProps): React.ReactNode => {
if (variant === "minimal") {
Expand All @@ -490,6 +499,7 @@ const Page = ({
title={title}
showQuestions={showQuestions}
showInstallerOptions={showInstallerOptions}
hideProgressMonitor={hideProgressMonitor}
>
{children || <Outlet />}
</StandardLayout>
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/core/ProgressReport.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2022-2024] SUSE LLC
* Copyright (c) [2022-2026] SUSE LLC
*
* All Rights Reserved.
*
Expand Down Expand Up @@ -48,14 +48,14 @@ describe("ProgressReport", () => {
});

it("shows the progress including the details", () => {
plainRender(<ProgressReport title="Testing progress" />);
plainRender(<ProgressReport />);

expect(screen.getByText(/Partition disks/)).toBeInTheDocument();
expect(screen.getByText(/Install software/)).toBeInTheDocument();

// NOTE: not finding the whole text because it is now split in two <span> because of PF/Truncate
expect(screen.getByText(/Doing some/)).toBeInTheDocument();
expect(screen.getByText(/\(1\/1\)/)).toBeInTheDocument();
expect(screen.getByText(/Step 1 of 1/)).toBeInTheDocument();
});
});

Expand All @@ -80,14 +80,14 @@ describe("ProgressReport", () => {
});

it("shows the progress including the details", () => {
plainRender(<ProgressReport title="Testing progress" />);
plainRender(<ProgressReport />);

expect(screen.getByText(/Partition disks/)).toBeInTheDocument();
expect(screen.getByText(/Install software/)).toBeInTheDocument();

// NOTE: not finding the whole text because it is now split in two <span> because of PF/Truncate
expect(screen.getByText(/Installing vim/)).toBeInTheDocument();
expect(screen.getByText(/\(5\/200\)/)).toBeInTheDocument();
expect(screen.getByText(/Step 5 of 200/)).toBeInTheDocument();
});
});
});
67 changes: 13 additions & 54 deletions web/src/components/core/ProgressReport.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) [2022-2025] SUSE LLC
* Copyright (c) [2022-2026] SUSE LLC
*
* All Rights Reserved.
*
Expand All @@ -21,86 +21,64 @@
*/

import React from "react";
import { sprintf } from "sprintf-js";
import {
Content,
Flex,
ProgressStep,
ProgressStepper,
ProgressStepProps,
Spinner,
Truncate,
} from "@patternfly/react-core";
import sizingStyles from "@patternfly/react-styles/css/utilities/Sizing/sizing";
import Text from "~/components/core/Text";
import { _ } from "~/i18n";
import { useStatus } from "~/hooks/model/status";
import type { Progress as ProgressType } from "~/model/status";

type StepProps = {
id: string;
titleId: string;
isCurrent: boolean;
variant?: ProgressStepProps["variant"];
description?: ProgressStepProps["description"];
};

const Progress = ({
steps,
step,
firstStep,
detail,
}: {
steps: string[];
step: ProgressType;
firstStep: React.ReactNode;
detail: ProgressType | undefined;
}) => {
const stepProperties = (stepNumber: number): StepProps => {
const properties: StepProps = {
const stepProperties = (stepNumber: number) => {
const properties: ProgressStepProps = {
isCurrent: stepNumber === step.index,
id: `step-${stepNumber}-id`,
titleId: `step-${stepNumber}-title`,
};

if (stepNumber > step.index) {
properties.variant = "pending";
properties.description = <div>{_("Pending")}</div>;
}

if (properties.isCurrent) {
properties.variant = "info";
properties.icon = <Spinner size="sm" />;
if (detail && detail.step !== "") {
const { step: message, index, size } = detail;
properties.description = (
<Flex direction={{ default: "column" }} rowGap={{ default: "rowGapXs" }}>
<div>{_("In progress")}</div>
<div>
<Truncate content={message} trailingNumChars={12} position="middle" />
</div>
<div>{`(${index}/${size})`}</div>
<Truncate content={message} trailingNumChars={12} position="middle" />
<Text component="small">{sprintf(_("Step %1$d of %2$d"), index, size)}</Text>
</Flex>
);
}
}

if (stepNumber < step.index) {
properties.variant = "success";
properties.description = <div>{_("Finished")}</div>;
}

return properties;
};

return (
<ProgressStepper
isCenterAligned
className={[sizingStyles.w_100, sizingStyles.h_33OnMd].join(" ")}
>
{firstStep && (
<ProgressStep key="initial" variant="success">
{firstStep}
</ProgressStep>
)}
{steps.map((description: StepProps["description"], idx: number) => {
<ProgressStepper isVertical>
{steps.map((description, idx: number) => {
return (
<ProgressStep key={idx} {...stepProperties(idx + 1)}>
{description}
Expand All @@ -112,9 +90,9 @@ const Progress = ({
};

/**
* Shows progress steps when a product is selected.
* Renders progress with a PF/ProgresStepper
*/
function ProgressReport({ title, firstStep }: { title: string; firstStep?: React.ReactNode }) {
export default function ProgressReport() {
const { progresses } = useStatus();

const managerProgress = progresses.find((t) => t.scope === "manager");
Expand All @@ -125,24 +103,5 @@ function ProgressReport({ title, firstStep }: { title: string; firstStep?: React

const detail = softwareProgress || storageProgress;

return (
<Flex
direction={{ default: "column" }}
rowGap={{ default: "rowGapMd" }}
alignItems={{ default: "alignItemsCenter" }}
justifyContent={{ default: "justifyContentCenter" }}
className={sizingStyles.h_100OnMd}
>
<Spinner size="xl" />
<Content component="h1">{title}</Content>
<Progress
steps={managerProgress.steps}
step={managerProgress}
detail={detail}
firstStep={firstStep}
/>
</Flex>
);
return <Progress steps={managerProgress.steps} step={managerProgress} detail={detail} />;
}

export default ProgressReport;
Loading