Skip to content

Conversation

@joseivanlopez
Copy link
Contributor

@joseivanlopez joseivanlopez commented Mar 19, 2025

Allow editing a volume group.

@joseivanlopez joseivanlopez force-pushed the edit-vg branch 5 times, most recently from 2c1b0ac to af51dc1 Compare March 21, 2025 09:15
- Helpers do not modify the given apiModel object.
- The logical volumes are kept.
Comment on lines 80 to +89
useEffect(() => {
if (model && !model.volumeGroups.length) setName("system");
}, [model]);
if (volumeGroup) {
setName(volumeGroup.vgName);
const targetNames = volumeGroup.getTargetDevices().map((d) => d.name);
const targetDevices = allDevices.filter((d) => targetNames.includes(d.name));
setSelectedDevices(targetDevices);
} else if (model && !model.volumeGroups.length) {
setName("system");
}
}, [model, volumeGroup, allDevices]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having troubles with this useEffect while trying to write a basic unit test. No matter the solution for making the test work, I would like to find another way to set these default values in forms.

As the project evolves, I find myself questioning the best approach more often. In this case, I’d prefer to wait until all the necessary information is available before rendering. I thought this might be achievable with Suspense queries, or perhaps with a feature from the Router (I can’t recall the name right now—I'll look it up later).

Copy link
Contributor

@dgdavid dgdavid Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect the useEffect is triggered more than needed because of model or allDevices, which are objects. I saw an useMemo somewhere, but still having problems when using the RTL user.clear(): it does work but immediately the name is updated to "system" again because of this useEffect.

In anycase, since it works manually I'll put a todo in the test in order to keep going.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally found the problem in testing: the lack of mocking the useVolumeGroup hook despite of useModel being mocked. Since it is exported and mocked as default, the useVolumeGroup was using (or trying to use, actually don't know) the real implementation.

In any case, still thinking we should revisit, when possible, the approach for initializing the forms when it comes to edit, trying to avoid these useEffects when possible since they make things a bit confusing and put the form at risk to be re-rendered more than needed. Specially when dependencies are objects.

