Implementation/COMMS-1048: Labels field on the work package view with inline creation - #25554
Conversation
Deploying openproject with ⚡ PullPreview
|
b47e919 to
a6d038f
Compare
483d2b9 to
04b74a4
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
04b74a4 to
60c6bcd
Compare
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
HDinger
left a comment
There was a problem hiding this comment.
Code wise this looks fine, and I only have some smaller remarks.
Conceptually, I was a bit surprised to see no permission check. It feels weird, that labels can only be edited and deleted on an admin level but everybody is allowed to add them. I was wondering what I am supposed to do when I added a typo or how to avoid duplicate creation, etc..
So I guess the main question for this PR is whether it is intended that really everybody can add new labels? So even anynomous users?
There was a problem hiding this comment.
Thanks for the useful feedback, Henriette! Sorry for the premature tag, I realized there were pending issues after requesting for review. 😅 In any case, the preliminary feedback is appreciated.
Conceptually, I was a bit surprised to see no permission check. It feels weird, that labels can only be edited and deleted on an admin level but everybody is allowed to add them. I was wondering what I am supposed to do when I added a typo or how to avoid duplicate creation, etc..
So I guess the main question for this PR is whether it is intended that really everybody can add new labels? So even anynomous users?
The missing permission is an oversight: it's currently only enforced at the API level. Only users with edit_work_packages permissions should be able to add/create labels.
Duplicate creation is handled at the API level in #25553 via a Labels::FindOrCreateService that is concurrency safe: a case-insensitive unique index on the name, so a create that differs only in casing, or races another request, returns the existing label.
we have a rule that any user-created label that isn't in use is also removed from the admin list of labels, you could then just remove the accidentally-created label and it would be "deleted" (assuming that no one else uses "OpenPrk" between the time you accidentally created the label and when you remove it).
Atm, typos are handled by the above rule (see FND-5/activity#comment-1735504), albeit not implemented yet.
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
akabiru
left a comment
There was a problem hiding this comment.
Follow-up on the conceptual questions:
Permissions (cbfd2ae, f0f777b):
- Users with
view_work_packagesonly see labels read-only. - With
add_work_packagesbut notedit_work_packages, users can pick existing labels when creating a work package. They can't edit labels on existing work packages or create new labels. - Creating new labels on the fly is offered only to users with
edit_work_packagesin the project. The API enforces the same rule, so anonymous users can only create labels if an admin grants that permission to the anonymous role. The client no longer special-cases permission errors (4c43553).
Duplicates: handled at the API level in #25553 by Labels::FindOrCreateService. It relies on a case-insensitive unique index on the name, and a create that only differs in casing, or races another request, returns the existing label.
Typos: the rule from FND-5 (unused user-created labels drop out of the admin list) is tracked in COMMS-1050. It needs label journaling (#25320) first, so labels that were used before are never deleted.
|
🚧 Failing tests fixed in #25655 |
Labels render like the other multi-value attributes and are edited in a multi-select that searches on the server so the workspace-relevance ordering is kept. A dedicated labels autocompleter holds the fetching so other surfaces can reuse it.
The create option is pinned below the options and only offered while the typed name matches no loaded label. The created label joins the open selection and is saved with the field.
The accessible name of the field stringified the HAL resources; it now joins the names like the title does. The create-label option also stays hidden while results are still loading.
The create action is an addTag option, so arrow keys and Enter reach it. A 403 on create shows a specific message instead of the raw error.
Save/Cancel render only alongside the input, and the combobox is announced by the field name.
The API authorizes label creation, so the client no longer special-cases 403 responses.
New labels read "<name> (New label)", matches highlight in blue, and the list closes on pick so it can open either way without covering Save/Cancel.
The create option stays fixed while the list scrolls and remains a keyboard-reachable option.
The shared schema no longer treats add_work_packages as enough to edit labels, so add-only users see the field read-only.
Add-only users pick existing labels; creating new ones on the fly needs edit_work_packages in the project.
f0f777b to
0936db9
Compare
| // ng-select gives the addTag row no class of its own; :has() reaches it via | ||
| // the marker element in the labels tag template. | ||
| .op-labels-autocompleter--panel | ||
| .ng-option:has(> .labels-autocompleter--create-option) | ||
| position: sticky | ||
| bottom: 0 | ||
| border-top: 1px solid var(--borderColor-default) | ||
|
|
||
| // The hover token is translucent; layering it over the body background keeps the pinned row opaque. | ||
| &.ng-option-marked | ||
| background-color: var(--body-background) !important | ||
| background-image: linear-gradient(var(--control-transparent-bgColor-hover), var(--control-transparent-bgColor-hover)) !important | ||
|
|
There was a problem hiding this comment.
A note on the custom styles for the "(New label)" row, since the assignee field pins its "Invite user" button without any.
The invite button lives in ng-select's footer slot, which renders outside the scrolling list, so it stays put for free. The create option here is ng-select's addTag option instead, so it renders inside the list as the last option. That keeps it a real option: arrow keys and Enter reach it, and when nothing matches it's the marked row, so Enter creates the label (the behaviour described in FND-5). A footer button can't be reached by keyboard at all.
Pinning something that lives inside the scroll container needs a bit of help:
position: sticky; bottom: 0keeps the row at the bottom edge of the list while labels scroll underneath (_autocomplete.sass). ng-select gives the row no class of its own, so:has()finds it through a marker span in our tag template. The rule is scoped to the labels panel viaclasses, since the panel is appended tobody.- The marked state layers the translucent hover token over the body background, otherwise scrolled labels show through the pinned row.
virtualScrollis off for this autocompleter, because virtual scrolling moves the list with transforms and breakssticky. Labels are fetched in full already, so this only changes rendering.keydowned()nudges the list when arrowing, because ng-select scrolls the marked option flush with the bottom edge, where the pinned row now sits.
All of it is limited to the labels autocompleter; other autocompleters are unaffected.
There was a problem hiding this comment.
Thanks for the detailed explanation. 🙇
I have to admit, that I am really not a big fan of that for several reasons:
- We already have a style for these kind of adding new elements. Look for example in the share WP dialog. Why can't we re-use the same style instead of adding again another one? The text is blue and in front of the option (instead of grey and behind it). Further, the background remains unchanged (instead of greyed).
-
A grey background ususally means disabled, which is not what you want to communicate. That somehow belongs to point 1. Further, why did you use
background-imageinstead of just setting abackground-color? -
I don't really get the necessity for making the create option sticky. Imho, this makes this way to prominent, as if this is the desired thing to do instead of picking an exisiting label. But it is actually the other way around, isn't it? The stickyness also makes it feel weird to navigate with the arrow keys because it feels like I can actual never reach the element due to the separator but suddenly I can..
-
You are using a hover color for a non-hover state
Name the permission each schema case covers and check the case-insensitive duplicate on its own.
The joined name backs the title and aria-label of every multi-value resource field, not only labels.
|
Warning Flaky specs
🤖 Ask Copilot to investigateCopy the prompt below into a new comment on this PR to delegate the investigation to GitHub Copilot. It will look into the flakiness and open a separate pull request with you as reviewer. |
HDinger
left a comment
There was a problem hiding this comment.
Looks good overall. My biggest concern is regarding the special styling of the create option. I'd like to see that harmonized with other places we have, please see my comment below.
One other thing I found: When a label is very long, it pushes the rest out of the field. In the example below, I have two labels selected, but I only see one:
This is probably an exisiting problem which also affects other fields, so I am fine with keeping that out of scope for this PR.
| // ng-select gives the addTag row no class of its own; :has() reaches it via | ||
| // the marker element in the labels tag template. | ||
| .op-labels-autocompleter--panel | ||
| .ng-option:has(> .labels-autocompleter--create-option) | ||
| position: sticky | ||
| bottom: 0 | ||
| border-top: 1px solid var(--borderColor-default) | ||
|
|
||
| // The hover token is translucent; layering it over the body background keeps the pinned row opaque. | ||
| &.ng-option-marked | ||
| background-color: var(--body-background) !important | ||
| background-image: linear-gradient(var(--control-transparent-bgColor-hover), var(--control-transparent-bgColor-hover)) !important | ||
|
|
There was a problem hiding this comment.
Thanks for the detailed explanation. 🙇
I have to admit, that I am really not a big fan of that for several reasons:
- We already have a style for these kind of adding new elements. Look for example in the share WP dialog. Why can't we re-use the same style instead of adding again another one? The text is blue and in front of the option (instead of grey and behind it). Further, the background remains unchanged (instead of greyed).
-
A grey background ususally means disabled, which is not what you want to communicate. That somehow belongs to point 1. Further, why did you use
background-imageinstead of just setting abackground-color? -
I don't really get the necessity for making the create option sticky. Imho, this makes this way to prominent, as if this is the desired thing to do instead of picking an exisiting label. But it is actually the other way around, isn't it? The stickyness also makes it feel weird to navigate with the arrow keys because it feels like I can actual never reach the element due to the separator but suddenly I can..
-
You are using a hover color for a non-hover state
https://community.openproject.org/wp/COMMS-1048
Work packages show their labels on the full and split view, and users with
edit_work_packagesedit them in a multi-select that searches labels on the server and saves with the field's own save/cancel controls. A pinned option at the bottom of the dropdown creates a label from the typed name and adds it to the selection. The fetching lives in a dedicatedop-labels-autocompleterso other surfaces can reuse it.Screenshots
Full view, Details group
Initial dropdown order: used in this workspace, then most used, then name
Create option pinned below the results
Split view
AI involvement
Directed – I specified the requirements and AI implemented most of it; I validated via testing rather than a full line-by-line review.