Updated: Comment #21
Problem/Motivation
The YAML keys for local tasks are longer than needed.
Proposed resolution
Make the grouping by route instead of plugin ID, and make the other keys simpler if possible.
Local tasks are provided by plugins implementing LocalTaskInterface instead of type MENU_LOCAL_TASK in hook_menu() https://drupal.org/node/2044515
says now:
user.login_tab:
route_name: user.page
title: 'Log in'
tab_root_id: user.login_tab
user.new_password_tab:
route_name: user.pass
title: 'Request new password'
tab_root_id: user.login_tab
user.new_register_tab:
route_name: user.register
title: 'Create new account'
tab_root_id : user.login_tab
weight: 10
adding an example:
core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
menu_test.tasks_default_tab:
route_name: menu_test.tasks_default
title: View
tab_root_id: menu_test.tasks_default_tab
menu_test.tasks_default_edit_tab:
route_name: menu_test.tasks_default_edit
title: Edit
tab_root_id: menu_test.tasks_default_tab
Each local task is represented by a plugin. Each plugin definition (found in the $module.local_actions.yml file) contains three to five local-task specific keys underneath the top-level key which is the unique plugin ID. Pattern for the plugin ID is: module_name.whatever_you_want_that_does_not_have_a_dot
Keys are:
route_name: The machine name of the local task route - this also determines where it's displayed
title: The title of the local action. By default, it will be passed through t() and localized.
tab_root_id: The plugin ID of the "root" tab (generally the top, leftmost one) which serves to group a set of tabs.
tab_parent_id: (optional) The plugin ID of the tab that is the parent - only relevant for 2nd level tabs
weight:(optional) The integer weight (lower weight tabs are further left, default is 0).
after would it be:
user.login:
route_name: user.page
title: 'Log in'
base_route: user.login
user.new_password:
route_name: user.pass
title: 'Request new password'
base_route: user.login
user.register:
title: 'Create new account'
route_name: user.register
base_route: user.login
weight: 10
added example would be
menu_test.tasks_default:
title: View
route_name: menu_test.tasks_default
base_route: menu_test.tasks_default
menu_test.tasks_default_edit:
title: Edit
route_name: menu_test.tasks_default_edit
base_route: menu_test.tasks_default
Each local task is represented by a plugin. Each plugin definition (found in the $module.local_actions.yml file) contains one to five local-task specific keys underneath the top-level key which is the unique plugin ID. Pattern for the plugin ID is: module_name.whatever_you_want_that_does_not_have_a_dot
It is recommended is to name the plugin the same as the route name or route_name + '.task' or '.tab'
route_name
Keys are:
- route_name: The machine name of the local task route - this also determines where it's displayed.
- title: The title of the local action. By default, it will be passed through t() and localized. Strings with spaces should use single quotes.
- base_route: The route where the "root" tab (generally the top, leftmost one) is displayed and which serves to group a set of tabs.
- parent_id (optional): The plugin ID of the tab that is the parent - only relevant for 2nd level tabs. If this is set, base_route should be omitted and will be supplied from the parent
- weight: (optional) The integer weight (lower weight tabs are further left, default is 0).
Remaining tasks
- #2102125: Big Local Task Conversion
- then decide what the goal is here, is to to shorten the keys?
- get agreement on dx goal
User interface changes
none
API changes
local task definition changes
Related Issues
#2095117: Menu system should provide a default tab if none exists
Comment | File | Size | Author |
---|---|---|---|
#74 | interdiff.txt | 1.29 KB | dawehner |
#74 | local_tasks-2107531.patch | 70.2 KB | dawehner |
#71 | increment-2107531-68-71.txt | 6.39 KB | pwolanin |
#71 | local-tasks-2107531-71.patch | 69.43 KB | pwolanin |
#69 | interdiff.txt | 4.9 KB | dawehner |
Comments
Comment #1
neclimdulA longer post describing some of my thoughts leading to proposing this in IRC. http://www.nerdpalace.net/node/6
There are complications with the parent_id/grouping changes I'd suggested in that post that I've almost worked out but the rest is fairly easy to accomplish. I can post a patch with those changes shortly.
Comment #2
pwolanin CreditAttribution: pwolanin commentedI think we could certainly improve the naming as suggested in that post, but I really don't like the idea of assuming the plugin ID is the the route name.
Comment #3
webchickTagging.
Comment #4
neclimdulPatch.
I don't think the assumption is as hidden or dangerously magical as is implied. route_name still exists and is usable and this validates that the guess is correct and throw an exception to notify developers. It is just a short hand for the common cases.
Comment #6
neclimdulwrong patch...
Comment #8
neclimdultypo
Comment #10
neclimdul#8: drupal8.plugin-system.2107531-8.patch queued for re-testing.
Comment #12
neclimduljust a reroll.
Comment #14
tim.plunkettMissed a spot. The other fail was HEAD being broken.
Comment #15
webchickx
Comment #16
pwolanin CreditAttribution: pwolanin commentedI think we should still convert the grouping to be by route, e..g. base_route
dawehner also suggests that if you have a parent_id we should automatically fill in this value for you from that of the parent.
Comment #17
neclimdulI thought we decided those where separate issues.
Comment #18
YesCT CreditAttribution: YesCT commentedmost of the other changes remove the _tab postfix. should these (and a couple others) also do that?
Comment #19
YesCT CreditAttribution: YesCT commentedit might be helpful to update the issue summary and explain the difference between the root and parent id. along with any other info to update the summary.
Comment #20
dawehnerI would love to get the conversions in first.
Comment #21
YesCT CreditAttribution: YesCT commentedoh, root and parent are describe in the local tasks change notice. parent is for 2nd level tasks.
taking the pattern further we can take of _tab from the plugin_id.
then if the plugin id is the same as the route_name, we can remove the route_name.
Also, if it is itself the root, we can take off the root_id.
So... with the patch here, it would become
and it magically knows to look for a route of the same name and that it is its own root.
Before, it was clear the root_id was the plugin_id for the local task and did not have to be named the same as the route.
issue title is:
Improve DX of local task YAML definitions
So, what will our instructions be for developers?
Local tasks are provided by plugins implementing LocalTaskInterface instead of type MENU_LOCAL_TASK in hook_menu() https://drupal.org/node/2044515
says now:
adding an example:
core/modules/system/tests/modules/menu_test/menu_test.local_tasks.yml
after would it be:
added example would be
I'll put this is the issue summary so others can edit and correct it.
Note to self: I think class is a key missing from the list of three to five keys.
So, I'm not sure if this is 'Improving the DX' or 'shortening what they have to type'.
Comment #21.0
YesCT CreditAttribution: YesCT commentedbefore and after example added
Comment #21.1
YesCT CreditAttribution: YesCT commentedhtml
Comment #21.2
YesCT CreditAttribution: YesCT commentedhtml again
Comment #22
YesCT CreditAttribution: YesCT commentedI'm not sure this is a good idea, but since I went through menu_test and applied the pattern to bits that were left out, here it is in case it is useful.
Comment #24
neclimdulSo, I think these are great statements describing the confusing I've seen around this patch.
So the developer experience literally is "what I have to type" so I don't see those as completely seperate. Also something less clear from touching up or reviewing is "what I have to remember and copy around" though in the future this will be the case for re-factoring.
I found that when making a set of local tasks the biggest process after finding the routes is this dance of copying things around or repeatedly typing out certain strings. Its really frustrating and it ends up with in the worst case 3 copies of the exact same string on a definition(this is true with the _tab only you've got to remember which one gets the suffix and which don't).
Anywhere in code where we have a pattern like this where we consistently are calling a function and copying the same thing into multiple parameters we would just provide a default behavior. We document this is how we expect to generally call this so this is how each argument will behave if the argument isn't provided. Everything is clear.
This however is actually more frustrating then code though because its manually typed strings rather then what would likely be repeated variables in code so its very error prone and has more maintenance debt in that you're going to have copy the string out all over the structure. The code example used by YesCT pretty clearly demonstrate this I think.
And I think YesCT did a pretty good idea of explaining what we tell developers as well.
Comment #25
pwolanin CreditAttribution: pwolanin commentedgood DX is NOT ONLY about what you have type the 1st time, it should also be about reducing the WTF factor when you come back to it 6 months later or are looking at it as a novice.
I think the magic proposed here is a bad idea and will make the DX overall worse. There is no reason to even want it unless you've just been converting 50 tasks, which is something we have to do once for core and never again. So, it's compelling to neclimdul for just that reason, but it should not go in as a general change.
I agree we could make the keys more concise, and I think we should redo the grouping based on route name rather than plugin ID.
Comment #26
pwolanin CreditAttribution: pwolanin commentedIf we are going to change to grouping by route, then there is no point to rename "tab_root_id" to something else since we are going to redefine it anyhow.
Part of the reason all those values are explicit now is that I decided when I wrote the initial patch for this that we should have them that way and that the excess magic of hook_menu should not be carried over here because it was horribly confusing for people to tease out what did what.
So, I think the change here should be limited to at most:
- changing "tab_parent_id" to "parent_id"
- copying by default the tab_root_id from the parent for tabs not at the top level.
Comment #27
tim.plunkettDidn't make it :(
Comment #28
pwolanin CreditAttribution: pwolanin commentedNow that the big conversion is in we can revisit this.
Comment #29
dawehnerWe could simplify it even a bit more. What about just having parent_id or parent_route_id (depends which one we want).
If parent_id == tab_parent_id, we are at the default local task case. If parent_id is a default local task, we are at level 0 but a sibling etc.
Comment #29.0
dawehnerremaining tasks
Comment #30
pwolanin CreditAttribution: pwolanin commentedtagging as beta bloker since I know improved DX is a big concern for webchick and others, and it would be unreasonable to change the YAML keys after beta.
Comment #31
xjmI swear there is a meta issue somewhere for "all the stuff that used to be defined with hook_menu()"... where?
Also, I'd consider this a major DX issue.
Comment #32
xjmComment #33
tim.plunkettComment #34
Gábor HojtsyLooks like we are still not close to figuring out how to improve DX of the tabs definitions or if any proposed improvement is actually an improvement or not. In practice, we now have the following structures in core:
The plugin ID of the tab is not required to be the same as the route, but we adopted that as a common practice because otherwise it is free reign, people can use '_tab' after the route name, or replace dots with underscores or shorten things or whatnot and then all you end up with is a mess. I think its an improvement that we have clear guidelines for this. I'm not sure if that is even a possible use case that a route may serve different tabs at different places, so tying the two together with a best practice pattern sounds good to me.
As for then leaving them out if they are not different from each other and ending up with:
I don't have a strong feeling either way, but it is true that people do understand classes where not all properties of the object are on the constructor (and have defaults, sometimes computed). People also understand functions that have optional arguments, etc. I think its a good question how default values here would be different from those.
I think if we document / suggest the naming parity rule for the tab plugin ID and the route name, then repeated equal values will keep being common. If we don't document / suggest that, then the plugin IDs will be an inconsistent mess, and people need to wonder how to name them and find some other rule / suggestion anyway. That sounds like more work. So if we assume our best practice setup will have the same ID 2-3 times on it, the question remains if that is best for developers to understand and repeat their understanding of the inner workings of the system each time they write a tab definition.
Comment #35
pwolanin CreditAttribution: pwolanin commentedI'll work on this soon.
as in #26 100% object to assuming the key is the route name, but taking the root from the parent is sensible and will cut down on errors.
Comment #36
Gábor Hojtsy@pwolanin: would be good to have a list of simplifications that all parties support :) You just mentioned one here. Hope there is more, because taking the root from the parent sounds like not a major or beta blocker change(?)
Comment #37
pwolanin CreditAttribution: pwolanin commentedSo, starting on this, I realized a problem - if you want to group by route (e.g. base_route) you cannot take route parameters into account. In contrast, with the current API, you can potentially specify both the route and route parameters for the root tab.
In other words, the current system would allow you to define a group of tabs that appear e.g. on just a specific node or user.
Is that something we want or need to support? I don't think it's supported in Drupal 7.
And in fact, the current code just matches the route name (not parameter), so really it would be no change.
Comment #38
pwolanin CreditAttribution: pwolanin commentedThis patch might have missed a couple things, but generally got most of the change
Comment #40
Gábor HojtsyThe base_route looks better then the tab_root_id to me. Is this the extent of changes proposed or just the first step?
Comment #41
cilefen CreditAttribution: cilefen commentedComment #42
pwolanin CreditAttribution: pwolanin commentedThanks - looks like a step in the right direction.
I'm not sure about the logic of:
Does that correctly group sub-tabs?
Comment #44
cilefen CreditAttribution: cilefen commentedProbably not. base_route isn't set in many cases now so what are we trying to store in the base routes instead? Should it be:
$base_routes[$task_info['route_name']] = $task_info['parent_id'];
I don't really understand the issue enough to say any more about it.
Comment #45
pwolanin CreditAttribution: pwolanin commentedI think you might be able to omit the elseif entirely - just skip those:
Comment #46
cilefen CreditAttribution: cilefen commentedComment #47
dawehnerI wonder whether we can introduce to auomatically add the base_route, if possible.
Comment #48
cilefen CreditAttribution: cilefen commentedIs parent_id supposed to be the base_route if base_route is not set?
Comment #49
pwolanin CreditAttribution: pwolanin commented@cilefen - the ID of each task might happen to be the same, BUT the ID is an arbitrary.
So, the logic I'm going for is that if parent_id is set, the base_route should be looked up from the parent using that ID.
@dawhener - I'm not sure what you mean - it should add it from the parent. Or you mean the idea of defining the tab on the base route by default? IF so, let's tackle that as a follow-up that this enables.
Comment #50
cilefen CreditAttribution: cilefen commented@pwolanin: that is what I thought. If I am following this, that additional code should go right near the patch in #46, correct?
Comment #52
pwolanin CreditAttribution: pwolanin commented@cilefen - yes, the definitions are keyed by plugin ID, so you shoudl be able to set the base_route in the 1st loop.
Comment #53
cilefen CreditAttribution: cilefen commentedI have noticed that in cases where this conditional is used:
foreach ($definitions as $plugin_id => $task_info) {}
$plugin_id sometimes has parameters attached after a colon. So, you shouldn't try to test if the $route_name passed to the method is equal to $plugin_id. It is better to do this:
$route_name == $task_info['id']
This will certainly still fail, but I'd like to see how.
Comment #56
pwolanin CreditAttribution: pwolanin commentedHere's an increment relative to #46, including fix for a yaml conflict.
I think this fixes the missing sub-tabs.
Comment #59
dawehnerThis patch is wonderful. It really improves the DX of local tasks while keeping the logic the same, now it just have to pass.
Sadly
This is wonderful <3 < 3
Comment #60
pwolanin CreditAttribution: pwolanin commentedAh, dawehner brilliantly saw the bug leading to the new fails - a PHP wtf with using a reference in the 1st foreach loop.
This also fixes some other tests and removes more code from ContentTranslationLocalTasks and ConfigTranslationLocalTasks
Comment #62
pwolanin CreditAttribution: pwolanin commentedfixes a fatal in ContentTranslationLocalTasksTest since it was calling a method that had been removed.
Comment #64
pwolanin CreditAttribution: pwolanin commentedok, silly mistake - if we're not using the & reference we still need to update the array we are using in the top loop.
Comment #65
pwolanin CreditAttribution: pwolanin commentedoops, posted the wrong patch
Comment #68
pwolanin CreditAttribution: pwolanin commentedSome (hopefully) final fixes for various mistakes in local_tasks.yml files and test fixes.
Comment #69
dawehnerWhat is the reason for this hunk?
I went down and tried to remove that removal as well as add a new unit test for childs, so we know that the parent_id works,
though I have not managed to get it green.
Comment #70
pwolanin CreditAttribution: pwolanin commented@dawehner - that section didn't seems to be doing anything different from the 1st section - just testing that the default tab gets a -10 weight - there wasn't actually anything related to derivatives involved.
We should indeed add a little new testing around parent_id, and we need a longer-term follow-up to make more than 2 levels of tabs work correctly.
Comment #71
pwolanin CreditAttribution: pwolanin commentedExtending dawehner's start in #69, this adds to the unit test so we verify the expected processing of child local tasks and tries to make the unit test code a little easier to maintain.
Comment #73
pwolanin CreditAttribution: pwolanin commented71: local-tasks-2107531-71.patch queued for re-testing.
Comment #74
dawehnerExtended the test coverage to 100% in the getLocalTasksForRoute method.
Comment #75
xjmCritical beta blocker.
Comment #76
webchickWow, this patch is absolutely just full of win. Great job. Also nice job on the issue summary, it was super easy to tell what was going on.
Committed and pushed to 8.x. Thanks! We need to update the existing change notices and documentation under https://drupal.org/node/2122071 now.
Comment #77
pwolanin CreditAttribution: pwolanin commentedComment #78
pwolanin CreditAttribution: pwolanin commentedComment #79
pwolanin CreditAttribution: pwolanin commentedadded change notice: https://drupal.org/node/2165273 and updated https://drupal.org/node/2044515
Comment #80
dawehnernice!
Comment #81
pwolanin CreditAttribution: pwolanin commentedupdated docs at https://drupal.org/node/2122253
Comment #83
YesCT CreditAttribution: YesCT commentedIn the change record, we wrote:
"Note in particular that by defining a base_route, it's simpler to discover how to associate a tab, rather than having to find a plugin ID."
I'm not sure why we said that. Don't we look up the route id in the same place that we would have looked up the plugin id?