Skip to content

Commit

Permalink
web: Move storage/SpacePolicyField to its own file
Browse files Browse the repository at this point in the history
And adapts it to use a core/Field/SettingsField button.
  • Loading branch information
dgdavid committed Apr 10, 2024
1 parent 035343d commit a0b0fec
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 110 deletions.
71 changes: 3 additions & 68 deletions web/src/components/storage/ProposalSettingsSection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ import React, { useEffect, useState } from "react";
import { Button, Checkbox, Form, Skeleton, Switch, Tooltip } from "@patternfly/react-core";

import { sprintf } from "sprintf-js";
import { _, n_ } from "~/i18n";
import { BootSelectionDialog, ProposalVolumes, SpacePolicyDialog } from "~/components/storage";
import { _ } from "~/i18n";
import { BootSelectionDialog, ProposalVolumes } from "~/components/storage";
import SpacePolicyField from "~/components/storage/SpacePolicyField";
import { If, PasswordAndConfirmationInput, Section, Popup } from "~/components/core";
import { Icon } from "~/components/layout";
import { noop } from "~/utils";
Expand Down Expand Up @@ -337,72 +338,6 @@ const BootConfigField = ({
);
};

/**
* Allows to select the space policy.
* @component
*
* @param {object} props
* @param {SpacePolicy|undefined} props.policy
* @param {SpaceAction[]} props.actions
* @param {StorageDevice[]} props.devices
* @param {boolean} props.isLoading
* @param {(config: SpacePolicyConfig) => void} props.onChange
*
* @typedef {object} SpacePolicyConfig
* @property {SpacePolicy} spacePolicy
* @property {SpaceAction[]} spaceActions
*/
const SpacePolicyField = ({
policy,
actions,
devices,
isLoading,
onChange
}) => {
const [isDialogOpen, setIsDialogOpen] = useState(false);

const openDialog = () => setIsDialogOpen(true);

const closeDialog = () => setIsDialogOpen(false);

const onAccept = ({ spacePolicy, spaceActions }) => {
closeDialog();
onChange({ spacePolicy, spaceActions });
};

const label = () => {
// eslint-disable-next-line agama-i18n/string-literals
if (policy.summaryLabels.length === 1) return _(policy.summaryLabels[0]);

// eslint-disable-next-line agama-i18n/string-literals
return sprintf(n_(policy.summaryLabels[0], policy.summaryLabels[1], devices.length), devices.length);
};

if (isLoading || !policy) {
return <Skeleton screenreaderText={_("Waiting for information about space policy")} width="25%" />;
}

return (
<div className="split">
<span>{_("Find space")}</span>
<Button variant="link" isInline onClick={openDialog}>{label()}</Button>
<If
condition={isDialogOpen}
then={
<SpacePolicyDialog
isOpen
policy={policy}
actions={actions}
devices={devices}
onAccept={onAccept}
onCancel={closeDialog}
/>
}
/>
</div>
);
};

/**
* Section for editing the proposal settings
* @component
Expand Down
41 changes: 0 additions & 41 deletions web/src/components/storage/ProposalSettingsSection.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,44 +237,3 @@ describe("Encryption field", () => {
});
});
});

describe("Space policy field", () => {
describe("if there is no space policy", () => {
beforeEach(() => {
props.settings = {};
});

it("does not render the space policy field", () => {
plainRender(<ProposalSettingsSection {...props} />);

expect(screen.queryByLabelText("Find space")).toBeNull();
});
});

describe("if there is a space policy", () => {
beforeEach(() => {
props.settings = {
spacePolicy: "delete"
};
});

it("renders the button with a text according to given policy", () => {
const { rerender } = plainRender(<ProposalSettingsSection {...props} />);
screen.getByRole("button", { name: /deleting/ });
rerender(<ProposalSettingsSection settings={{ spacePolicy: "resize" }} />);
screen.getByRole("button", { name: /shrinking/ });
});

it("allows to change the policy", async () => {
const { user } = plainRender(<ProposalSettingsSection {...props} />);
const button = screen.getByRole("button", { name: /deleting all content/ });

await user.click(button);

const popup = await screen.findByRole("dialog");
within(popup).getByText("Find space");
const cancel = within(popup).getByRole("button", { name: "Cancel" });
await user.click(cancel);
});
});
});
106 changes: 106 additions & 0 deletions web/src/components/storage/SpacePolicyField.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright (c) [2024] SUSE LLC
*
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, contact SUSE LLC.
*
* To contact SUSE LLC about this file by physical or electronic mail, you may
* find current contact information at www.suse.com.
*/

// @ts-check

import React, { useState } from "react";
import { Skeleton } from "@patternfly/react-core";

import { sprintf } from "sprintf-js";
import { _, n_ } from "~/i18n";
import { If, SettingsField } from "~/components/core";
import SpacePolicyDialog from "~/components/storage/SpacePolicyDialog";

/**
* @typedef {import ("~/client/storage").SpaceAction} SpaceAction
* @typedef {import ("~/components/storage/utils").SpacePolicy} SpacePolicy
* @typedef {import ("~/client/storage").StorageDevice} StorageDevice
*
* @typedef {object} SpacePolicyConfig
* @property {SpacePolicy} spacePolicy
* @property {SpaceAction[]} spaceActions
*/

/**
* Allows to select the space policy.
* @component
*
* @param {object} props
* @param {SpacePolicy|undefined} props.policy
* @param {SpaceAction[]} props.actions
* @param {StorageDevice[]} props.devices
* @param {boolean} props.isLoading
* @param {(config: SpacePolicyConfig) => void} props.onChange
*
*/
export default function SpacePolicyField({
policy,
actions,
devices,
isLoading,
onChange
}) {
const [isDialogOpen, setIsDialogOpen] = useState(false);

const openDialog = () => setIsDialogOpen(true);

const closeDialog = () => setIsDialogOpen(false);

const onAccept = ({ spacePolicy, spaceActions }) => {
closeDialog();
onChange({ spacePolicy, spaceActions });
};

let value;
if (isLoading || !policy) {
value = <Skeleton screenreaderText={_("Waiting for information about space policy")} width="25%" />;
} else if (policy.summaryLabels.length === 1) {
// eslint-disable-next-line agama-i18n/string-literals
value = _(policy.summaryLabels[0]);
} else {
// eslint-disable-next-line agama-i18n/string-literals
value = sprintf(n_(policy.summaryLabels[0], policy.summaryLabels[1], devices.length), devices.length);
}

return (
<SettingsField
label={_("Find space")}
value={value}
description={ _("Allocating the file systems might need to find free space \
in the installation device(s).")}
onClick={openDialog}
>
<If
condition={isDialogOpen}
then={
<SpacePolicyDialog
isOpen
policy={policy}
actions={actions}
devices={devices}
onAccept={onAccept}
onCancel={closeDialog}
/>
}
/>
</SettingsField>
);
}
71 changes: 71 additions & 0 deletions web/src/components/storage/SpacePolicyField.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) [2024] SUSE LLC
*
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, contact SUSE LLC.
*
* To contact SUSE LLC about this file by physical or electronic mail, you may
* find current contact information at www.suse.com.
*/

// @ts-check

import React from "react";
import { screen } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import { SPACE_POLICIES } from "~/components/storage/utils";
import SpacePolicyField from "~/components/storage/SpacePolicyField";

const sda = {
sid: 59,
description: "A fake disk for testing",
isDrive: true,
type: "disk",
vendor: "Micron",
model: "Micron 1100 SATA",
driver: ["ahci", "mmcblk"],
bus: "IDE",
busId: "",
transport: "usb",
dellBOSS: false,
sdCard: true,
active: true,
name: "/dev/sda",
size: 1024,
recoverableSize: 0,
systems : [],
udevIds: ["ata-Micron_1100_SATA_512GB_12563", "scsi-0ATA_Micron_1100_SATA_512GB"],
udevPaths: ["pci-0000:00-12", "pci-0000:00-12-ata"],
};

const keepPolicy = SPACE_POLICIES.find(p => p.id === "keep");

const props = {
devices: [sda],
policy: keepPolicy,
isLoading: false,
onChange: jest.fn(),
actions: [
{ device: "/dev/sda", action: "force_delete" },
]
};

describe("SpacePolicyField", () => {
it("renders a button for opening the space policy dialog", async () => {
const { user } = plainRender(<SpacePolicyField {...props} />);
const button = screen.getByRole("button");
await user.click(button);
screen.getByRole("dialog", { name: "Find space" });
});
});
2 changes: 1 addition & 1 deletion web/src/components/storage/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ const deviceLabel = (device) => {
const deviceChildren = (device) => {
const partitionTableChildren = (partitionTable) => {
const { partitions, unusedSlots } = partitionTable;
const children = partitions.concat(unusedSlots);
const children = partitions.concat(unusedSlots).filter(i => !!i);
return children.sort((a, b) => a.start < b.start ? -1 : 1);
};

Expand Down

0 comments on commit a0b0fec

Please sign in to comment.