Problem/Motivation
The current workflow to create new media entity bundles is not very user friendly. When creating a new bundle the user has to create it and choose a placeholder value for the form item "Field with source information". Then he has to create a field to store the source and edit the bundle to be able to choose the field.
Proposed resolution
Define a trait that isolates the handling of field selection and the creation of a default field. Media type plugins can use the trait to get this behaviour.
Remaining tasks
- Write tests
User interface changes
When creating an entity bundle, provide a checkbox "Use default source field for this media type provider" which is checked by default. (Select box is only shown on initial creation of the bundle, deselecting the box does not reveal the dropdown, because there will be nothing to select yet).
API changes
A new trait that allows media type plugins to add source field selection and default field creation. The trait has a method that produces the field selection and the checkbox for use in the configuration form.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #83 | interdiff-2625854-82-83.txt | 865 bytes | phenaproxima |
| #83 | 2625854-83.patch | 25.1 KB | phenaproxima |
| #82 | interdiff-2625854-78-82.txt | 8.15 KB | phenaproxima |
| #82 | 2625854-82.patch | 25.34 KB | phenaproxima |
| #78 | interdiff-2625854-76-78.txt | 7.03 KB | phenaproxima |
Comments
Comment #2
slashrsm commentedComment #3
sam152 commentedIf anyone is interested in this video_embed_media creates a default field for the user when they create the bundle. They can then choose another one if they wish not to use the provided one. This works quite well and could be considered to solve this problem as well?
Comment #4
slashrsm commentedYes, solution in VEF is definitely very nice UX-wise. I am not sure if we can solve this generally or will every type plugin need to solve it separately.
Comment #5
DeFr commentedI think what's in VEF should be generalized and set up in Media Entity.
VEF current createVideoEmbedField pretty much match the getDefaultSourceField proposed in the IS ; to get something really nice, I think it should be split, and return an unsaved field. It'd then be MediaEntity responsibility to actually save those fields if the site contributor ticked the corresponding checkbox.
Comment #6
draganerorFixed typo in title.
Comment #7
slashrsm commented#5 sounds like a good solution. This would significantly improve site builder UX.
Comment #8
sam152 commentedI think it's would be unusual that a site builder would want to delete the default field? Should the media provider enforce the creation of a field and not give the site builder the option to delete it? This kind of simplifies things, because we would no longer need any kind of config form for selecting which is the "source field", but it's a rather large BC break as well.
Comment #9
slashrsm commentedIdea was to keep this flexibility. Some media types support more than one field type for example. Sticking to auto-created fields would remove possibility to choose.
Comment #10
marcoscanoThis is a WIP patch, with the corresponding modif also in the twitter plugin, as an example.
Can someone please give a look to see how this approach looks?
It is not functional yet, the field is created but the "source_field" plugin config value is not saved. Didn't figure out how to do that yet...
On the other hand, it would be great if we could avoid the hook_insert and the flag on the entity to carry on the user preference from the checkbox.. But I couldn't find a way either, any suggestion is welcome!
Comment #12
marcoscanosorry, didn't name correctly one of them
Comment #13
marcoscanoKept working, found some bugs...
This seems to be functional, please review this patch and not the previous one.
Comment #14
marcoscanoI seem to have a problem uploading patches.. :) sorry
Comment #17
marcoscanoComment #18
marcoscanoComment #19
marcoscanoComment #20
slashrsm commentedGreat work so far. More of a general approach kind of review for now.
This could live on the bundle class as part of the
postSave()callback.I prefer to have everything in one place and entity classes are that place in D8.
The reason why we didn't add this feature at the beginning is to prevent any assumptions.
Who says that every type will be using just one field? There might be (very rare) situations where a type might need more fields to do its job.
For that reason I'd try to avoid adding this assumption to the bundle. It would be much better if all this logic would live on base type class. This way most of types just extend the base class nad the ones that need more do their own crazy stuff.
Checkbox could stay on the bundle. Maybe we could add another key in the type plugin annotation that would determine if it creates fields or not. This would allow us to control the display of the checkbox based on which type is being used.
Comment #21
marcoscanoThank you for reviewing! The patches attached address the feedback.
There is still work to be done on DI, removing deprecates and writing tests, but let's build an agreement on the general approach first :)
Re
Mmm.. do you think it is possible that a given plugin could have no source field at all? I was assuming that at least one source field would always be present, and then all plugins would need to implement the
::getDefaultSourceField()method (which is more or less the whole point of this issue, in order to assure a standard UX across all plugins). With this idea, the flag to create automatically a source field could be just another configuration value on the plugin config (as it is implemented in this patch). Or perhaps I just didn't understand your point correctly :) please indicate if that is the case.Comment #22
marcoscanoComment #23
marcoscanoHad some time to work a little more on this, removed some deprecates and started working on a test (not functional yet, couldn't figure out why at this point)
(Note: no twitter interdiff because it is the same patch as #21)
Comment #24
slashrsm commentedGreat progress! Few more thoughts.
Bundle should not assume which configuration variables are on the plugin. Could we rely on the return value of
getDefaultSourceField(). If something is in there create fields and skip if not.We also have to figure out how we detect auto creation of fields and set "source_field" on the type configuration.
Since this is on the main interface all plugins need to implement it. It covers 95% single source field problem, but it doesn't cover a hypothetical multiple fields situation. Could we change this function to return a list of fields instead of one. This should solve everything that we need.
Comment #25
marcoscanoThank you Janez!
Tried to address all the feedback. Not sure though if the
is the best approach to save the source field name into the plugin config...
Comment #26
marcoscanoComment #27
marcoscanoMinor tweaks
Comment #28
slashrsm commentedI feel that we don't need this any more now that we rely on the return value for the function.
$this->getType()should be sufficient for this.Plugin should have control at least over field instance configuration if not also over view and form displays. If some custom field type would be used there might be configuration values we can not assume here.
Could plugin, instead of relying on array keys, return full configuration for itself if the default fields are going to be used.
This was of saving is not the nicest. Something like
would be best but I am not sure if we are supposed to save the entity again here. We might want to check with someone that is more familiar with Entity API.
Would probably make sense to rename to
getDefaultSourceFields()Comment #29
marcoscanoThanks! one doubt though:
In #5 the approach proposed was to make the plugin create an unsaved field and then Media Entity would be responsible for saving it, but now that the checkbox is on the plugin side, does it still make sense? Is there a problem in moving all this logic to the plugin method, and on the ::postSave() we would just call something like ::createDefaultSourceFields() (which would actually create the field(s) only if the checkbox is marked) ?
On the other hand, the
$this->save()inside the postSave() doesn't work, I got exceptions when trying to do that :) so the only way I could come up with was the ->getEditable() way, but I agree that it is definitely not the best solution...Comment #30
slashrsm commentedThis comment made me think if we should rather rename this to
onBundleCreate()or something along those lines and let type plugins do whatever they need to do (including creating fields). This way we avoid any assumptions re fields and prevent ourselves from adding another callback next time when we come across some other similar requirement.This won't fix the saving problem, but we can go with the current approach in the worst case scenario I guess.
Comment #31
boobaa@slashrsm: What about using hook_ENTITY_TYPE_insert()?
Comment #32
slashrsm commentedI'd assume that we'd have same problem there since it is run just after the postSave() we're currently using.
Comment #33
slashrsm commented@marcoscano: What kind of errors were you getting?
Comment #34
marcoscanoThe exception I got with the ->save() approach is:
Concerning the comment int #30, yes.. perhaps leaving everything to be done on the plugin side will end up being the cleanest solution after all. The only drawback I see is that we cannot enforce a common UX accross all plugin types, because some may implement the auto-generation of the source field, and some others may not. But it is difficult to assure a standard workflow anyways, once in theory the plugins may have different behavior (more than one source fields, etc)
Comment #35
marcoscanoStarting over. This new approach delegates all the logic to the plugins.
Concerning the save of the
source_fieldvalue, is it really necessary? This new patch uses the same strategy used currently in video_embed_field, where if no value is set on$this->configuration['source_field'], then we assume we have created a new field and its name isself::MEDIA_ENTITY_PLUGINNAME_DEFAULT_FIELD_NAME. Functionally it works, but I'm unsure of any side effects or edge cases we might miss by doing this.Also, the test needs some more love, still WIP.
Comment #36
chr.fritschComment #37
eelkeblokComment #38
eelkeblokComment #39
eelkeblokTwitter patch needed a reroll (the media_entity one is just a copy of 35). There were conflicts in the use clause of Twitter.php. Besides some actual conflicts (that didn't seem to hard to resolve) I noticed the previous version of the patch moved the line:
while it was removed entirely in the solution to #2650976: Use SVG to generate thumbnails for text-only tweets. The reroll would add it back at the top, but the removal seems legitimate (although unrelated to that specific issue) so I haven't added it back here.
Comment #40
eelkeblokHi all. @chr.fritsch asked me to have a look at this issue for the #dcmuc16 code sprint.
I would hesitate to make assumptions about the source field based on the value being empty; as soon as the default is created, I would revert back to the "old" behaviour where the field is just configured as the source field, as any other field. I would actually advocate for an empty value meaning "no source field selected yet", with an appropriate label in the dropdown. This might make sense when the user opted to not create a default field, but has not yet created the field they intend to use.
I do wonder about storing the create_source_field value, it doesn't really do anything once the bundle has been created. As it stands, it would be fine if it just triggers the creation of the default field and then is forgotten. I would also go one step further and nog actually show the source field dropdown on initial creation, since there won't be anything to be selectable there for sure.
When it might make sense to store the create_source_field value (although the naming might need to be reconsidered) is if we want to allow the user to change this afterwards (or at least as some sort of indication that the bundle was created with the default field, initially). However, I think this will be a scenario filled with headaches surrounding what exactly to do when the user actually starts switching the setting. Should we force it back on if the user doesn't actually choose another field? What if another field was selected, and the setting to use the default is then switched on again? Do we need to create the default field again if it was removed? It would seem to be a lot more manageable if the switch just handles initial creation, and afterwards just reverts back to the standard behaviour with the dropdown. Full circle: in that case we do not need to actually store it anywhere.
Comment #41
eelkeblokComment #42
eelkeblokWith regard to storing the source field, this seems to happen already, although I am not sure how, exactly. However, when I generate a new bundle and immediately export the configuration afterwards, there it is.
Attached is a new patch for m_e_twitter and another copy of the same m_e patch, just to have the complete set. Changes I've implemented:
- Do not show the dropdown on initial form creation
- Add a "- None -" option to the dropdown, just so the dropdown is not empty when there are no fields to select yet
- Show the checkbox exclusively based on whether the bundle is new
WRT where this should live, I want to have a look to see if this can be isolated into a trait. It seems a bad idea to have each plugin implement practically the same logic just because we're not sure it is appropriate for *all* plugins.
I did not dig further into not storing the create_source_field config value. Once it's in the form, it will get stored, so it would take effort to not save it, which seems unnecessary. If anything, it records the fact that the bundle was initially created with the default field. However, feel free to disagree.
Comment #43
eelkeblokHere's a stab at moving most stuff from the Twitter class into a trait. I'm curious to hear whether this makes any sense as an approach, or whether I am overlooking something as to why this wouldn't work (such as the field definition stuff is not generic enough).
Comment #44
chr.fritschFrom the UX workflow, this is much better compared with what we have currently in media_entity.
I think we don't need that. Source field name could be generated from 'field_media' . providerId
We shouldn't reuse existing fields in bundles. Better create a unique field for every new bundle
Don't store this property in configuration. It's just needed once
I think we can use the new trait here
Comment #45
eelkeblokI implemented these changes in the attached patches. One other change I did is remove the createDefaultSourceField() method from the MediaTypeInterface again. I think it is overly restrictive now that we have the reactToBundleCreate(); its point is that a media type *might* need to do something different than create a single default source field. The trait seems to provide enough guidance for those that do not wish to use it but do their own thing.
I am including interdiffs, but it is actually quite illustrative to just look at the patches; the changes to the Twitter class as well as the TestType class are quite minimal with most of the heavy lifting going into the trait.
I have an implementation for media_entity_image ready to go as well, I'll create an issue over there.
Comment #46
eelkeblokComment #47
eelkeblokSmall tweak, the trait was not doing one essential thing; not creating the field when the checkbox is disabled. So it as always creating the field, regardless of the setting of the checkbox.
Comment #48
slashrsm commentedGreat work! General direction looks very nice. It might seem like I had comments on everything, but they are mostly cosmetic. Overall approach look fine to me.
This is happening on save and since create is different operation it would make sense to rename this to
reactOnBundleSave()Entity type should always be media bundle. I don't think that we need this argument.
This function won't make much sense if the trait is not used. Since it is defined there I don't think that we need to define it here too.
All this functions should be protected IMO. I'd also consider joining them into one that would return all information as an array.
#empty_value/#empty_key can be used on #type => select.
No need to break. Let's make this condition one-liner.
Consistently use short array syntax.
Would it make sense to use #disabled instead? To make this configuration visible but not alterable?
This sounds like "I am checking if name was set" and say nothing about when and how it should be. It would be nicer if it would say "Should field be created on create"?
Can we add a comment and @see to determineDefaultSourceFieldName().
This should be protected I think.
Both could have custom settings depending on the type used.
If we do what I commented on above we could allow plugins to include this settings into that unified function as well.
I wouldn't make any assumptions about that (I think that we can skip it).
This is correct and it works, but it is very non-conventional. Can we assign first and then return?
Is this intended change? We are not storing this value anywhere else.
It would be nice to have more test coverage. Not just form but also actual creation of the field.
EDIT: Feel free to send every patch through Drupal CI. There is no reason not to do that.
Comment #49
eelkeblokThanks for all that, glad you like the approach. I am currently working on the test coverage (I noticed late yesterday I had inadvertently removed @marcoscano's preliminary tests, I am in the process of restoring it and expanding), but am getting stuck running Javascript tests, unfortunately. Seems to be something with my environment, because BrowserTestBase's drupalLoging method is failing, for some reason.
Comment #50
eelkeblokHere's most of the above points addressed. I couldn't get tests to work correctly yet. I do have all greens running test on dev, though, so my test env is OK now, but with this patch the test environment is running into a 500 when saving the bundle. Maybe the testbot over here will produce some helpful hints.
Some comments to specific points from #48. In all cases, don't hesitate to let me know if you disagree.
1. I actually went one step further and renamed it to "onBundleSave". on[Event] is a pretty common naming pattern, so I suppose it is appropriate here too.
4. For now, I have done as suggested, this was a point I was uncertain of myself. However, it now struck me this seems to be the sort of thing annotations were invented for. I dipped my toe in getting something going for that, but didn't go very deep because I couldn't quickly figure out what is the best way to read annotation information for the class itself. Any comments?
6. I pulled it apart as suggested in Coding Standards > Line length and wrapping
8. Not sure. I did consider doing something like it, but what should the select contain? Should it show the field label for the default field as it would create it? Or should it just be "- None -"? If not, it should get an ajax update when the user unchecks the checkbox. This seems to have some UX implications, while at the code sprint last week it was suggested the flow might actually be turned upside down. Maybe this needs some more UX scrutiny that we may want to pull into another issue.
12. I think not adding the field into the display will have many puzzled for a moment or two. If the field is not displayed at all, the user will quickly find their way to change it. @marcoscano, you originally added this, do you have any input?
Comment #51
eelkeblokSomething went wrong uploading the patches. Another try.
Comment #53
eelkeblokOne thing I forgot to mention, I renamed some stuff from DefaultField to SourceField. E.g. the trait (now MediaTypeSourceFieldTrait) and the method that generates the configuration form fragment (sourceFieldConfigurationForm()). Because basically, this implements everything for configuring a single source field.
Comment #54
eelkeblokForgot to rename the configuration form in the test type.
Comment #56
eelkeblokUnassigning for now, not sure when I might find time to continue with this. Maybe next weekend. Anyone feel free to pick this up further, especially getting the tests going properly. Also, input on the annotation-idea (point 4 above) is very welcome.
Comment #57
mtodor commentedComment #58
mtodor commentedHere is try to unstuck tests. Interdiff available to check changes.
Comment #59
eelkeblokAwesome. I think we should also have a test to make sure the field is *not* created when the checkbox is unchecked. That's a mistake I made (and caught before creating a patch) while working on this.
Comment #60
mtodor commentedComment #61
mtodor commented@eelkeblok thank you for feedback. I agree that it would be nice to check that too and here is patch with testing of creation of bundle without default field.
It simply checks that there are no fields created. I thought to add checking of combobox for source field selection, but it looks to me like duplicate (pointless) check, since there is nothing to be displayed in combobox if there are no fields.
I have also checked that test is compatible with PhantomJS and Selenium.
Comment #62
chr.fritschComment #63
boobaaMissing docblock.
Please be consistent and stick with short array syntax.
Comment #64
marcoscanoThanks everyone for moving this forward!
I have updated some of the last CS-related feedbacks, but not sure if I missed some more important point that is still pending.
Hopefully we will be able to get this over the finish line next week in Berlin.
Comment #65
phenaproximaConsulting with @slashrsm, I decided to refactor this so it's not doing the onBundleCreate() thing -- that's never been seen in core. I decided instead to have type plugins implicitly declare their support for source fields by implementing a new SourceFieldInterface, which is responsible for guaranteeing the field definition of the type's source field. The MediaBundle entity, on the other hand, is responsible for actually saving the field definition and updating all relevant entity displays.
Comment #66
phenaproximaOh for Zeus' sake. I meant to upload the interdiff, not patch #64.
Comment #68
phenaproximaFixed a load of bugs. Hopefully this will at least run the tests, if not pass them.
Comment #70
phenaproximaThis will hopefully fix the tests; I also added the ability to re-use existing field storage definitions that are present on the Media entity type. Creating a new bundle will always necessitate a new field (which is no different from the way things already work), but we'll want to be able to re-use existing storages.
Comment #72
phenaproximaSchema error, eh? Okay, let's see if this helps.
Comment #74
phenaproximaThis. This will pass the tests. At least, it passes on localhost. Works on my machine. Signed, sealed, and delivered.
Special thanks to @dawehner for figuring out a particularly tricky failure.
Comment #75
gábor hojtsyLets hide the field, if its not actionable. People don't necessarily see the real fields vs. the usually light gray disabled fields, and they click on it anyway but it does not work :) If we are to create it anyway, we can hide it.
Also lets have Create instead of lowercasing it like these:
Comment #76
phenaproximaGood call. Fixed!
Comment #77
dawehnerJust a couple of comments ...
Maybe having a quick comment what we do and why would be neat.
For string comparisons its alawys just better to use strict checking, because of this niceness: https://3v4l.org/9k0Bt
Can't you use the entity field manager instead?
\Drupal\Core\Entity\EntityFieldManagerInterface::getFieldDefinitionsand get it from there? There are more areas in this particular patchHehe,
Should this extend the existing media type interface?
If you are here you could add a trailing comma, so future changes will require less diff size
Can we use the UI here, less issues ...
Comment #78
phenaproximaAll fixed. Thanks, @dawehner!
Comment #79
dawehnerThank you @phenaproxima
For me this looks pretty good, it would be neat to get another opinion, but I'm totally fine with it.
Comment #80
slashrsm commentedWon't this be done in the plugin already?Not relevant.
What will happen if field that does not exist is saved in 'source_field'? This will create it, but won't necessarily respect the name.
In which situations can this execution patch happen?
Could this be added to the base class? Would be nice to have a description anyway?
Comment #81
slashrsm commentedLet's wrap this two in isNew()
Comment #82
phenaproximaDiscussed with @slashrsm, and we decided to not really change preSave() or the field handling stuff in MediaTypeBase. Although we did decide to add test coverage.
The reason we decided to leave things be is because the code, as written, is (by design) hyper-defensive. This means a few things:
So, for example, if you have an existing bundle that used an automatically generated, automatically named field, and you change it to use a field that already existed, it should switch to the new field without trying to create it anew. If you did the opposite -- switch from an existing field to an unspecified one -- it should automatically create a new field for you. If you do the same thing, but specify the field's name, it should automatically generate the field with that name.
It's trying to account for a lot of edge cases, and the test coverage is now expanded to account for those. I wouldn't put anything past our users, and therefore prefer to write paranoid logic.
Comment #83
phenaproximaA minor change -- made getSourceFieldName() use the entity field manager's field map, rather than trying to load fields from storage.
Comment #86
slashrsm commentedCommitted #82. Thank you all!
Comment #87
phenaproximaI'd still like to make the changes in #83, but I'll target those for the core version of Media Entity, which might appreciate the changes a little more ;-)
Comment #90
slashrsm commentedI decide to revert that since it breaks existing plugins. The plan for now is to keep this a core-only feature. Plugins will need to upgrade to the core version of the media entity anyway.
Will leave this open until #2831274: Bring Media entity module to core as Media module lands.
Comment #92
marcoscanoThis doesn't seem to be happening in this module anymore.