Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage: show skeletons only when needed #1171

Merged
merged 13 commits into from
May 2, 2024
Merged

Storage: show skeletons only when needed #1171

merged 13 commits into from
May 2, 2024

Conversation

lslezak
Copy link
Contributor

@lslezak lslezak commented Apr 26, 2024

Problem

  • The loading skeletons are displayed always, even when not needed
  • The installation device selection popup is empty when clicked quickly before loading all data

Solution

Loading Skeletons

  • Pass the name of the component which is changed by user to parent
  • Send back the changing component to all children
  • There is mapping which defined whether a child needs refresh when some setting is being changed

Installation Device Progress

  • Use the sent data when when the stored values are undefined
  • Show a simple skeleton

screenreaderText Properties

  • I have removed all screenreaderText properties for skeletons and keep it only for the result section
  • The reason is that it does not make sense to notify users about 5 values in progress, it usually takes about 2 seconds to refresh the data so the screen reader would not have enough time to read all messages
  • Use it only the result section which is refreshed always, moreover the data for the other values will be read together with the result data, they will always take the same time

Questions

  • Do we need more granularity with changing values? I.e. if the Btrfs snapshots are enabled/disabled then the partition list below is refresh as well. (IMHO fine to solve this later in a separate card / PR.)
  • Better progress for the installation device loading? (Note: do not overengineer here, the progress is displayed only when you click the link very quickly after opening the storage page, in most cases it won't be used at all.)

Testing

  • Tested manually

Screenshots

When enabling/disabling the Btrfs snapshots all data was refreshed, now the installation device and the space policy does not show the progress skeletons.

Original Fixed
storage_skeletons_broken storage_skeletons_fixed

Another fix was empty installation device screen when the "Installation device" link was clicked quickly, before loading the data finished.

Original Fixed
storage_loading_progress_broken storage_loading_progress2

Also display progress for loading the installation device
@coveralls
Copy link

coveralls commented Apr 26, 2024

Coverage Status

coverage: 75.022% (-0.03%) from 75.053%
when pulling 2f954fa on storage_skeletons
into 493706b on master.

@lslezak lslezak marked this pull request as ready for review April 29, 2024 12:54
@ancorgs
Copy link
Contributor

ancorgs commented Apr 29, 2024

I see the form for selecting the installation device is now protected from "quick clickers"[1].

But that's the only pop-up that got that protection. What about these?

  • Encryption - if the current settings have encryption enabled but I click on "Encryption" before data is loaded, then I get a form that makes me think encryption is not enabled. I expected a "loading" message/spinner/something while the data loads and then getting the form correctly initialized.
  • Find space - if I click before the data is loaded, I get a Javascript error "policy is undefined".

[1] Although I don't like using a skeleton within the pop-up instead of some kind of "spinner", but that's another topic for another comment.

</Form>
<If
condition={isLoading}
then={ <SectionSkeleton numRows={6} /> }
Copy link
Contributor

Choose a reason for hiding this comment

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

Honestly, this skeleton here looks really weird.

As commented during the planning meeting of the card and again today during review, the goal of the skeletons is to "reserve space" in the UI for the future content, so things don't jump up and down as that content is loaded into its place.

In case of a pop-up that is going to load at once as soon the content arrives, that multi-line skeleton looks like some kind of render error. I expected the pop-up to open kind of blank but with some "loading" message and/or a spinner (I personally expected the same three circles we have when loading the whole UI).

// it does not depend on any changed item and does not show skeleton later.
// the ProposalResultSection is refreshed always
InstallationDeviceField: [CHANGING.ENCRYPTION, CHANGING.BOOT, CHANGING.POLICY, CHANGING.VOLUMES],
PartitionsField: [CHANGING.ENCRYPTION],
Copy link
Contributor

@ancorgs ancorgs Apr 29, 2024

Choose a reason for hiding this comment

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

Shouldn't this include CHANGING.POLICY? I don't see how changing the find space policy can affect the list of volumes.

// the ProposalResultSection is refreshed always
InstallationDeviceField: [CHANGING.ENCRYPTION, CHANGING.BOOT, CHANGING.POLICY, CHANGING.VOLUMES],
PartitionsField: [CHANGING.ENCRYPTION],
SpacePolicyField: [CHANGING.ENCRYPTION, CHANGING.BOOT, CHANGING.VOLUMES, CHANGING.TARGET],
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a comment about this. Being fully strict, the fact is that the content of the sentence displayed right after "Find Space" can actually be affected by changes in the target disk(s), in the booting disk and in the list of volumes.

For example, the sentence can initially be "deleting all content of the installation disk". But then I change the target and decide to install on an LVM over sdb and sdc. Then the sentence changes to "deleting all content of the 2 selected disks". Then I decide that, in addition to that, the system will boot from sda so the sentence changes to "deleting all content of the 3 selected disks". And for extra fun I decide to allocate /home as a new partition at sdd. Then the sentence changes again to "deleting all content of the 4 selected disks".

I don't know if those changes in the sentence really deserve a skeleton. I just wanted to mention there is SOME influence and as such it should be documented next to the code.

@ancorgs
Copy link
Contributor

ancorgs commented Apr 29, 2024

In fact, BootConfigField is never affected by changes in other settings. It only makes sense to display it as a skeleton when the whole page is loading. But once the value is set it can only be changed via its very own "Change boot options" button.

Watching it become a skeleton so often feels quite wrong.

return condition ? positive : negative;
const ret = condition ? positive : negative;
// if the parameter is a function then evaluate it
return (typeof ret === "function") ? ret() : ret;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This allows lazy evaluation of the branches. The problem is that <If> evaluates both branches and then returns one of them.

So for example this

<If condition={data === undefined} then={<Loading/>} else={<Foo foo={data.foo}>} />

crashes when data is undefined because it evaluates the else branch too early.

With this improvement you can pass a function which can be evaluated only when the result is needed. (Passing functions is quite common in Javascript to allow the lazy evaluation.)

Then the fix is trivial, just add () => before the value:

<If condition={data === undefined} then={<Loading/>} else={() => <Foo foo={data.foo}>} />

@dgdavid are you OK with this change?

I'll add some example into the <If> documentation if you agree with this improvement.

Copy link
Contributor

@dgdavid dgdavid Apr 30, 2024

Choose a reason for hiding this comment

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

Thanks @lslezak

Let's say that I have no strong opinion about it. At first sight, it looks reasonable to prevent not needed evaluations. But on the other hand it's weird to me to accept a function as content of the branches. IMHO it should be limited to ReactNode (Although technically a component is a function too).

That said, I fully understand the proposal looking at the code written at https://github.com/openSUSE/agama/pull/1171/files#diff-994f1f4ff00fecec1797a2aaa799dbbeddc4e8fb2c1ae666fedbb95b7c499770R155-R178

  <If
        condition={isLoading}
        then={
          <Loading text={_("Loading data...")} />
        }
        else={
          () =>
            <Form id="space-policy-form" onSubmit={onSubmit}>
              <SpacePolicyPicker currentPolicy={policy} onChange={setPolicy} />
              <If
                condition={devices.length > 0}
                then={
                  <SpaceActionsTable
                    devices={devices}
                    expandedDevices={expandedDevices}
                    deviceAction={deviceAction}
                    isActionDisabled={policy.id !== "custom"}
                    onActionChange={changeActions}
                  />
                }
              />
            </Form>
        }
      />

But I would go for a different implementation instead. For me, what is in the else should be a component that React should be able to understand. Let's say <SpacePolicyForm />. Then, the component should be aware about what it needs for return either, an output or nothing.

const SpacePolicyForm = ({ prop1, prop2 }) => {
  if (!prop1 || !prop2) return;

  ...

  return (
    <div><b>{prop1}:</b>{prop2}</div>
  );
}

I.e., the key for me is to write more robust components.

Of course, I might be wrong, so maybe another view here could help to decide what way to go.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was assuming the else branch is not evaluated if the condition is true. So, probably, I have written unsafe code.

Copy link
Contributor

Choose a reason for hiding this comment

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

BTW, as far as I can see I think that this set of changes reveals another change we could address: to add the isLoading prop to core/Popup and make it show the spinner or its children based on the prop value. That way, we could reduce the <If condition={isLoading} then={... repetition and have more clean WhateverDialog.jsx components.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After reading this I decided to revert the <If> change. It seems it is not safe and it can have some nasty side effects in some cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

@joseivanlopez Actually I do not think you have written "unsafe" code. The Form itself is not executed (@dgdavid and me has done some tests with another example). The problem is the data.foo when data is undefined (that's the code that gets executed).

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.

LGTM, thanks.

Comment on lines 102 to 103
// a PF skeleton is displayed
expect(container.querySelector("div.pf-v5-c-skeleton")).toBeVisible();
Copy link
Contributor

Choose a reason for hiding this comment

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

NP: We use to mock the PF/Skeleton and look for the mocked content instead, which is somehow more aligned with RTL principles (to look for what the user see, not how it's styled).

https://github.com/openSUSE/agama/blob/7e43d5883ef894000194c841c2e547ca3fa81455/web/src/components/storage/ProposalPage.test.jsx#L40-L48

But feel free to keep the test as it is.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's also more robust in the sense that no matter if in the next PF update they change the HTML node for wrapping the content or the CSS classes used for styling it. The latest will happens as soon as we migrate to PFv6.

@lslezak lslezak merged commit a554ab0 into master May 2, 2024
2 checks passed
@lslezak lslezak deleted the storage_skeletons branch May 2, 2024 13:50
@imobachgs imobachgs mentioned this pull request May 17, 2024
imobachgs added a commit that referenced this pull request May 17, 2024
Prepare for releasing Agama 8. It includes the following pull requests:

* #884
* #886
* #914
* #918
* #956
* #957
* #958
* #959
* #960
* #961
* #962
* #963
* #964
* #965
* #966
* #969
* #970
* #976
* #977
* #978
* #979
* #980
* #981
* #983
* #984
* #985
* #986
* #988
* #991
* #992
* #995
* #996
* #997
* #999
* #1003
* #1004
* #1006
* #1007
* #1008
* #1009
* #1010
* #1011
* #1012
* #1014
* #1015
* #1016
* #1017
* #1020
* #1022
* #1023
* #1024
* #1025
* #1027
* #1028
* #1029
* #1030
* #1031
* #1032
* #1033
* #1034
* #1035
* #1036
* #1038
* #1039
* #1041
* #1042
* #1043
* #1045
* #1046
* #1047
* #1048
* #1052
* #1054
* #1056
* #1057
* #1060
* #1061
* #1062
* #1063
* #1064
* #1066
* #1067
* #1068
* #1069
* #1071
* #1072
* #1073
* #1074
* #1075
* #1079
* #1080
* #1081
* #1082
* #1085
* #1086
* #1087
* #1088
* #1089
* #1090
* #1091
* #1092
* #1093
* #1094
* #1095
* #1096
* #1097
* #1098
* #1099
* #1100
* #1102
* #1103
* #1104
* #1105
* #1106
* #1109
* #1110
* #1111
* #1112
* #1114
* #1116
* #1117
* #1118
* #1119
* #1120
* #1121
* #1122
* #1123
* #1125
* #1126
* #1127
* #1128
* #1129
* #1130
* #1131
* #1132
* #1133
* #1134
* #1135
* #1136
* #1138
* #1139
* #1140
* #1141
* #1142
* #1143
* #1144
* #1145
* #1146
* #1147
* #1148
* #1149
* #1151
* #1152
* #1153
* #1154
* #1155
* #1156
* #1157
* #1158
* #1160
* #1161
* #1162
* #1163
* #1164
* #1165
* #1166
* #1167
* #1168
* #1169
* #1170
* #1171
* #1172
* #1173
* #1174
* #1175
* #1177
* #1178
* #1180
* #1181
* #1182
* #1183
* #1184
* #1185
* #1187
* #1188
* #1189
* #1190
* #1191
* #1192
* #1193
* #1194
* #1195
* #1196
* #1198
* #1199
* #1200
* #1201
* #1203
* #1204
* #1205
* #1206
* #1207
* #1208
* #1209
* #1210
* #1211
* #1212
* #1213
* #1214
* #1215
* #1216
* #1217
* #1219
* #1220
* #1221
* #1222
* #1223
* #1224
* #1225
* #1226
* #1227
* #1229
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.

6 participants