diff --git a/.github/workflows/pages-dbus-doc.yml b/.github/workflows/pages-dbus-doc.yml new file mode 100644 index 0000000000..3b803acb27 --- /dev/null +++ b/.github/workflows/pages-dbus-doc.yml @@ -0,0 +1,55 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: [$default-branch] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + pages_deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install DocBook tooling + run: | + sudo apt-get update + sudo apt-get --assume-yes --no-install-recommends install xmlto docbook-xsl xmlstarlet + + - name: Check that introspected API and its docs have not diverged + run: cd doc/dbus; make diff + + - name: Build HTML via DocBook + run: make -C doc + + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + # upload the built docs + path: 'doc/dist' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000000..849ddff3b7 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000000..011fe4a56b --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,4 @@ +all: + mkdir -p dist + $(MAKE) -C dbus + cp index.html dist diff --git a/doc/dbus/.gitignore b/doc/dbus/.gitignore new file mode 100644 index 0000000000..819af2d652 --- /dev/null +++ b/doc/dbus/.gitignore @@ -0,0 +1,2 @@ +tmp/ref-*.xml +tmp/*.iface.xml diff --git a/doc/dbus/Makefile b/doc/dbus/Makefile new file mode 100644 index 0000000000..426a6b824b --- /dev/null +++ b/doc/dbus/Makefile @@ -0,0 +1,50 @@ +distdir=../dist/dbus +tmpdir=./tmp + +# build HTML for GitHub pages +all: ${distdir} ${tmpdir} + for f in org.opensuse.Agama*.doc.xml; do \ + gdbus-codegen \ + --interface-prefix=org.opensuse.Agama. \ + --output-directory=${tmpdir} \ + --generate-docbook=ref \ + $$f; \ + docbook=${tmpdir}/ref-$${f%.doc.xml}.xml; \ + xmlto -o ${distdir} --skip-validation html-nochunks $$docbook; \ + done + cp index.html ${distdir} + +# 'foo 2> >(grep ... >&2)' greps stderr and keeps it as stderr +NO_COMMENTS=xmlstarlet canonic --without-comments 2> >(grep -v 'Attempt to load network entity' >&2) +# bash because of the >() process substitution +SHELL=/bin/bash + +# check that the implementation and documentation haven't diverged +# TODO: factor out a script to decouple Make syntax from the rest +diff: ${tmpdir} + ALL_GOOD=true; \ + for doc_xml in org.opensuse.Agama*.doc.xml; do \ + IFACE=$${doc_xml%.doc.xml}; \ + bus_xml=bus/$$IFACE.bus.xml; \ + doc_iface=${tmpdir}/$$IFACE.doc.iface.xml; \ + bus_iface=${tmpdir}/$$IFACE.bus.iface.xml; \ + \ + echo "Diffing $$IFACE"; \ + \ + $(NO_COMMENTS) \ + $${doc_xml} \ + > $${doc_iface}; \ + xmlstarlet ed \ + -d "//interface[@name!='$$IFACE']" \ + $${bus_xml} \ + | $(NO_COMMENTS) - \ + > $${bus_iface}; \ + diff --ignore-blank-lines --ignore-trailing-space -u $${doc_iface} $${bus_iface} || ALL_GOOD=false; \ + done; \ + $$ALL_GOOD + @echo "NO DIFF, YAY" + +${distdir}: + mkdir -p $@ +${tmpdir}: + mkdir -p $@ diff --git a/doc/dbus/README.md b/doc/dbus/README.md new file mode 100644 index 0000000000..ca8e059378 --- /dev/null +++ b/doc/dbus/README.md @@ -0,0 +1,19 @@ + + +## File Names + +**`*.doc.xml`**: documentation authored by humans in XML comments, +as specified in [`gdbus-codegen`][gd-cg]. + +[gd-cg]: https://developer-old.gnome.org/gio/stable/gdbus-codegen.html#id-1.4.25.7.9 + +`tmp/`**`ref-*.xml`**: intermediate, produced from `*.doc.xml`, contains DocBook +"**ref**entry" + +`../dist/dbus/`**`ref-*.html`**: rendered for publishing on GitHub Pages + +`bus/`**`*.bus.xml`**: output of D-Bus introspection + +`tmp/`**`*.iface.xml`**: intermediate, simplified `*.doc.xml` and `*.bus.xml` +to leave the common parts for diffing, see `make diff`. + diff --git a/doc/dbus/bus/README.md b/doc/dbus/bus/README.md new file mode 100644 index 0000000000..13d361c517 --- /dev/null +++ b/doc/dbus/bus/README.md @@ -0,0 +1 @@ +FIXME: describe how to maintain these (semi)automatically diff --git a/doc/dbus/bus/org.opensuse.Agama.Language1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Language1.bus.xml new file mode 100644 index 0000000000..1ab8363dd8 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Language1.bus.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Questions1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Questions1.bus.xml new file mode 100644 index 0000000000..53ba763367 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Questions1.bus.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Software1.Proposal.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Software1.Proposal.bus.xml new file mode 100644 index 0000000000..ae0a38b859 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Software1.Proposal.bus.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Software1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Software1.bus.xml new file mode 100644 index 0000000000..ae45ba2037 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Software1.bus.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml new file mode 100644 index 0000000000..8dca929024 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Storage1.bus.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama.Users1.bus.xml b/doc/dbus/bus/org.opensuse.Agama.Users1.bus.xml new file mode 100644 index 0000000000..ade59a4671 --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama.Users1.bus.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/org.opensuse.Agama1.Manager.bus.xml b/doc/dbus/bus/org.opensuse.Agama1.Manager.bus.xml new file mode 100644 index 0000000000..135f5d530d --- /dev/null +++ b/doc/dbus/bus/org.opensuse.Agama1.Manager.bus.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus/bus/seed.sh b/doc/dbus/bus/seed.sh new file mode 100755 index 0000000000..67f8a2b724 --- /dev/null +++ b/doc/dbus/bus/seed.sh @@ -0,0 +1,23 @@ +#!/bin/bash +abusctl() { + busctl --address=unix:path=/run/agama/bus "$@" +} + +DD=org.opensuse.Agama +SS=/${DD//./\/} + +abusctl introspect --xml-interface ${DD}1 ${SS}1/Manager \ + > ${DD}1.Manager.bus.xml + +look() { + abusctl tree --list $DD.${1%.*} + abusctl introspect --xml-interface $DD.${1%.*} $SS/${1//./\/} \ + > $DD.$1.bus.xml +} + +look Language1 +look Questions1 +look Software1 +look Software1.Proposal +look Storage1 +look Users1 diff --git a/doc/dbus/index.html b/doc/dbus/index.html new file mode 100644 index 0000000000..47ef104813 --- /dev/null +++ b/doc/dbus/index.html @@ -0,0 +1,6 @@ + diff --git a/doc/dbus/org.opensuse.Agama.Language1.doc.xml b/doc/dbus/org.opensuse.Agama.Language1.doc.xml new file mode 100644 index 0000000000..4fa4fbe82e --- /dev/null +++ b/doc/dbus/org.opensuse.Agama.Language1.doc.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + diff --git a/doc/dbus/org.opensuse.Agama1.Manager.doc.xml b/doc/dbus/org.opensuse.Agama1.Manager.doc.xml new file mode 100644 index 0000000000..8d136b54a7 --- /dev/null +++ b/doc/dbus/org.opensuse.Agama1.Manager.doc.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/dbus_api.md b/doc/dbus_api.md index ae35e462a9..40a6a2c04c 100644 --- a/doc/dbus_api.md +++ b/doc/dbus_api.md @@ -46,37 +46,10 @@ We use these resources to get more familiar with D-Bus API designing. Iface: o.o.Agama.Language1 -#### methods: - -- ToInstall(array(string LangId)) -> void - Set list of languages to install - Example: - - ToInstall(["cs_CZ", "de_DE"]) -> () # only lang codes from AvailableLanguages is supported - -#### Properties (all read only): - -- AvailableLanguages -> array(struct(string LangId, string LangLabel, dict(string, variant) details)) - List of all available languages to install on target system. - Example: - - AvailableLanguages -> [["cs_CZ", "Czech", {}]] # it is lang code, human readable lang name and dict for future extensions to provide more data - -- MarkedForInstall -> array(string LangId) - List of languages to install. Same format as ToInstall +See the new-style [reference][lang-ref] ([source][lang-src]). -#### Signals: - -- PropertiesChanged ( only standard one from org.freedesktop.DBus.Properties interface ) - - -notes: - -identifiers: maybe LanguageTag https://www.rubydoc.info/github/yast/yast-packager/master/LanguageTag -- move it to yast-yast2 -- link to the standard from yard -- see https://tools.ietf.org/html/rfc4647 Matching of Language Tags -- see https://lists.opensuse.org/archives/list/yast-devel@lists.opensuse.org/message/D52PSZ7TRID2RVM6CE6K2C2RUNNGOS6Z/ +[lang-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama.Language1.html +[lang-src]: dbus/org.opensuse.Agama.Language1.doc.xml ## Base Product @@ -803,62 +776,7 @@ any issue that might block the installation. ## Manager -### Installation Phases - -The installation process follows a set of phases. Only the main service (`Agama::Manager`) -knows the information about the current installation phase. The rest of services will act as utility -services without any knowledge about the whole installation process. - -A client (e.g., a web UI) will ask to the main service for the current phase of the installation. - -In principle, the installation will follow 3 possible phases: *Startup*, *Config* and *Install*. - -* *Startup* Phase - -This is the initial phase. The manager service will start in this phase and it will not change to -another phase until the client asks for performing the next phase. - -* *Config* Phase - -The installation is configured during this phase. Configuring the installation means that everything -needed from the system is read and the required default proposal are calculated. In YaST terms, the -*config* phase implies to probe some modules like storage, language, etc, and to perform their -proposals. Note that not all modules have to be probed/proposed. Probing some modules could be -delayed to the next *install* phase. - -* *Install* Phase - -This phase implies to perform everything to install the system according to the selected options and -proposals. Note that this phase is not only a typical YaST commit. For example, some proposals -(software?) could be done during this phase. In short, at the beginning of this phase we have all -the required information to perform the installation, and at the end of the phase the system is -installed. - -### Status of the Services - -Note that the services are blocked meanwhile they are performing a long task. For this -reason, the *manager* service will store the status of each service and the clients will ask to -*manager* to know that status. - -### org.opensuse.Agama1.Manager - -#### Properties - -- InstallationPhases -> array(array(dict(string, variant))) (r) - - All possible phases: -~~~ - [ - {"id" => 0, "label" => "startup"}, - {"id" => 1, "label" => "config"}, - {"id" => 2, "label" => "install"} - ] -~~~ - -- CurrentInstallationPhase -> unsigned 32-bit integer (r) - - Id of the current phase. - -- BusyServices -> a(s) (r) +See the new-style [reference][mgr-ref] ([source][mgr-src]). - List of names of the currently busy services. +[mgr-ref]: https://opensuse.github.io/agama/dbus/ref-org.opensuse.Agama1.Manager.html +[mgr-src]: dbus/org.opensuse.Agama1.Manager.doc.xml diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000000..58402d52f9 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,10 @@ + +Agama Documentation + + + + + + diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index ecef283806..df21528888 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -11,6 +11,29 @@ Mon Apr 24 15:53:35 UTC 2023 - David Diaz - Extract page options from the Sidebar to make them more discoverable (gh#openSUSE/agama#545) +------------------------------------------------------------------- +Thu Apr 27 08:24:27 UTC 2023 - Ladislav Slezák + +- Display details for the "autocalculated" label in the storage + settings + +------------------------------------------------------------------- +Tue Apr 25 13:42:22 UTC 2023 - David Diaz + +- UI: Fix dropdown content alignment at storage proposal page + (gh#openSUSE/agama#547). + +------------------------------------------------------------------- +Tue Apr 25 12:23:27 UTC 2023 - David Diaz + +- UI: Do not indent sections without icons (gh#openSUSE/agama#549). + +------------------------------------------------------------------- +Thu Apr 20 12:44:44 UTC 2023 - David Diaz + +- Make styles consistent when the user is hovering a button, + no matter if it is a focus state or not (gh#openSUSE/agama#544). + ------------------------------------------------------------------- Fri Apr 14 13:08:05 UTC 2023 - José Iván López González diff --git a/web/src/assets/styles/blocks.scss b/web/src/assets/styles/blocks.scss index f3dbec39cd..abea6d5559 100644 --- a/web/src/assets/styles/blocks.scss +++ b/web/src/assets/styles/blocks.scss @@ -3,7 +3,7 @@ // section layouts. section:not([class^="pf-c"]) { display: grid; - grid-template-columns: var(--icon-size-m) 1fr; + grid-template-columns: min-content 1fr; grid-template-areas: "icon title" ".... content"; @@ -262,3 +262,8 @@ span.notification-mark[data-variant="sidebar"] { padding-left: calc(var(--icon-size) + var(--pf-c-helper-text__item-icon--MarginRight)); } } + +// compact lists in popover +.pf-c-popover li + li { + margin: 0; +} diff --git a/web/src/assets/styles/patternfly-overrides.scss b/web/src/assets/styles/patternfly-overrides.scss index 604afa49c2..e88a4e6e9d 100644 --- a/web/src/assets/styles/patternfly-overrides.scss +++ b/web/src/assets/styles/patternfly-overrides.scss @@ -44,6 +44,11 @@ --pf-c-button--m-primary--hover--BackgroundColor: var(--color-button-primary-hover); } +// Make :hover style visible when the button is in a :focus state too +.pf-c-button.pf-m-primary:focus:hover { + --pf-c-button--m-primary--BackgroundColor: var(--color-button-primary-hover); +} + .pf-c-button.pf-m-link { // Colors for buttons modifiers --pf-c-button--m-link--Color: var(--color-link); @@ -61,6 +66,12 @@ --pf-c-button--m-secondary--hover--Color: var(--color-link-hover); } +// Make :hover style visible when the button is in a :focus state too +.pf-c-button.pf-m-secondary:focus:hover { + --pf-c-button--after--BorderColor: var(--color-link-hover); + --pf-c-button--m-secondary--Color: var(--color-link-hover); +} + // SVG icons does not obey font-size .pf-c-empty-state__icon { inline-size: 10rem; diff --git a/web/src/components/core/Description.jsx b/web/src/components/core/Description.jsx new file mode 100644 index 0000000000..849334be31 --- /dev/null +++ b/web/src/components/core/Description.jsx @@ -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, Button } 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 ( + + + + ); + } + + // none or empty description, just return the children + return children; +} diff --git a/web/src/components/core/Description.test.jsx b/web/src/components/core/Description.test.jsx new file mode 100644 index 0000000000..500f50cd6e --- /dev/null +++ b/web/src/components/core/Description.test.jsx @@ -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({item}); + + // 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({item}); + }); + + it("displays the object without description when it is null", async () => { + expectNoPopup({item}); + }); + + it("displays the object without description when it is empty", async () => { + expectNoPopup({item}); + }); +}); diff --git a/web/src/components/core/Tip.jsx b/web/src/components/core/Tip.jsx new file mode 100644 index 0000000000..e71f8bd74b --- /dev/null +++ b/web/src/components/core/Tip.jsx @@ -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 ( + + + + ); + } + + return ; +} diff --git a/web/src/components/core/Tip.test.jsx b/web/src/components/core/Tip.test.jsx new file mode 100644 index 0000000000..967b41edb9 --- /dev/null +++ b/web/src/components/core/Tip.test.jsx @@ -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({label}); + + // 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({label}); + + // 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(); + }); + }); +}); diff --git a/web/src/components/core/index.js b/web/src/components/core/index.js index f5f7efcf67..92f44ce589 100644 --- a/web/src/components/core/index.js +++ b/web/src/components/core/index.js @@ -21,6 +21,7 @@ export { default as About } from "./About"; export { default as PageOptions } from "./PageOptions"; +export { default as Description } from "./Description"; export { default as Disclosure } from "./Disclosure"; export { default as Sidebar } from "./Sidebar"; export { default as Section } from "./Section"; @@ -48,5 +49,6 @@ 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"; export { default as NotificationMark } from "./NotificationMark"; diff --git a/web/src/components/storage/ProposalVolumes.jsx b/web/src/components/storage/ProposalVolumes.jsx index c912c9fa2e..202f62a5b4 100644 --- a/web/src/components/storage/ProposalVolumes.jsx +++ b/web/src/components/storage/ProposalVolumes.jsx @@ -25,6 +25,7 @@ import React, { useState } from "react"; import { Dropdown, DropdownToggle, DropdownItem, Form, FormGroup, FormSelect, FormSelectOption, + List, ListItem, Skeleton, TextInput, Toolbar, ToolbarContent, ToolbarItem @@ -32,7 +33,7 @@ import { 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"; @@ -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: + + {volume.snapshotsAffectSizes && + The configuration of snapshots} + {volume.sizeRelevantVolumes && volume.sizeRelevantVolumes.length > 0 && + Presence of other volumes ({volume.sizeRelevantVolumes.join(", ")})} + + + ); +}; + /** * Form used for adding a new file system from a list of templates * @component @@ -181,6 +209,7 @@ const GeneralActions = ({ templates, onAdd, onReset }) => { return ( <> { const limits = `${sizeText(volume.minSize)} - ${sizeText(volume.maxSize)}`; const isAuto = volume.adaptiveSizes && !volume.fixedSizeLimits; - const autoModeIcon = ; - return (
{limits} - auto-calculated} /> + auto} />
); };