model: model.Model,
volumeGroup?: model.VolumeGroup,
): string | undefined {
if (!vgName.length) return sprintf(_("Name is empty"), vgName);
Copy link
Contributor

@dgdavid dgdavid Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locally I've removed the sprintf here, which is actually not needed. The change will be sent along the unit test.

That said, let me add something regarding these errors that I forgot to mention in the previous PR.

Although is true that we have agreed on going ahead and unify later how to render the form errors, these message errors can be improved right now bearing in mind that all rendered fields are considered mandatory.

So, I would go for something like

Form has errors and cannot be sent

  * Provide a name for the volume group.
  * Select at least one disk for placing the volume group.
Form has errors and cannot be sent


* There is a volume group using "system" name already. Please, enter a different name

Or something like that.

"Name is empty" and "No disk is selected" are somehow useless and obvious from a helpful error message point of view.

Also, when we can provide even useful warning errors to users in the future, we could go for something like

Form has errors and cannot be sent


* There is a volume group using "system" name already.
  Please choose a different one or going to *edit system* instead.

Where edit system would be a link for switching to the edition form

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, feel free to adapt it. I simply got inspired by the errors in the encryption form.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have changed them.

I simply got inspired by the errors in the encryption form.

As I said in the commit message, I'm aware of

it remains inconsistent with the rest of the interface until the tone, style, and error handling can be unified.

joseivanlopez and others added 3 commits March 21, 2025 15:39
The test is limited and could be improved, especially in terms of
mocking. It also highlights potential issues with form initialization
and the use of useEffect. However, it ensures the form works as expected
at a basic level.
Introduced by mistake by undoing changes.
Comment on lines +127 to +129
__esModule: true,
useVolumeGroup: (id: string) => (id ? mockRootVolumeGroup : null),
default: () => mockUseModel,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A side effect of exporting these hooks as default 🤷‍♂️

jest.mock("~/hooks/storage/model", () => ({
...jest.requireActual("~/hooks/storage/model"),
__esModule: true,
useVolumeGroup: (id: string) => (id ? mockRootVolumeGroup : null),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thought 💭 Mocking is becoming each time more ugly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel we should mock the queries instead of the hooks, but I don't know if it is possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And going further, I would like to simply mock the result of a http call (we have already talked about it).

import { QueryHookOptions } from "~/types/queries";

function useApiModel(options?: QueryHookOptions): apiModel.Config | null {
export default function useApiModel(options?: QueryHookOptions): apiModel.Config | null {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No matter where default is placed. I still feeling is weird exporting hooks in that way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote export default function because somehow I am following "this rule": if there is only one thing exported and it is exported by default (i.e., that cases in which the file name reveals what it is exporting) then I use export default function directly. Otherwise I export things at the end.

Copy link
Contributor

@dgdavid dgdavid Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, but it is not the rule we were following. As said previously, it feel weird having hooks exported by default. It's not wrong, but weird. It even make mocking worst.

Refactor error messages to be clearer, more helpful, and concise. While
this improves the messaging, it remains inconsistent with the rest of
the interface until the tone, style, and error handling can be unified.
@dgdavid
Copy link
Contributor

dgdavid commented Mar 22, 2025

I've added a simple unit test file that has A LOT OF room for improvements. I really sorry for that. But as said in the commit message

The test is limited and could be improved, especially in terms of
mocking. It also highlights potential issues with form initialization
and the use of useEffect. However, it ensures the form works as expected
at a basic level.

Hopefully, we will have time for improve at some point in the future. Ideally after having time to rework forms and unify how they behaves on all aspects: initialization, validations, tone, etc.

...jest.requireActual("~/hooks/storage/edit-volume-group"),
__esModule: true,
default: () => mockEditVolumeGroup,
}));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other tests we do this for mocking a default:

jest.mock("./ConfigEditorMenu", () => () => <div>config editor menu</div>);

Would the same work for these hooks?

jest.mock("~/hooks/storage/edit-volume-group", () => mockEditVolumeGroup);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried and didn't work, but maybe I was doing something wrong at some point. I'll try again later, but still being a pain in the ass mocking some modules in a way and others in another.

I can live we such a pain, though. But don't know why still adding more complexity to simple things.

Copy link
Contributor

@dgdavid dgdavid Mar 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it works. Like you in your above example, I was missing a function in the middle in my previous attempts 🤷

diff --git a/web/src/components/storage/LvmPage.test.tsx b/web/src/components/storage/LvmPage.test.tsx
index d3014b167..145166b95 100644
--- a/web/src/components/storage/LvmPage.test.tsx
+++ b/web/src/components/storage/LvmPage.test.tsx
@@ -139,11 +139,7 @@ jest.mock("~/hooks/storage/add-volume-group", () => ({
   default: () => mockAddVolumeGroup,
 }));
 
-jest.mock("~/hooks/storage/edit-volume-group", () => ({
-  ...jest.requireActual("~/hooks/storage/edit-volume-group"),
-  __esModule: true,
-  default: () => mockEditVolumeGroup,
-}));
+jest.mock("~/hooks/storage/edit-volume-group", () => () => mockEditVolumeGroup);
 
 describe("LvmPage", () => {
   describe("when creating a new volume group", () => {

However, I will keep mocking as it is now because, leaving aside the fact that the whole topic about these default exports is not clear, we have in a rush with other topics.

Feel free to change it before merging if you consider it really important. I still thinking that it is

a pain in the ass mocking some modules in a way and others in another

just because changing the way hooks are exported.

<Form id="lvmForm" onSubmit={onSubmit}>
{errors.length > 0 && (
<Alert variant="warning" isInline title={_("Something went wrong")}>
<Alert variant="warning" isInline title={_("Check the following before continuing")}>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NP: Should it end with colons?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. It's acting as a title.

Avoid using full stops in titles and headings unless they are complete sentences. https://freelingual.com/full-stop-punctuation

Also having a look to the SUSE documentation style guide I've noticed they do not use it in titles neither

dgdavid added 2 commits March 24, 2025 07:58
Use `model.Drive` instead of directly importing the `Drive` type. This
aims to make clear which Drive type is being used when reading, writing,
or maintaining the code.

However, StorageDevice is not under the `model` namespace (yet?) because
it doesn’t currently conflict with a type from `apiModel`. It would be
nice to find a better organization for these types, but there is neither
time nor a clear vision for how to structure this complex area at the
moment.
@joseivanlopez joseivanlopez marked this pull request as ready for review March 24, 2025 11:56
Copy link
Contributor Author

@joseivanlopez joseivanlopez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, everything looks good ;)

Copy link
Contributor

@dgdavid dgdavid left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving and merging based on Iván's comment

@dgdavid dgdavid merged commit 10526e8 into storage-lvm Mar 24, 2025
3 checks passed
@dgdavid dgdavid deleted the edit-vg branch March 24, 2025 12:30
@imobachgs imobachgs mentioned this pull request Mar 27, 2025
imobachgs added a commit that referenced this pull request Mar 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants