Problem/Motivation

Shortcut module employs a one-off storage for the association of of users to shortcut sets instead of using a proper field.
This has several disadvantages:

Maintainability
Instead of relying on field API to do the heavy lifting lot's of custom code is required:
  • A database schema
  • Storage methods
  • Form arrays
Flexibility/Extendability
Field API provides several entry points for altering various parts of the form-storage-display pipeline, which would greatly expand the ability to alter the behavior of the user shortcut set assignment.
Portability
Shortcut module currently contains a hardcoded database table and the shortcut set storage queries that table. People wanting to store their shortcut user data in a non-SQL backend need to re-implement all the storage logic and still have a useless SQL table lying around.

Proposed resolution

Make the {shortcut_set_users} table a shortcut_set (base) field on the user entity type by implementing hook_entity_base_field_info() in Shortcut module.

Remaining tasks

User interface changes

None.

API changes

None.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task
Issue priority Normal
Unfrozen changes Not unfrozen
Prioritized changes Not prioritized
Disruption No disruption

Comments

dawehner’s picture

Interesting!

tstoeckler’s picture

Issue summary: View changes
Status: Active » Needs review
kgoel’s picture

Status: Needs review » Active
tstoeckler’s picture

Assigned: Unassigned » tstoeckler

Oops, thanks!

Also assigning to me for now.

tstoeckler’s picture

Status: Active » Needs review
StatusFileSize
new14.67 KB

Here we go. This was ridiculously easy (but for the test failures...) because we're at a point where Entity API Just Works© (yay!).

This should be green already. Note that I had quite some fails at first and even found an separate core bug (will open issue in a minute) so that does not mean that test coverage is insufficient.

I snuck in a few changes that could be left out, but make sense to do as part of this:
- Add AccountInterface typehinting to some ShortcutStorageInterface methods. This is not an API change it's only documenting what happens anyway. What we really want is UserInterface but that would be an API change, which is why I'm doing that magic loading dance.
- Fix some test assertions to be more debuggable.

Next step is ripping out the custom form stuff for assigning a shortcut set to a user. Current plan is to introduce a new form mode for that, we'll see how that goes.

Status: Needs review » Needs work

The last submitted patch, 5: 2446195-5-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

Hehe, good thing we have the testbot. I only ran the shortcut tests locally, and then obviously my two favorite tests in the world get jealous and want to spend some alone time with me. Fun!

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new2.65 KB
new14 KB

Let's see if this is green.

ModuleInstallUninstallTest revealed that the schema updates were not being applied on install and uninstall of the module. I thought that was a weirdness of the test, which is why I added it into some of the tests above, but it was in fact not working. In my local testing I just used Standard profile which does that manually in standard_install() (which is super weird, by the way!).

This fixes that, so could be green.

It reveals another problem, however: With this patch shortcut module cannot be uninstalled as soon as any user has ever submitted the switch shortcut sets form. That's kind of a good thing, as that means the entity API correctly detects that it's going to delete unrecoverable data and it refuses to do that (which is nice), but it's not really an acceptable situation. Not sure what the fix for that is exactly, but we somehow need to handle the default shortcut set differently, I think.

dawehner’s picture

The patch looks really great!!

  1. +++ b/core/modules/shortcut/shortcut.module
    @@ -44,6 +45,20 @@ function shortcut_help($route_name, RouteMatchInterface $route_match) {
     /**
    + * Implements hook_entity_base_field_info().
    + */
    +function shortcut_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
    +  $fields = [];
    +  if ($entity_type->id() === 'user') {
    +    $fields['shortcut_set'] = BaseFieldDefinition::create('entity_reference')
    +      ->setLabel(t('Shortcut set'))
    +      ->setDescription(t('The shortcut set that will be displayed for this user.'))
    +      ->setSetting('target_type', 'shortcut_set');
    +  }
    +  return $fields;
    +}
    

    One thing I just don't get, when does someone uses a base field, and when does someone uses a configurable field? Do you always uses base fields if you are in code?

  2. +++ b/core/modules/shortcut/src/ShortcutSetStorage.php
    @@ -67,49 +79,61 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
    -    db_delete('shortcut_set_users')
    -      ->condition('set_name', $entity->id())
    +    $uids = $this->userStorage->getQuery()
    +      ->condition('shortcut_set', $entity->id())
           ->execute();
    +
    +    foreach ($this->userStorage->loadMultiple($uids) as $user) {
    +      $user->set('shortcut_set', NULL);
    +      $user->save();
    

    This is much nicer!

tstoeckler’s picture

Thanks!

One thing I just don't get, when does someone uses a base field, and when does someone uses a configurable field? Do you always uses base fields if you are in code?

Yes, basically that is it. As soon as there is business logic (i.e. code) that requires that field it makes sense to use a base field.

Also: isn't it just way more fancy that writing/exporting a bunch of YAML files? ;-)

This is much nicer!

I agree. :-) I think with this ShortcutSetStorageInterface is basically obsolete, I did not add @todos yet to remove the relevant methods (in 9.x). At the very least assignUser, unassignUser and getAssignedToUser are rather silly with the patch.

jibran’s picture

We just need a change record here and we are good to go.
Code changes make a lot of sense and if we can fix #2083123: Shortcut cleanup: Remove duplicated code and deprecate legacy functions it'd be perfect.
Also, adding it as a base field and not as configureable is a very good idea.

waringnick’s picture

Change record available here: https://www.drupal.org/node/2447781

Let me know if anything needs to be added

tstoeckler’s picture

StatusFileSize
new1.43 KB
new14.26 KB

Thanks for the change record @waringnick! I added some example code to it.

I think the patch is in fact ready to go. Attached patch just adds some @todos to the methods mentioned in #10.

I spent a lot of time thinking about and hacking around the "problem" described in #8 until I realized that I am an incredible buffoon and there is absolutely no problem at all. Shortcut module cannot be uninstalled if there are shortcut entities (with and without the patch), this has nothing at all to do with the user->shortcut_set assignments.

I also had a stab at making the switch shortcut set form a proper (user) entity form that uses widgets for the shortcut set reference fields. Even though that would be the correct implementation in theory there are several nitty gritty details that made me revert all those nice local commits in the end:

  1. The nice setDisplayOptions() only apply to the default form mode. Any additional form modes have to specified as an entity form display entity in YAML which inevitably makes them appear in the UI, which might not be what we want.
  2. It is possible to use the "check boxes/radio buttons" widget for entity references (even though strangely only with Entity reference module installed, even though all the classes are in Drupal\Core) but that does not have the auto_create feature that the current form has, where you can create a new shortcut set on the fly. So that would have to be added somehow
  3. There is a subtle difference between no shortcut set assignment at all for a user and an assignment to the Default shortcut set, i.e. there is a slight difference between selecting Default and N/A. The current UI does not ofer the latter choice (for UX reasons) which is not someting that the "check boxes/radio buttons" widget allows
  4. It is not easily possible to create a shortcut set or even entity specific widget which subclasses OptionsWidgetBase because that hardcodes the plugin IDs of its subclasses in getOptions(). While that is a legitimate bug in its own right, it would be out of scope to fix here.

In summary, in order to make the form an entity form we would have to code around too many shortcomings of our APIs to even remotely justify the benefit.

Played around with it again and everything seems to work.

tstoeckler’s picture

StatusFileSize
new1.67 KB
new1.54 KB

Hmm... apparently I got our standards for documenting @deprecated code wrong. This one should be better.

Status: Needs review » Needs work

The last submitted patch, 14: 2446195-14-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new14.37 KB

Sorry for the noise.

Interdiff was correct, here's the patch I should have posted in #14.

jibran’s picture

Status: Needs review » Reviewed & tested by the community

Given that it's not disruptive and a huge architectural win I think It would be awesome if we can commit it and make shortcut module more maintainable. Thanks @tstoeckler for the patch and issue.

berdir’s picture

Well, it *is* disruptive to existing installations ;)

berdir’s picture

Also, do we have any tests that uninstall shortcut.module when there have been user assignments? You can't just run applyUpdates() in hook_uninstall, I can't imagine that being enough. We would need to use the API to remove the field first, and make sure we delete the data, which AFAIK isn't actually possible yet? (the field purge process).

tstoeckler’s picture

Assigned: tstoeckler » Unassigned
Status: Reviewed & tested by the community » Needs review

Re #18: That hasn't been a criterion before (e.g. user_roles and other things), which is why I pursued this in the first place. You are of course correct, though.

Re #19: Will test again. I think I verified that this worked, but will re-verify. Setting to "needs review" for that.

tstoeckler’s picture

Status: Needs review » Needs work

This does in fact not work correctly, thanks @Berdir. Will post updates later.

tstoeckler’s picture

So at the time hook_uninstall() gets called the respective module (Shortcut in this case) is still registered as installed and still takes part in hooks, etc. That's why the call to EntityDefinitionUpdateManager::applyUpdate() doesn't work: Because at the time of calling the field storage definition for the shortcut_set field still exists.

What does work, though, is removing the field storage definition "manually" á la:

  $entity_manager = \Drupal::service('entity.manager');
  $storage_definition = $entity_manager->getFieldStorageDefinitions('user')['shortcut_set'];
  $entity_manager->onFieldStorageDefinitionDelete($storage_definition);

There is no field purging involved. The uninstallation is only possible if no data is stored in the field in the first place and the onFieldStorageDefinitionDelete() simply drops the column alltogether, which is exactly what we want.

The problem that crops up then, is that when Shortcut module is uninstalled the default shortcut set along with any other shortcut sets, which aren't used anymore is deleted. So ShortcutSetStorage::deleteAssignedUsers() is called which queries the shortcut_set field on users, which doesn't exist anymore at that point.

I haven't found a solution to that yet, so no new patch.

Thanks again @Berdir, for pointing this out!!!

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new2.05 KB
new15.66 KB

Not necessarily advocating this as a good idea, but let's see if this is green at least.

(Note that I need to use getLastInstalledFieldStorageDefinition() because the storage definition does not exist at that point anymore. Seems awkward at first, but I think (?) it's kind of correct.)

As @Berdir pointed out, this points to a more general problem of uninstalling modules which provide field storage definitions for other entity types. Trying to uninstall Content Translation will be equally borked, if not more.

tstoeckler’s picture

StatusFileSize
new992 bytes
new15.43 KB

Actually, writing #23 I realized that makes EntityDefinitionUpdateManager::applyUpdates() which does make me slightly less uneasy about this approach.

The last submitted patch, 23: 2446195-23-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

StatusFileSize
new4.31 KB
new17.84 KB

Maybe it was right there all along. If this is green, I want a cookie.

The last submitted patch, 24: 2446195-24-shortcut-set-user-field.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 26: 2446195-25-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

StatusFileSize
new4.02 KB
new6.09 KB

I meant to revert that post_uninstall stuff before, which this does. Also took a stab at fixing KernelTestBase. I have a strange batch error locally, but am hoping that's not reproducible.

tstoeckler’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 29: 2446195-29-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new19.46 KB

Sorry

(interdiff is correct...)

Status: Needs review » Needs work

The last submitted patch, 32: 2446195-32-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new708 bytes
new19.01 KB

Ok, if this still doesn't apply, I seriously have to consider just quitting IT and becoming a construction worker or something...

tstoeckler’s picture

Assigned: Unassigned » plach

Awesome, so that seems to have worked.

I think these latest changes revealed on a major bug I would title "Modules that provide dynamic field storages cannot be uninstalled reliably" that should be split and fixed separately, but @Berdir rightly pointed out that @plach should have a look at the patch so I wanted to wait for that before doing any issue queue foo.

Assigning for that purpose and will try to reach out in IRC.

tstoeckler’s picture

StatusFileSize
new1.33 KB
new18.96 KB

Status: Needs review » Needs work

The last submitted patch, 36: 2446195-36-shortcut-set-user-field.patch, failed testing.

tstoeckler’s picture

Status: Needs work » Needs review
StatusFileSize
new18.96 KB

Hmm... random?

jibran’s picture

Nice it's green. Now can we please move all the non shortcut module stuff to a separate issue and postpone till that's committed? Because this is a task and we identified a bug. Is it worth adding tests for #19?

plach’s picture

Assigned: plach » Unassigned
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -232,16 +232,8 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
-        // Notify interested components that this module's entity types are new.
-        // For example, a SQL-based storage handler can use this as an
-        // opportunity to create the necessary database tables.
-        // @todo Clean this up in https://www.drupal.org/node/2350111.
-        $entity_manager = \Drupal::entityManager();
-        foreach ($entity_manager->getDefinitions() as $entity_type) {
-          if ($entity_type->getProvider() == $module) {
-            $entity_manager->onEntityTypeCreate($entity_type);
-          }
-        }
+        // Install the schema for new entity types or field definitions.
+        \Drupal::service('entity.definition_update_manager')->applyUpdates();

@@ -386,15 +378,8 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
-      // Notify interested components that this module's entity types are being
-      // deleted. For example, a SQL-based storage handler can use this as an
-      // opportunity to drop the corresponding database tables.
-      // @todo Clean this up in https://www.drupal.org/node/2350111.
-      foreach ($entity_manager->getDefinitions() as $entity_type) {
-        if ($entity_type->getProvider() == $module) {
-          $entity_manager->onEntityTypeDelete($entity_type);
-        }
-      }
+      // Remove schema for entity types and field definitions.
+      \Drupal::service('entity.definition_update_manager')->applyUpdates();

I don't think this is correct: applying updates can do way more than simply notifying entity type creation/deletion, so it might easily mean we end-up applying unrelated changes without the user being aware of that.

I opened #2346013: Improve DX of manually applying entity/field storage definition updates some time ago to make sure we are able to limit applied updates to a single module, which would be definitely more correct here. Still I'm not sure it would be the right solution.

Not sure why this is needed, but I suspect it's telling us there's something wrong elsewhere. If it's just about removing lingering base field schema, we have #2282119: Make the Entity Field API handle field purging for that.

tstoeckler’s picture

Assigned: Unassigned » tstoeckler
Status: Needs review » Needs work

If it's just about removing lingering base field schema, we have #2282119: Make the Entity Field API handle field purging for that.

Yes, that was the reason I started all this in the first place. I will move the relevant code as proposal to the issue(s) you linked and re-focus the patch here. Should be a lot less controversial then. Thanks!

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 38: 2446195-36-shortcut-set-user-field.patch, failed testing.

jibran’s picture

bump

mgifford’s picture

Assigned: tstoeckler » Unassigned

Unassigning stale issue. Hopefully someone else will pursue this.

jibran’s picture

Version: 8.0.x-dev » 8.2.x-dev

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

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

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

smustgrave’s picture

Status: Needs work » Postponed (maintainer needs more info)
Issue tags: +stale-issue-cleanup

Thank you for creating this issue to improve Drupal.

We are working to decide if this task is still relevant to a currently supported version of Drupal. There hasn't been any discussion here for over 8 years which suggests that this has either been implemented or is no longer relevant. Your thoughts on this will allow a decision to be made.

Since we need more information to move forward with this issue, the status is now Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.

Thanks!

smustgrave’s picture

Status: Postponed (maintainer needs more info) » Postponed

With shortcut being deprecated will let the new maintainer decide this one.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

smustgrave’s picture

Project: Drupal core » Shortcut (from core)
Version: main » 2.x-dev
Component: shortcut.module » Code
Status: Postponed » Needs work