Problem/Motivation

The module uninstall form lists all modules in alphabetical order, this means that non-stable modules are mixed in with stable modules.

In #3266397: Highlight non-stable modules on the admin/modules/uninstall form we are introducing the non-stable statuses for modules on the uninstall form.

During a UX calls it was identified that grouping such modules together at the top of the list would be preferable as it would draw more attention to them. This is particularly important for obsolete modules where immediate action may be required.

In #3266397-7: Highlight non-stable modules on the admin/modules/uninstall form it was agreed to push that change to a follow-up issue, this is that issue.

Steps to reproduce

Proposed resolution

For non-stable modules in the module uninstall list, promote those modules to the top of the list so that they appear first, then all other stable modules appear below in the list.

Remaining tasks

User interface changes

Before

Module uninstall form, before patch

After

Module uninstall form, patched to put all non-stable extensions at the top

API changes

None.

Data model changes

None.

Release notes snippet

Not needed.

Comments

AaronMcHale created an issue. See original summary.

dww’s picture

Thanks for opening this to put my idea into a properly scoped issue. 😉

Unrelated in code, but #3205746: Group the available updates report by status, make each collapsible is the same UX principle for the Available updates page. Put the things you actually need to deal with at the top of the page.

aaronmchale’s picture

Title: [PP-1] Promote non-stable modules to the top of the list at admin/modules/uninstall form » Promote non-stable modules to the top of the list at admin/modules/uninstall form
Status: Postponed » Active
andregp’s picture

I'm working on a patch to place the non-stable modules to the top of the list, preserving their alphabetical order.
I am in the tests part now, I plan to finish it still today.

andregp’s picture

Status: Active » Needs review
StatusFileSize
new2.3 KB
new3.48 KB
new94.74 KB
new97.02 KB

This patch looks for all unstable modules and place them above on the list on the uninstall form. It also perform tests to check if the unstable modules appear first.

The last submitted patch, 5: 3270378-5-tests-only.patch, failed testing. View results

aaronmchale’s picture

Status: Needs review » Needs work

Thanks @andregp, I haven't tested it myself but it's small and looks good.

Just one thing I picked up on from reading it:

unset($uninstallable[array_search($module, $uninstallable)]);

This can be simplified, if we change the foreach to:

foreach ($uninstallable as $module_id=>$module) {

Then we have the key so don't need the array_search() meaning it then becomes:

unset($uninstallable[$module_id]);

Thanks,
-Aaron

andregp’s picture

Status: Needs work » Needs review
StatusFileSize
new3.46 KB
new955 bytes

Thanks for the review @AaronMcHale!
Here's the new patch. No 'tests-only.patch' this time as it's the same as in #5.

rinku jacob 13’s picture

StatusFileSize
new111.84 KB
new109.14 KB

Verified and tested patch#8 on the drupal 9.4.x-dev version. Patch applied successfully and looks good to me.Need +1 RTBC.

aaronmchale’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for confirming that the patch works for you @Rinku Jacob 13.

I'm also happy with everything here so moving to RTBC.

dww’s picture

Issue summary: View changes

Great, thanks! Added the screenshots to the summary, and hid all the files except the test-only and green patches. I don't think this needs a change record nor a release snippet.

I also reviewed the patch:

  1. Functional change looks clear and simple.
  2. Test coverage changes are small and make sense.

My only concern is that part of why this was punted to a separate issue was because of needing to sort out inconsistencies between the main module "Extend" form vs. this uninstall form. Do we need a follow-up for that? Leaving RTBC, and not tagging, but asking just in case. 😉

Crediting @AaronMcHale for writing up & reviews, and myself for the proposal, summary edit, and review.

Thanks again! -Derek

aaronmchale’s picture

Thanks Derek!

My only concern is that part of why this was punted to a separate issue was because of needing to sort out inconsistencies between the main module "Extend" form vs. this uninstall form. Do we need a follow-up for that? Leaving RTBC, and not tagging, but asking just in case. 😉

I have a vague memory of discussing that, but don't remember the details. Are we talking purely from a UI perspective or also from the perspective of the underlying code?

dww’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 8: 3270378-8.patch, failed testing. View results

andregp’s picture

Status: Needs work » Reviewed & tested by the community

Unrelated test fail, restoring status.

quietone’s picture

Version: 9.4.x-dev » 10.0.x-dev

This will need to be on 10.0.x now. Added a test for 10.0.x and changing version.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -150,6 +150,16 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     uasort($uninstallable, [ModuleExtensionList::class, 'sortByName']);
     $validation_reasons = $this->moduleInstaller->validateUninstall(array_keys($uninstallable));
 
+    $uninstallable_unstable = [];
+    foreach ($uninstallable as $module_id => $module) {
+      $lifecycle = $module->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER];
+      if ($lifecycle !== ExtensionLifecycle::STABLE && !empty($module->info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER])) {
+        $uninstallable_unstable[] = $module;
+        unset($uninstallable[$module_id]);
+      }
+    }
+    $uninstallable = array_merge($uninstallable_unstable, $uninstallable);

