Problem/Motivation
Right after installing Media and Media library modules, adding a Media field to some entity type fails as there's no media type to select. See also: #3033652: Be more helpful if there's no bundle to choose from.
With the Media library module installed, I'm however getting the following InvalidArgumentException
Media library is still experimental, however this might be the very first impression a sitebuilder gets of Media/Media library... :/
Enclosed is the full backtrace.
Steps to reproduce:
- Install Drupal with the minimal profile
- Install at least the node, media and media library modules.
- Create a node type
- Add a an entity reference field for the media entity type
- The following error is shown:
The allowed types parameter is required and must be an array of strings. in Drupal\media_library\MediaLibraryState->validateParameters() (line 119 of core/modules/media_library/src/MediaLibraryState.php).
Drupal\media_library\MediaLibraryState->__construct(Array) (Line: 66)
This is caused by the widget shown for the default value. This uses a MediaLibraryWidget. The MediaLibraryWidget need a MediaLibraryState value object. While creating this value object, the allowed types parameter is not set correctly, since there are no media types in the system yet.
Proposed resolution
Instead of throwing an error, detect this state early on in the widget and show a nice message to the user.
Remaining tasks
Write patch
Code review
UX review
Commit
User interface changes
Empty message for widget in content form:
Empty message for widget in field configuration form:
API changes
None.
Data model changes
None.
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#41 | 3033653-41.patch | 23.17 KB | seanB |
#41 | interdiff-39-41.txt | 3.06 KB | seanB |
#39 | 3033653-39.patch | 23.08 KB | seanB |
#39 | interdiff-37-39.txt | 10.02 KB | seanB |
#37 | 3033653-37.patch | 23.43 KB | seanB |
Comments
Comment #2
Panchotypo
Comment #3
seanBPatch is attached.
Comment #5
seanBNeeds to be tested against 8.7.x
Comment #6
phenaproximaWe can remove the word "allowed" from this sentence, I think.
Can we also get before/after screenshots of this, steps to reproduce, and tests?
Comment #7
seanBComment #8
seanBFixed #6 and added tests. IS updated with steps to reproduce and screenshots.
Comment #9
phenaproximaComment #10
PanchoNice the exception is gone!
However I don't really think we need to introduce a new string here nor do we need any Media library-specific message at all.
(both screenshots with #1953568: Make the bundle settings for entity reference fields more visible. applied and with Inline Form Errors enabled).
This way, the bug may be fixed in 8.6 as well (enclosed patch is against 8.7 however).
The elephant in the room to be fixed generically is #3033652: Be more helpful if there's no bundle to choose from. Here, I think we should have an explicit error message, but that's out of scope here.
Comment #11
PanchoNote that when adding a media library field (completely configured or not) as existing field to a second host bundle, autocomplete rather than the Media library browser is selected as default widget. First thought was this could be a general problem, see #2717319: Provide better default configuration when re-using an existing field, however it seems this might be specific to Media library. Anyway, that's a discrete bug, probably worth a separate issue.
Note also that what we're doing here basically holds for autocomplete widgets as well, but is out of scope here and not media library's problem anyway, so yet another issue to be filed.
Comment #12
seanBThanks @Pancho! Nice write-up. Patch also looks good to me.
I don't have a strong opinion on whether we should hide the widget or show a message. I'll leave that up to the UX team.
Other entity reference widgets in core get rendered in this case, even though they are not useful at all. For example the 'Select' widget just shows the option 'None'. That might be a reason to at least show the widget, but since this is kind of an edge case anyway, I think the most important thing is to at least not break with a fatal error. I could live with it either way :)
Comment #14
PanchoThanks for reviewing!
#10 (D8.7) had just an unrelated, random test failure, but passed all tests in re-run.
Here's a D8.6 patch as well. No added strings, so should be ok for D8.6.
Comment #16
phenaproximaIdeally we will land this for 8.7.x first, and then try to get it backported. :)
Comment #17
PanchoIt doesn't really matter, but I read:
on https://www.drupal.org/core/backport-policy. Has this changed or am I getting it wrong?
Comment #18
PanchoD8.7 patches are NR.
Comment #19
PanchoTrue, but the next paragraph applies as well:
So sorry, it was me who got it wrong.
Comment #20
seanBI believe this bug was introduced in the 8.7 branch, so fixing this in 8.6 is probably not necessary.
Comment #21
Pancho1.
✅ Doublechecked this on a D8.6 install: bug was introduced in D8.7 so doesn't need to be fixed in D8.6.
2.
Both patches fix the InvalidArgumentException, and they are fine unless the media reference field is required. In that case, on submit, ValidReferenceConstraint throws the rather generic error "This value should not be null."
I think we should revisit that aspect more generically in #3038261: Provide help text on Entity Reference widgets if there's no referenceable entity, and get this one fixed.
3.
Neither do I. Neither solution is the last word on this, but a quick stop-gap for an ugly bug. So let's rather get forward with one of these.
4.
I'm marking this issue as major for two reasons:
Comment #23
seanBThis has been discussed for the media library widget in the UX meeting today: https://www.youtube.com/watch?v=MG0SgDMWsH0
I think the general consensus was we should try to show a helpful message to users instead of hiding the widget. So for editor users without permissions to edit the field or create new bundles of the referenced entity type it should be something like:
There are no media types available. Please contact a site administrator.
For users with permissions to create new bundles of the entity type, it should be something like:
There are no media types available. Please <a href=":url">add a media type</a> and enable it for this field.
Comment #24
seanBComment #25
phenaproximaThe UX feedback was pretty unanimous; we can change labels all the way through beta if we want to bikeshed the wording of the messages. I'm removing the "needs usability review" tag.
Comment #26
seanBStarted from patch in #8 since that was closest to what it should be. Added extra tests for the message variations.
Comment #27
phenaproximaThree things:
1) This is a very long ternary. I think it might be easier to read if it were done as an if/else structure.
2) I feel like we might be better off if we just check user permissions, rather than access to a specific route.
3) I'm not sure if we should switch on whether the user can add media types. My feeling is that we should instead look at whether the user has permission to configure the field, and link to its settings form if they do. Then, if they get there and see that there are no media types that can be referenced, they can navigate away on their own and add more. In other words, we shouldn't assume that no media types exist -- we should instead assume that the field is misconfigured.
Comment #28
seanB#27
1. Fixed.
2. The permission does not guarantee access to create media types. Users might see the link and still not have access if we do that.
3. The problem we trying to solve here is that the installation does not contain media types. In all other cases the widget loads either the selected media types, or all existing ones (if the list of configured types is null or empty). The place where this could manifest itself already is the field configuration page. Creating a media type solves the issue, so that should be the action we link the user to. Added a comment to explain this a bit better.
Comment #29
seanBWhoops, small typo. This patch should do it.
Comment #30
phenaproximaWe're not checking permissions, so let's say "...if they have access."
Um...the comment JUST said that this would return all media types if none are available. So doesn't this mean the message will never be displayed?
There's no need to inject the access manager at all. We can just do this:
Comment #31
seanBChanged the approach a bit since I found #3038245: [PP-1] Don't allow all media types if no target bundle is configured. This patch uses the media types returned in
getAllowedMediaTypeIdsSorted()
to determine whether or not to show a message. As it turns out, there is undesirable behavior ingetAllowedMediaTypeIdsSorted()
. When the list of target bundles for a field is an empty array, we currently return all media types, while we should return none.I think we should solve both issues at the same time here, since I don't really see how we can separate these 2 issues. New patch is attached.
Comment #32
seanBForgot to remove the access manager. We don't need that anymore.
Comment #33
phenaproximaI don't think this needs to be a ternary. I believe the target_bundles setting will always exist, and NULL is a valid value for it. So we can just say
$allowed_media_type_ids = $handler_settings['target_bundles']
.Nit: This could just be
$allowed_media_type_ids === []
.This should probably be an elseif.
Based on my quick research, we have no actual dependency on Field UI. So we'll need to check if field_ui is enabled before generating this URL. Also, can we move all of this message generation logic to a protected helper method?
Shouldn't we also be including a field.field.node.TYPE.field_null_types_media.yml too?
Comment #34
phenaproximaI discussed this with Sean a bit on Slack. Here's what I think we need to do if no media types are available for the field:
If the user has the "administer HOST ENTITY TYPE fields" permission, we show this message: "There are no allowed media types configured for this field. Edit the field settings to select the allowed media types." If the Field UI module is enabled, we link the words "Edit the field settings" to the appropriate place (assuming the user has access to the generated URL).
If the user does NOT have the "administer HOST ENTITY TYPE fields" permission, we just say: "There are no allowed media types configured for this field. Please contact the site administrator."
It's more important that we get the logic correct than the actual wording. Strings can be changed in beta.
Comment #35
seanB#33
1. Fixed.
2. Fixed.
3. I think the intention clearer and the a little bit less complexity if we don't use an elseif.
4. Fixed.
5. The patch shows it as a copy, but it's there :)
#34 Fixed.
Comment #36
phenaproximaGetting there!!
This should be using Drupal\Core\Session\AccountInterface, not AccountProxyInterface.
$current_user and $module_handler should both default to NULL, and their doc comment should begin with (optional).
Should be "Gets the message displayed when..."
Technically this should be MarkupInterface. And the description should probably be something like "The message to display when there are no allowed media types for this field."
I think we should rename this method. getEmptyMessage() sounds like nothing is referenced, but in fact this is the message we generate when there are no media types available. So how about getNoMediaTypesAvailableMessage()?
Nit: This is hard to read, can it be organized a bit? Maybe something like this:
We only want to link the text if $url->access() checks out. So let's be sure to check that as well.
Once Field UI is uninstalled, we should also assert that the "Edit the field settings" link does not exist.
Comment #37
seanBFixed #36
Comment #38
phenaproximaMore nits!
Nit: Can this key be 'no_types_message', not 'empty_types'?
s/administrator/privileged
Nit: "field UI" should be "Field UI", since that is the proper name of the module.
Same here.
Nit: Let's use Url::fromRoute(), so it's a bit clearer what we're creating here.
We can pass $this->currentUser to $url->access().
Field UI
I don't think this comment adds much. :)
I think we might want to assert that the link appears _within_ the field wrapper. To that end, we'd need to do something like this:
$assert_session->elementExists('named', ['link', 'Edit the field settings'], $wrapper_element);
Should be 'target_bundles'.
This is going above and beyond. Nice. :)
"Field UI"
So it occurs to me that these assertions are pretty repetitive. We could abstract them into a helper method. Something like this:
Should be target_bundles.
Comment #39
seanB#38
1. Fixed.
2. Fixed.
3. Fixed.
4. Fixed.
5. Fixed.
6. Fixed.
7. Fixed.
8. Fixed.
9. See 13.
10. Fixed.
11. Yay!
12. Fixed.
13. Changes the test a bit. We were actually asserting the link as part if the message twice. So removed the duplicate assertions. This helps make the test feel less repetitive. The helper method would need quite a bit of parameters and logic (field name, field config, user permissions, message, available media) to figure out what the state should be of each field, so I think the current state makes the tests quite a bit easier.
14. Fixed.
Comment #40
phenaproximaNit: Should be "Gets", not "Get".
Should be "Show the default message..."
Let's just assert the absence of the text "There are no allowed media types configured for this field." If we assert the entire default message, the test would still pass if the user has permission to administer the fields.
Same here. We should assert "There are no allowed media types configured for this field."
And here too. Let's make the same change for everywhere else in the test.
RTBC after this. Nice work!!
Comment #41
seanBFixed #40.
I think we should only assert part of the message in the cases where we do not expect a message. If we do expect a message, we probably want to make sure it is the right one. So only updated the tests for
elementTextNotContains
.Comment #42
phenaproximaMakes sense to me. Thanks!
Comment #47
webchickCrediting folks from the UX call back in #21.
The bug fix looks great; I'm really happy that we give the user the most actionable feedback possible, given that fixing this requires finding a very, VERY buried settings form somewhere.
Committed and pushed to 8.8.x, back-ported to 8.7.x.
Comment #49
xjm