Skip to content

Commit

Permalink
Merge dc6c80f into 7e43d58
Browse files Browse the repository at this point in the history
  • Loading branch information
joseivanlopez committed Apr 30, 2024
2 parents 7e43d58 + dc6c80f commit b8049bc
Show file tree
Hide file tree
Showing 26 changed files with 977 additions and 627 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def filesystem_sid
#
# @return [String] e.g., "ext4"
def filesystem_type
storage_device.filesystem.type.to_s
storage_device.filesystem.type.to_human_string
end

# Mount path of the file system.
Expand Down
4 changes: 2 additions & 2 deletions service/lib/agama/storage/volume_conversion/from_y2storage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def sizes_conversion(target)
planned = planned_device_for(target.mount_path)
return unless planned

target.min_size = planned.min
target.max_size = planned.max
target.min_size = planned.min if planned.respond_to?(:min)
target.max_size = planned.max if planned.respond_to?(:max)
end

# Planned device for the given mount path.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

describe "#filesystem_type" do
it "returns the file system type" do
expect(subject.filesystem_type).to eq("ext4")
expect(subject.filesystem_type).to eq("Ext4")
end
end

Expand Down
29 changes: 29 additions & 0 deletions web/src/client/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,35 @@ class ProposalManager {
return proxy.AvailableDevices.map(path => findDevice(systemDevices, path)).filter(d => d);
}

/**
* Gets the devices that can be selected as target for a volume.
*
* @returns {Promise<StorageDevice[]>}
*/
async getVolumeDevices() {
const availableDevices = await this.getAvailableDevices();

const isAvailable = (device) => {
const isChildren = (device, parentDevice) => {
const partitions = parentDevice.partitionTable?.partitions || [];
return !!partitions.find(d => d.name === device.name);
};

return (
!!availableDevices.find(d => d.name === device.name) ||
!!availableDevices.find(d => isChildren(device, d))
);
};

const allAvailable = (devices) => devices.every(isAvailable);

const system = await this.system.getDevices();
const mds = system.filter(d => d.type === "md" && allAvailable(d.devices));
const vgs = system.filter(d => d.type === "lvmVg" && allAvailable(d.physicalVolumes));

return [...availableDevices, ...mds, ...vgs];
}

/**
* Gets the list of meaningful mount points for the selected product
*
Expand Down
43 changes: 29 additions & 14 deletions web/src/components/core/ExpandableSelector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
import React, { useState } from "react";
import { Table, Thead, Tr, Th, Tbody, Td, ExpandableRowContent, RowSelectVariant } from "@patternfly/react-table";

/**
* @typedef {import("@patternfly/react-table").TableProps} TableProps
* @typedef {import("react").RefAttributes<HTMLTableElement>} HTMLTableProps
*/

/**
* An object for sharing data across nested maps
*
Expand Down Expand Up @@ -93,23 +98,26 @@ const sanitizeSelection = (selection, allowMultiple) => {
};

/**
* Build a expandable table with selectable items
* Build a expandable table with selectable items.
* @component
*
* @note It only accepts one nesting level.
*
* @param {object} props
* @param {ExpandableSelectorColumn[]} props.columns - Collection of objects defining columns.
* @param {boolean} [props.isMultiple=false] - Whether multiple selection is allowed.
* @param {object[]} props.items - Collection of items to be rendered.
* @param {string} [props.itemIdKey="id"] - The key for retrieving the item id.
* @param {(item: object) => Array<object>} [props.itemChildren=() => []] - Lookup method to retrieve children from given item.
* @param {(item: object) => boolean} [props.itemSelectable=() => true] - Whether an item will be selectable or not.
* @param {(item: object) => (string|undefined)} [props.itemClassNames=() => ""] - Callback that allows adding additional CSS class names to item row.
* @param {object[]} [props.itemsSelected=[]] - Collection of selected items.
* @param {string[]} [props.initialExpandedKeys=[]] - Ids of initially expanded items.
* @param {(selection: Array<object>) => void} [props.onSelectionChange=noop] - Callback to be triggered when selection changes.
* @param {object} [props.tableProps] - Props for {@link https://www.patternfly.org/components/table/#table PF/Table}.
* @typedef {object} ExpandableSelectorBaseProps
* @property {ExpandableSelectorColumn[]} [columns=[]] - Collection of objects defining columns.
* @property {boolean} [isMultiple=false] - Whether multiple selection is allowed.
* @property {object[]} [items=[]] - Collection of items to be rendered.
* @property {string} [itemIdKey="id"] - The key for retrieving the item id.
* @property {(item: object) => Array<object>} [itemChildren=() => []] - Lookup method to retrieve children from given item.
* @property {(item: object) => boolean} [itemSelectable=() => true] - Whether an item will be selectable or not.
* @property {(item: object) => (string|undefined)} [itemClassNames=() => ""] - Callback that allows adding additional CSS class names to item row.
* @property {object[]} [itemsSelected=[]] - Collection of selected items.
* @property {any[]} [initialExpandedKeys=[]] - Ids of initially expanded items.
* @property {(selection: Array<object>) => void} [onSelectionChange=noop] - Callback to be triggered when selection changes.
*
* @typedef {ExpandableSelectorBaseProps & TableProps & HTMLTableProps} ExpandableSelectorProps
*
* @param {ExpandableSelectorProps} props
*/
export default function ExpandableSelector({
columns = [],
Expand All @@ -126,7 +134,14 @@ export default function ExpandableSelector({
}) {
const [expandedItemsKeys, setExpandedItemsKeys] = useState(initialExpandedKeys);
const selection = sanitizeSelection(itemsSelected, isMultiple);
const isItemSelected = (item) => selection.includes(item);
const isItemSelected = (item) => {
const selected = selection.find((selectionItem) => {
return Object.hasOwn(selectionItem, itemIdKey) &&
selectionItem[itemIdKey] === item[itemIdKey];
});

return selected !== undefined || selection.includes(item);
};
const isItemExpanded = (key) => expandedItemsKeys.includes(key);
const toggleExpanded = (key) => {
if (isItemExpanded(key)) {
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/core/TreeTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import { Table, Thead, Tr, Th, Tbody, Td, TreeRowWrapper } from '@patternfly/rea

/**
* @typedef {object} TreeTableColumn
* @property {string} title
* @property {(any) => React.ReactNode} content
* @property {string} name
* @property {(object) => React.ReactNode} value
* @property {string} [classNames]
*/

Expand Down Expand Up @@ -82,14 +82,14 @@ export default function TreeTable({
const renderColumns = (item, treeRow) => {
return columns.map((c, cIdx) => {
const props = {
dataLabel: c.title,
dataLabel: c.name,
className: c.classNames
};

if (cIdx === 0) props.treeRow = treeRow;

return (
<Td key={cIdx} {...props}>{c.content(item)}</Td>
<Td key={cIdx} {...props}>{c.value(item)}</Td>
);
});
};
Expand Down Expand Up @@ -138,7 +138,7 @@ export default function TreeTable({
>
<Thead>
<Tr>
{ columns.map((c, i) => <Th key={i} className={c.classNames}>{c.title}</Th>) }
{ columns.map((c, i) => <Th key={i} className={c.classNames}>{c.name}</Th>) }
</Tr>
</Thead>
<Tbody>
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/storage/BootConfigField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const Button = ({ isBold = false, onClick }) => {
* @param {boolean} props.configureBoot
* @param {StorageDevice|undefined} props.bootDevice
* @param {StorageDevice|undefined} props.defaultBootDevice
* @param {StorageDevice[]} props.devices
* @param {StorageDevice[]} props.availableDevices
* @param {boolean} props.isLoading
* @param {(boot: BootConfig) => void} props.onChange
*
Expand All @@ -72,7 +72,7 @@ export default function BootConfigField({
configureBoot,
bootDevice,
defaultBootDevice,
devices,
availableDevices,
isLoading,
onChange
}) {
Expand Down Expand Up @@ -113,7 +113,7 @@ export default function BootConfigField({
configureBoot={configureBoot}
bootDevice={bootDevice}
defaultBootDevice={defaultBootDevice}
devices={devices}
availableDevices={availableDevices}
onAccept={onAccept}
onCancel={closeDialog}
/>
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/storage/BootSelectionDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const RadioOption = ({ id, onChange, defaultChecked, children }) => {
* @param {boolean} props.configureBoot - Whether the boot is configurable
* @param {StorageDevice|undefined} props.bootDevice - Currently selected booting device.
* @param {StorageDevice|undefined} props.defaultBootDevice - Default booting device.
* @param {StorageDevice[]} props.devices - Devices that user can select to boot from.
* @param {StorageDevice[]} props.availableDevices - Devices that user can select to boot from.
* @param {boolean} [props.isOpen=false] - Whether the dialog is visible or not.
* @param {function} [props.onCancel=noop]
* @param {(boot: Boot) => void} [props.onAccept=noop]
Expand All @@ -75,7 +75,7 @@ export default function BootSelectionDialog({
configureBoot: configureBootProp,
bootDevice: bootDeviceProp,
defaultBootDevice,
devices,
availableDevices,
isOpen,
onCancel = noop,
onAccept = noop,
Expand Down Expand Up @@ -161,7 +161,7 @@ partitions in the appropriate disk."
</div>
<DevicesFormSelect
aria-label={_("Choose a disk for placing the boot loader")}
devices={devices}
devices={availableDevices}
selectedDevice={bootDevice}
onChange={setBootDevice}
isDisabled={!isBootManual}
Expand Down
27 changes: 14 additions & 13 deletions web/src/components/storage/DeviceSelectionDialog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { _ } from "~/i18n";
import { deviceChildren } from "~/components/storage/utils";
import { ControlledPanels as Panels, Popup } from "~/components/core";
import { DeviceSelectorTable } from "~/components/storage";
import { noop } from "~/utils";
import { compact, noop } from "~/utils";

/**
* @typedef {import ("~/client/storage").ProposalTarget} ProposalTarget
Expand All @@ -46,20 +46,21 @@ const OPTIONS_NAME = "selection-mode";
* Renders a dialog that allows the user to select a target device for installation.
* @component
*
* @param {object} props
* @param {ProposalTarget} props.target
* @param {StorageDevice|undefined} props.targetDevice
* @param {StorageDevice[]} props.targetPVDevices
* @param {StorageDevice[]} props.devices - The actions to perform in the system.
* @param {boolean} [props.isOpen=false] - Whether the dialog is visible or not.
* @param {() => void} [props.onCancel=noop]
* @param {(target: Target) => void} [props.onAccept=noop]
* @typedef {object} DeviceSelectionDialogProps
* @property {ProposalTarget} target
* @property {StorageDevice|undefined} targetDevice
* @property {StorageDevice[]} targetPVDevices
* @property {StorageDevice[]} devices - The actions to perform in the system.
* @property {boolean} [isOpen=false] - Whether the dialog is visible or not.
* @property {() => void} [onCancel=noop]
* @property {(target: TargetConfig) => void} [onAccept=noop]
*
* @typedef {object} Target
* @typedef {object} TargetConfig
* @property {string} target
* @property {StorageDevice|undefined} targetDevice
* @property {StorageDevice[]} targetPVDevices
*
* @param {DeviceSelectionDialogProps} props
*/
export default function DeviceSelectionDialog({
target: defaultTarget,
Expand Down Expand Up @@ -149,7 +150,7 @@ devices.").split(/[[\]]/);
<DeviceSelectorTable
aria-label={_("Device selector for target disk")}
devices={devices}
selected={[targetDevice]}
selectedDevices={compact([targetDevice])}
itemChildren={deviceChildren}
itemSelectable={isDeviceSelectable}
onSelectionChange={selectTargetDevice}
Expand All @@ -166,7 +167,7 @@ devices.").split(/[[\]]/);
aria-label={_("Device selector for new LVM volume group")}
isMultiple
devices={devices}
selected={targetPVDevices}
selectedDevices={targetPVDevices}
itemChildren={deviceChildren}
itemSelectable={isDeviceSelectable}
onSelectionChange={setTargetPVDevices}
Expand Down
13 changes: 13 additions & 0 deletions web/src/components/storage/DeviceSelectionDialog.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ import { screen, within } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import { DeviceSelectionDialog } from "~/components/storage";

/**
* @typedef {import ("~/client/storage").StorageDevice} StorageDevice
* @typedef {import("./DeviceSelectionDialog").DeviceSelectionDialogProps} DeviceSelectionDialogProps
*/

/** @type {StorageDevice} */
const sda = {
sid: 59,
isDrive: true,
Expand All @@ -40,13 +46,15 @@ const sda = {
sdCard: true,
active: true,
name: "/dev/sda",
description: "",
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"],
};

/** @type {StorageDevice} */
const sdb = {
sid: 62,
isDrive: true,
Expand All @@ -61,13 +69,15 @@ const sdb = {
sdCard: false,
active: true,
name: "/dev/sdb",
description: "",
size: 2048,
recoverableSize: 0,
systems : [],
udevIds: [],
udevPaths: ["pci-0000:00-19"]
};

/** @type {StorageDevice} */
const sdc = {
sid: 63,
isDrive: true,
Expand All @@ -82,13 +92,15 @@ const sdc = {
sdCard: false,
active: true,
name: "/dev/sdc",
description: "",
size: 2048,
recoverableSize: 0,
systems : [],
udevIds: [],
udevPaths: ["pci-0000:00-19"]
};

/** @type {DeviceSelectionDialogProps} */
let props;

const expectSelector = (selector) => {
Expand Down Expand Up @@ -124,6 +136,7 @@ describe("DeviceSelectionDialog", () => {
props = {
isOpen: true,
target: "DISK",
targetDevice: undefined,
targetPVDevices: [],
devices: [sda, sdb, sdc],
onCancel: jest.fn(),
Expand Down

0 comments on commit b8049bc

Please sign in to comment.