This will result in the $uninstallable having a mixed set of keys.

Which in turn will break the following piece of code...

      // If a validator returns reasons not to uninstall a module,
      // list the reasons and disable the check box.
      if (isset($validation_reasons[$module_key])) {
        $form['modules'][$module->getName()]['#validation_reasons'] = $validation_reasons[$module_key];
        $form['uninstall'][$module->getName()]['#disabled'] = TRUE;
      }

So if one of the unstables modules provided a field that was still being used the checkbox would still be active.

A better thing to do here will change uasort($uninstallable, [ModuleExtensionList::class, 'sortByName']); to something like

    // Sort all modules by their lifecycle identifier and name.
    uasort($uninstallable, function (Extension $a, Extension $b) {
      // Unstable modules should appear at the top of the list.
      $lifecycle_a = $a->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::STABLE && !empty($a->info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER]) ? -1 : 1;
      $lifecycle_b = $b->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::STABLE && !empty($b->info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER]) ? -1 : 1;
      if ($lifecycle_a === $lifecycle_b) {
        return ModuleExtensionList::sortByName($a, $b);
      }
      return $lifecycle_a <=> $lifecycle_b;
    });
aaronmchale’s picture

Good spot Alex.

$uninstallable_unstable could also be index by the module ID.

So $uninstallable_unstable[] = $module; would become $uninstallable_unstable[$module_id] = $module;.

alexpott’s picture

@AaronMcHale - I think we should adjust the sort - as I wrote in #17 - there's no need to sort twice.

alexpott’s picture

I don't understand why info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER]) is involved in the sorting. Surely the only thing that matters is info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]?

dww’s picture

Re #20: 💯, I’ve said the same in other patches. Ever since lifecycle went in, there’s been all this code to only do certain things if the link is defined. I think it’s all weird. The behavior (whatever it is), should happen regardless, and the only thing the presence of the link info controls is if the link is used.

Re #19: fully agree. Simple++

Thanks, and sorry for not spotting this at #11

aaronmchale’s picture

@AaronMcHale - I think we should adjust the sort - as I wrote in #17 - there's no need to sort twice.

Oh yeah I wasn't disagreeing with you, it was just something I happened to notice at the time of reading your comment, so thought I'd mention it in any case.

andregp’s picture

Status: Needs work » Needs review
StatusFileSize
new3.55 KB
new1.54 KB

New patch addressing #17 and #20

Status: Needs review » Needs work

The last submitted patch, 23: 3270378-23.patch, failed testing. View results

andregp’s picture

Status: Needs work » Needs review

I believe the test is unrelated, re-queueing test

aaronmchale’s picture

Status: Needs review » Reviewed & tested by the community

Interdiff appears to address points from #17-21, so I think we're good here.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/system/src/Form/ModulesUninstallForm.php
@@ -146,6 +146,17 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     uasort($uninstallable, [ModuleExtensionList::class, 'sortByName']);
     $validation_reasons = $this->moduleInstaller->validateUninstall(array_keys($uninstallable));
 
+    // Sort all modules by their lifecycle identifier and name.
+    uasort($uninstallable, function ($a, $b) {
+      // Unstable modules should appear at the top of the list.
+      $lifecycle_a = $a->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::STABLE ? -1 : 1;
+      $lifecycle_b = $b->info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::STABLE ? -1 : 1;
+      if ($lifecycle_a === $lifecycle_b) {
+        return ModuleExtensionList::sortByName($a, $b);
+      }
+      return $lifecycle_a <=> $lifecycle_b;
+    });

There only needs to be one uasort - the second one should take the place of the first.

andregp’s picture

Status: Needs work » Needs review
StatusFileSize
new3.56 KB
new682 bytes

Addressing #27.

andregp’s picture

StatusFileSize
new3.5 KB
new526 bytes

It seems that I removed more that I should. Here is the (hopefully) right patch.

aaronmchale’s picture

StatusFileSize
new60.64 KB

I just tested this locally.

I think we might want to bring back the check for ExtensionLifecycle::LIFECYCLE_IDENTIFIER that was discussed in #20 and #21.

I enabled HAL and CKEditor 5, HAL is deprecated while ckeditro5 is only experimental, but the result is that CKEditor 5 shows up first (with no link) and HAL shows up second with a link, then the list is sorted as normal.

Module uninstall list

This just doesn't look right to me; From a user's perspective, looking at this list, with no other knowledge of what is going on here, because experimental modules don't have an associated link, it's unclear why CKEditor 5 is at the top of the list, above HAL.

Coincidentally, I also don't think promoting experimental modules adds any value here. The point of this is to highlight modules where more urgent action is needed, but that isn't really the case for experimental modules (at least not in the same way that it is for deprecated/obsolete modules).

So, either:

  1. we should check for the existence of the link,
  2. or (and I hate that I'm suggesting this because it adds more custom logic) explicitly filter the list so that either:
    1. experimental modules are excluded from promotion, or
    2. only deprecated and obsolete module are promoted.

Any thoughts? @dww @alexpott

alexpott’s picture

I think only "only deprecated and obsolete module are promoted." - since this is what you are likely to want to uninstall.

aaronmchale’s picture

Status: Needs review » Needs work

I think only "only deprecated and obsolete module are promoted." - since this is what you are likely to want to uninstall.

Agreed, let's put this back to NW.

dww’s picture

Agreed. The original intention of my proposal was to promote the things you're most likely to want/need to uninstall to the top. That'd be only deprecated and obsolete, not experimental.

Not sure if I should work on the patch now, or save my RTBC-ability... ;)

murilohp’s picture

Status: Needs work » Needs review
StatusFileSize
new3.36 KB
new3.55 KB

Not sure if I should work on the patch now, or save my RTBC-ability... ;)

Keep it please hahaha, I was taking a look here and I think I can help, here's a new patch that excludes the experimental and look just at the deprecated and obsolete lifecycles.

aaronmchale’s picture

Thanks @murilohp!

Confirmed from a visual inspection that the most recent patch by @murilohp is pretty much the same as the previous one by @andregp, with the key difference being, as described, sorting only by deprecated and obsolete.

I haven't tested that patch locally, but I did test the previous one, and since all the tests are passing, I'm happy to give a +1 to it.

@dww if you're also happy with it then I think we're good to go to RTBC :)

I do wonder if we need a change record or something for the release notes though?

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +undefined, +Needs change record

Confirmed applying the patch #34 is sorting my deprecated modules.
Tested with Activity tracker and Media Library Theme Reset

Think a simple change record would be nice to just announce "hey modules are now sorted differently"

Think this can go to RTBC after that.

murilohp’s picture

Status: Needs work » Needs review
Issue tags: -Needs change record

Hey @smustgrave, I've added a new CR, moving back to NR.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

  • lauriii committed 642c614b on 10.1.x
    Issue #3270378 by andregp, murilohp, Rinku Jacob 13, AaronMcHale, dww,...
lauriii’s picture

Issue tags: -undefined

I don't think issue needs a change record because there is no action required from our users. Users who are interested about changes on this level would likely follow the commits / issue queue.

Committed 642c614 and pushed to 10.1.x. Thanks!

I'm wondering if we should backport this to 10.0.x and 9.5.x. This seems like a low risk usability improvement to backport, but I could see how there could be a very low chance of tripping some automated tests.

  • lauriii committed 157ebea1 on 10.0.x
    Issue #3270378 by andregp, murilohp, Rinku Jacob 13, AaronMcHale, dww,...

  • lauriii committed 7c9a05d8 on 9.5.x
    Issue #3270378 by andregp, murilohp, Rinku Jacob 13, AaronMcHale, dww,...
lauriii’s picture

Version: 10.0.x-dev » 9.5.x-dev
Status: Reviewed & tested by the community » Fixed

Discussed with @alexpott and @catch on Slack and we agreed to backport this to 10.0.x and 9.5.x as a non-disruptive UX improvement.

dww’s picture

Fabulous, thanks everyone!

aaronmchale’s picture

Great to see this get in! Thanks everyone!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.