Skip to content

Commit

Permalink
Merge pull request #543 from openSUSE/calculated_tooltip
Browse files Browse the repository at this point in the history
Tooltip for the autocalculated partition size limits
  • Loading branch information
lslezak committed Apr 27, 2023
2 parents 76fd018 + 25cda5f commit 83b1558
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 4 deletions.
6 changes: 6 additions & 0 deletions web/package/cockpit-agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu Apr 27 08:24:27 UTC 2023 - Ladislav Slezák <lslezak@suse.com>

- Display details for the "autocalculated" label in the storage
settings

-------------------------------------------------------------------
Tue Apr 25 13:42:22 UTC 2023 - David Diaz <dgonzalez@suse.com>

Expand Down
5 changes: 5 additions & 0 deletions web/src/assets/styles/blocks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,8 @@ section > .content {
--pf-c-progress--GridGap: var(--spacer-small);
}
}

// compact lists in popover
.pf-c-popover li + li {
margin: 0;
}
4 changes: 4 additions & 0 deletions web/src/assets/styles/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@
/** END block-size fallbacks **/
block-size: 95dvh;
}

.cursor-pointer {
cursor: pointer;
}
43 changes: 43 additions & 0 deletions web/src/components/core/Description.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright (c) [2023] 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.
*/

import React from "react";
import { Popover } from "@patternfly/react-core";

/**
* Displays details popup after clicking the children elements
* @component
*
* @param {(JSX.Element|null)} description content displayed in a popup
* @param {JSX.Element} children the wrapped content
*/
export default function Description ({ description, children, ...otherProps }) {
if (description) {
return (
<Popover showClose={false} bodyContent={description} {...otherProps}>
<span className="cursor-pointer">{children}</span>
</Popover>
);
}

// none or empty description, just return the children
return children;
}
68 changes: 68 additions & 0 deletions web/src/components/core/Description.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) [2023] 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.
*/

import React from "react";
import { screen } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import { Description } from "~/components/core";

describe("Description", () => {
const description = "Some great description";
const item = "Item with description";

it("displays the description after clicking the object", async () => {
const { user } = plainRender(<Description description={description}>{item}</Description>);

// the description is not displayed just after the render
expect(screen.queryByText(description)).not.toBeInTheDocument();
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();

// click it
const item_node = screen.getByText(item);
await user.click(item_node);

// then the description is visible in a dialog
screen.getByRole("dialog");
screen.getByText(description);
});

const expectNoPopup = async (content) => {
const { user } = plainRender(content);

const item_node = screen.getByText(item);
await user.click(item_node);

// do not display empty popup
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
};

it("displays the object without description when it is undefined", async () => {
expectNoPopup(<Description>{item}</Description>);
});

it("displays the object without description when it is null", async () => {
expectNoPopup(<Description description={null}>{item}</Description>);
});

it("displays the object without description when it is empty", async () => {
expectNoPopup(<Description description="">{item}</Description>);
});
});
47 changes: 47 additions & 0 deletions web/src/components/core/Tip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) [2023] 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.
*/

import React from "react";
import { Label } from "@patternfly/react-core";

import { Description } from "~/components/core";
import { Icon } from "~/components/layout";

/**
* Display a label with additional details. The details are displayed after
* clicking the label and the "i" icon indicates available details.
* If the label is not defined or is empty it behaves like a plain label.
* @component
*
* @param {JSX.Element} description details displayed after clicking the label
* @param {JSX.Element} children the content of the label
*/
export default function Tip ({ description, children }) {
if (description) {
return (
<Description description={description}>
<Label isCompact>{children}&nbsp;<Icon name="info" size="16" /></Label>
</Description>
);
}

return <Label isCompact>{children}</Label>;
}
67 changes: 67 additions & 0 deletions web/src/components/core/Tip.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) [2023] 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.
*/

import React from "react";
import { screen } from "@testing-library/react";
import { plainRender } from "~/test-utils";
import { Tip } from "~/components/core";

describe("Tip", () => {
const description = "Some great description";
const label = "Label";

describe("The description is not empty", () => {
it("displays the label with the 'info' icon and show the description after click", async () => {
const { user, container } = plainRender(<Tip description={description}>{label}</Tip>);

// an icon is displayed
expect(container.querySelector("svg")).toBeInTheDocument();

// the description is not displayed just after the render
expect(screen.queryByText(description)).not.toBeInTheDocument();
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();

// click it
const label_node = screen.getByText(label);
await user.click(label_node);

// then the description is visible in a dialog
screen.getByRole("dialog");
screen.getByText(description);
});
});

describe("The description is not defined", () => {
it("displays the label without the 'info' icon and clicking does not show any popup", async () => {
const { user, container } = plainRender(<Tip>{label}</Tip>);

// no icon
expect(container.querySelector("svg")).not.toBeInTheDocument();

// click it
const label_node = screen.getByText(label);
await user.click(label_node);

// no popup
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
});
});
2 changes: 2 additions & 0 deletions web/src/components/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

export { default as About } from "./About";
export { default as Description } from "./Description";
export { default as Disclosure } from "./Disclosure";
export { default as Sidebar } from "./Sidebar";
export { default as Section } from "./Section";
Expand All @@ -46,4 +47,5 @@ export { default as ProgressReport } from "./ProgressReport";
export { default as ProgressText } from "./ProgressText";
export { default as ValidationErrors } from "./ValidationErrors";
export { default as Terminal } from "./Terminal";
export { default as Tip } from "./Tip";
export { default as ShowTerminalButton } from "./ShowTerminalButton";
34 changes: 30 additions & 4 deletions web/src/components/storage/ProposalVolumes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ import React, { useState } from "react";
import {
Dropdown, DropdownToggle, DropdownItem,
Form, FormGroup, FormSelect, FormSelectOption,
List, ListItem,
Skeleton,
TextInput,
Toolbar, ToolbarContent, ToolbarItem
} from "@patternfly/react-core";
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
import { filesize } from "filesize";

import { Em, If, Popup, RowActions } from '~/components/core';
import { Em, If, Popup, RowActions, Tip } from '~/components/core';
import { Icon } from '~/components/layout';
import { noop } from "~/utils";

Expand All @@ -56,6 +57,33 @@ const sizeText = (size) => {
return filesize(size, { base: 2 });
};

/**
* Generates an hint describing which attributes affect the auto-calculated limits.
* If the limits are not affected then it returns `null`.
* @function
*
* @param {object} volume - storage volume object
* @returns {(ReactComponent|null)} component to display (can be `null`)
*/
const AutoCalculatedHint = (volume) => {
// no hint, the size is not affected by snapshots or other volumes
if (!volume.snapshotsAffectSizes && volume.sizeRelevantVolumes && volume.sizeRelevantVolumes.length === 0) {
return null;
}

return (
<>
These limits are affected by:
<List>
{volume.snapshotsAffectSizes &&
<ListItem>The configuration of snapshots</ListItem>}
{volume.sizeRelevantVolumes && volume.sizeRelevantVolumes.length > 0 &&
<ListItem>Presence of other volumes ({volume.sizeRelevantVolumes.join(", ")})</ListItem>}
</List>
</>
);
};

/**
* Form used for adding a new file system from a list of templates
* @component
Expand Down Expand Up @@ -239,12 +267,10 @@ const VolumeRow = ({ columns, volume, isLoading, onDelete }) => {
const limits = `${sizeText(volume.minSize)} - ${sizeText(volume.maxSize)}`;
const isAuto = volume.adaptiveSizes && !volume.fixedSizeLimits;

const autoModeIcon = <Icon name="auto_mode" size={12} />;

return (
<div className="split">
<span>{limits}</span>
<If condition={isAuto} then={<Em icon={autoModeIcon}>auto-calculated</Em>} />
<If condition={isAuto} then={<Tip description={AutoCalculatedHint(volume)}>auto</Tip>} />
</div>
);
};
Expand Down

0 comments on commit 83b1558

Please sign in to comment.