From a0b0feca5ae59b4ce13dd43fb33fd2e3152988af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Tue, 9 Apr 2024 17:35:27 +0100 Subject: [PATCH] web: Move storage/SpacePolicyField to its own file And adapts it to use a core/Field/SettingsField button. --- .../storage/ProposalSettingsSection.jsx | 71 +----------- .../storage/ProposalSettingsSection.test.jsx | 41 ------- .../components/storage/SpacePolicyField.jsx | 106 ++++++++++++++++++ .../storage/SpacePolicyField.test.jsx | 71 ++++++++++++ web/src/components/storage/utils.js | 2 +- 5 files changed, 181 insertions(+), 110 deletions(-) create mode 100644 web/src/components/storage/SpacePolicyField.jsx create mode 100644 web/src/components/storage/SpacePolicyField.test.jsx diff --git a/web/src/components/storage/ProposalSettingsSection.jsx b/web/src/components/storage/ProposalSettingsSection.jsx index 3ae852af7b..57e10656d3 100644 --- a/web/src/components/storage/ProposalSettingsSection.jsx +++ b/web/src/components/storage/ProposalSettingsSection.jsx @@ -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"; @@ -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 ; - } - - return ( -
- {_("Find space")} - - - } - /> -
- ); -}; - /** * Section for editing the proposal settings * @component diff --git a/web/src/components/storage/ProposalSettingsSection.test.jsx b/web/src/components/storage/ProposalSettingsSection.test.jsx index 2b15463f95..29f7fc4723 100644 --- a/web/src/components/storage/ProposalSettingsSection.test.jsx +++ b/web/src/components/storage/ProposalSettingsSection.test.jsx @@ -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(); - - 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(); - screen.getByRole("button", { name: /deleting/ }); - rerender(); - screen.getByRole("button", { name: /shrinking/ }); - }); - - it("allows to change the policy", async () => { - const { user } = plainRender(); - 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); - }); - }); -}); diff --git a/web/src/components/storage/SpacePolicyField.jsx b/web/src/components/storage/SpacePolicyField.jsx new file mode 100644 index 0000000000..a1bc41e64c --- /dev/null +++ b/web/src/components/storage/SpacePolicyField.jsx @@ -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 = ; + } 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 ( + + + } + /> + + ); +} diff --git a/web/src/components/storage/SpacePolicyField.test.jsx b/web/src/components/storage/SpacePolicyField.test.jsx new file mode 100644 index 0000000000..a8b70c31fe --- /dev/null +++ b/web/src/components/storage/SpacePolicyField.test.jsx @@ -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(); + const button = screen.getByRole("button"); + await user.click(button); + screen.getByRole("dialog", { name: "Find space" }); + }); +}); diff --git a/web/src/components/storage/utils.js b/web/src/components/storage/utils.js index 6e45ad190c..1e842c211c 100644 --- a/web/src/components/storage/utils.js +++ b/web/src/components/storage/utils.js @@ -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); };