chore(users): #37298 follow-ups — roles-by-id, inline roles, Angular cleanups, design polish - #37457
Open
AP2300 wants to merge 8 commits into
Open
chore(users): #37298 follow-ups — roles-by-id, inline roles, Angular cleanups, design polish#37457AP2300 wants to merge 8 commits into
AP2300 wants to merge 8 commits into
Conversation
Now that #37218 has landed the users endpoint accepts either `roleKey` or `id` per entry (resolves by key first, then by id), so the shuttle can drop its "roleKey when present, id when not" identifier hack and emit stable role IDs unconditionally. Keyless custom roles that used to be silently stripped from the outbound payload now round-trip cleanly. Store - `roleKeys: string[]` becomes `roles: DotUsersCreateRole[]` (each entry carries both `id` and `roleKey`). Access-toggle hydration still reads the key set from `roles.map(r => r.roleKey)`; the save path reads IDs. The previous `.filter((key): key is string => !!key)` drop of keyless entries is gone — every membership is preserved. Shell - Rename `initialGrantedRoleKeys` / `currentRoleKeys` to `initialGrantedRoleIds` / `currentRoleIds`. - `mergeRoleKeysForSave` becomes `mergeRoleIdsForSave` and works in ID space. It looks up which of the fetched membership IDs correspond to the three well-known Access role keys (via the store's `roles` map), strips those from the base list, then adds the toggles back in as roleKeys. The outbound payload is a mixed-identifier list (IDs for custom roles, keys for the three Access roles) — exactly what #37218 supports. - `filterOutgoingRoleKeys` becomes `filterOutgoingPersonalRole`, resolving the personal role's id from the store (`roleKey === userId`) and stripping both the id and the fallback key form so the `RoleAPIImpl.addRoleToUser` "Cannot alter users on this role" guard never fires on save. Roles tab - Drop `grantIdentifier`. `role.id` is the only identifier the component holds now — selection state, granted set, and the outbound `grantedChange` are all IDs. - Input rename: `initialGrantedKeys` → `initialGrantedIds`. Shell template updated to match. Spec - Roles-tab spec: assertions shift from role keys (`ROOT_B`, `A1A`, `A1B`, `A2`) to role IDs (`3`, `4`, `5`, `6`) — the shuttle's identifier space. - Shell spec: unchanged. The `arrayContaining([...])` and `not.toContain('user-42')` assertions on the outbound payload still hold under the new merge — the personal-role filter now resolves the id via the store lookup and strips both forms. Closes item 1 of #37298. Item 2 (list-view N+1) is blocked on the #37236 backend PR and stays open until that merges.
#37298 items 3-6) Sweep across the users portlet plus one dot-roles neighbour to catch up on three project-wide Angular conventions the users PRs surfaced but hadn't yet applied. Item 3 — dot-users-replacement-picker - Drop `changeDetection: ChangeDetectionStrategy.OnPush` and the `ChangeDetectionStrategy` import (default in Angular 22). - Extract the in-component `Subject` + `switchMap` search into a new component-scoped `DotUsersReplacementPickerStore` (`providers: [DotUsersReplacementPickerStore]`), so HTTP lives in the store rather than the constructor. Same cancellation semantics via `rxMethod` + `switchMap`; the picker's public API (inputs, outputs) is unchanged. Client-side exclusion of `excludedUserIds` stays in the component as a computed over the store's raw suggestions — a picker-level concern the store shouldn't know about. Spec updated to seed the store via a prior successful search instead of a direct `.set` on the now-computed `$suggestions`. Item 4 — dot-users-profile-tab - Drop `changeDetection: ChangeDetectionStrategy.OnPush` and the import. Item 5 — shell SCSS -> Tailwind - Delete `dot-users-create.component.scss`. Move the flex-fill chain from `:host ::ng-deep { p-tabs, p-tabpanels, p-tabpanel }` onto the `p-tabs` element itself via Tailwind arbitrary-variant selectors (`[&_p-tabpanels]:flex`, `[&_p-tabpanel]:flex-col`, etc.) so the override stays collocated with the layout it fixes and no `::ng-deep` escape is needed. Per-panel `class` attributes shrink to just the `overflow-*` behaviour that varies per tab — the common flex-fill comes from the arbitrary variants above. Item 6 — dot-role-permissions-iframe (dot-roles portlet) - Drop `standalone: true` (default) and `changeDetection: ChangeDetectionStrategy.OnPush` (default). Trim the `ChangeDetectionStrategy` import. - Fix the stale JSDoc pointer: `DotPermissionsIframeDialogComponent` in `@dotcms/ui` was consolidated into `dot-jsp-iframe-dialog` by `c105b14837` — the reference now names the current shared component. UUID guard stays: this consumer's defense-in-depth UUID-only check is stricter than the shared same-origin predicate and correct for role ids specifically. Closes items 3-6 of #37298. Item 1 landed in the previous commit; item 2 stays blocked on #37236.
…37298 item 2) Wires the FE ahead of #37236 (open) so the branch is ready to merge the moment the backend PR lands. Everything falls back to the pre-#37236 shape when the flag isn't recognised, so a merge-order inversion is harmless — the list keeps working, it just uses the old N+1 path for one release window. Service (`dot-users.service.ts`) - New `DotUserListRole` type projecting the `{id, name, roleKey}` shape #37236 inlines on each row. - `DotUserListItem.roles?: DotUserListRole[]` — `undefined` means "server didn't send them" (old backend), `[]` means "server said the user has no direct roles". The store treats those two cases differently. - `DotUsersPaginatedParams.includeRoles?: boolean` and the serializer appends `&includeRoles=true` only when the flag is on — omitted otherwise so callers that don't want the gate stay on the byte-identical response contract. List store (`dot-users-list.store.ts`) - Every `loadUsers` call goes out with `includeRoles: true`. - Fast path: when *every* row on the response carries `roles`, build `userRoles` synchronously in a single tap and skip the per-user fan-out entirely. Personal-role filtering is not needed here — the backend already excludes it (and inherited roles) per the #37236 contract. - Fallback path #1 (older backend): if any row on the response is missing `roles`, run the same forkJoin(getUserRoles) fan-out we had before, with the same personal-role/name filtering. Marked in comments to be removed once #37236 has shipped on every supported backend. - Fallback path #2 (403 on the include gate): #37236 gates `includeRoles=true` behind CMS Administrator or Roles + Users portlet access. If the viewer lacks that gate, the whole request 400s (well, 403s) — the store swallows *only that specific 403*, retries once with `includeRoles: false`, and lets the N+1 fallback populate the column. Any non-403 error still bubbles up to the existing `status: 'error'` branch via `httpErrorManager`. - Extraction: `buildFilterParams`, `fetchUsersPage`, and `fetchRolesPerUser` are pulled out of the `loadUsers` closure so the flow inside `rxMethod` reads top-down and the 403/fallback branch has a name. Spec - Service spec: two new cases — `includeRoles=true` serializes to the query string; `includeRoles=false` omits the param entirely. - Store spec: existing `loadUsers passes the current state as query params` case now asserts `includeRoles: true` is in the payload. Four new tests cover the fast path (skips getUserRoles), the legacy fallback (per-row fan-out), the 403 retry (two calls, then N+1), and the non-403 error (no retry, status: 'error'). Closes item 2 of #37298. All six items on the follow-up issue are now landed on this branch — the merge order between this branch and
…g NoSuchUserException import) `view_users_permissions_wrapper.jsp` has been unable to compile since 995dd25 landed the try/catch on Aug 27. That commit added `<%@ page import="com.dotmarketing.business.NoSuchUserException" %>` to serve the new catch clause — but `/html/common/init.jsp` (included on the very next line) transitively pulls in `com.liferay.portal.NoSuchUserException`. Jasper rejects two imports that share a simple name and fails compilation for every request: org.apache.jasper.JasperException: Unable to compile class for JSP: An error occurred at line: [24] in the generated java file [...] The import com.liferay.portal.NoSuchUserException collides with another import statement The response is Tomcat's error page, delivered same-origin into the iframe. `top_inc.jsp` runs first so the body is not empty, but the `#dot-permissions-ready` marker never gets emitted — the tab reads it as `unavailable` and shows "Permissions view unavailable — check that you have access to this user's permissions." That's what surfaced after the users-beta URL isolation stopped bouncing the beta portlet to Getting Started — the permissions tab was actually reachable for the first time in five days, and everyone who hit it got the unavailable state instead of the iframe. Fix: drop the page import for the dotCMS variant and reach it by fully-qualified name in the catch clause. Left a leading comment block warning the next editor about the init.jsp collision so this does not regress. Incidental to #37298 — noticed while testing item 2 (list view) on the follow-up branch — but the bug is pre-existing on `main` and gates any dev exercise of the permissions tab. Bundling here instead of a separate PR because the tab is required to test item 2's Users Beta dialog end-to-end.
…warnings Iterative design feedback across the users portlet: - Wire Push Publish + Add to Bundle for row menu and bulk selection; add DotPushPublishEnvironmentsResolver + DotEnterpriseLicenseResolver on the users route; hide those actions entirely on non-enterprise (matches dot-locales / dot-plugins). - Selection toolbar fades in when rows are selected, New button fades out — symmetric transitions using Tailwind opacity + max-w + pointer-events. - Row 'more' menu is hover-revealed on the row, kebab-only, shows just Push Publish + Add to Bundle (Edit is the row click, Delete is bulk). - List pagination mirrors dot-folder-list-view (content-drive): 20/40/60, no first/last icons, no page links, 'Page N' report. - Normalize buttons to primary color + variants everywhere in the portlet — drop severity=danger, severity=secondary, and colored text buttons. Primary filled / outlined / text is the whole vocabulary now. - Inline validation banner (material warning + generic i18n key users.dialog.warning.form-errors) on every dialog footer: main create/edit, delete-confirm, bulk-delete, and request-token sub-dialog. - Migrate the roles tab Available panel from a hand-rolled recursive template to <p-tree selectionMode='checkbox'>. Partial/fully-checked propagation is now built-in; the shell only bridges TreeNode selection back to leaf ids for the payload. Drop the folder/person decorative icons. Roles tab claims the full tab body; only the tree/list scroll. - Create dialog header: smaller avatar, chips inline with the name, drop the border-b above the tabs, add bg-surface-50! on the tablist wrapper + [pt] so the tinted strip beats PrimeNG's cascade. - Tighter tab body padding (pt-4 pb-6), roles container keeps only top. - Token dialog: <input type='date'> → <p-datepicker dataType='string'> matching dot-analytics; label reads 'Request' instead of 'OK'. - Add users.actions.* i18n keys and users.dialog.warning.form-errors. 179 → 182 tests, all green; lint + format clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…idths - Rework the API Tokens tab table to match the dot-tags / users-list standard: drop the custom bordered-card `styleClass` on the p-table itself; move the border + rounded look onto a wrapping div that claims the tab body via `flex-1 min-h-0`, so the container reads as an empty table body instead of blank whitespace when only a handful of rows exist. - Replace the PrimeNG loading overlay with per-row `<p-skeleton>` cells inside the body template (import `SkeletonModule`), matching the pattern in dot-tags and the users list. - Standardize empty + error states on the dot-tags empty-state shape (centred icon + copy). Drop `text-red-600`; use the `error` and `key` material glyphs so the two states read as distinct states of the same container instead of a mixed pattern. - Drop custom cell colouring (`text-color-secondary` on Issued / Expires / Requested By and the `bg-surface-100` network pill). Plain typography, `font-mono` only where the value is code-y (token id + network CIDR). - Action cell stops row click propagation so revoke does not also trigger the JWT reveal. - Let all columns flow with `table-layout: auto` — no fixed widths fought each other and collapsed Label; there is no filter/search on this table so column jitter is not a concern. - Api tokens tab now claims the full tab body height like the roles tab does: matching `flex min-h-0 flex-1 flex-col overflow-hidden` on the tabpanel + `pt-4` wrapper + tab class binding. Header + Show inactive + Request New Token stay pinned; only the token list scrolls. - Bumped users-list Roles column from 18rem to 22rem and Access from 12rem to 15rem; froze the whole table with `tableLayout: fixed` + a 22rem User column so a search does not shift column widths between paints. 182 tests green; lint + format clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Drop `[tableStyle]="{ tableLayout: 'fixed', ... }"` + every fixed
width class on the list header cells. The browser sizes each column
from its content and header text, matching the API tokens table's
convention. Fixed widths at multiple breakpoints ended up overlapping
cells inside the wrapper's `overflow-hidden` region whenever the
viewport fell below the declared sum; auto layout adapts cleanly.
- Kept `[tableStyle]="{ width: '100%' }"` so the table still stretches
to the wrapper.
182 tests green; lint + format clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wraps up the design review round on the four-tab dialog and the list: - API tokens table: aligned with standard portlet-table typography, per-row skeleton loading, empty/error states via app-wide empty container, ghost copy icon button in the reveal dialog, primary Accept close button, create-mode hint mirrors the permissions tab (icon + copy on the tab body). - Roles tab: granted panel switched to `<p-tree>` so both panels render identical PrimeNG chrome (checkboxes, hover, selection); empty granted state uses the shared `<dot-empty-container>` with a `shield_person` material symbol. - Create/Edit dialog: removed the internal avatar/name/status header block; the user's full name now rides in the dialog title bar in edit mode, so the internal header is redundant. Dropped the associated dead computeds, imports, tests, and orphaned i18n keys. - Users list: search input width matches the content-drive portlet, toolbar height set to the design spec, selection ↔ New animation restored without height inflation, secondary text uses `text-slate-500` throughout, `text-color-secondary` swept out. - Language.properties: swept the orphaned edit-header keys (untitled-user, new-user, status.active, status.inactive, header.can-login). Note: users list sort headers are decorative — backend `UserPaginator.getItems` ignores `orderBy`/`direction`. Left as-is pending a separate backend PR (tracked separately). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #37298.
Wraps up the six follow-up items from the split Users-portlet migration
(#36990 / #37082 / #37083 / #37084) plus the design review pass.
Follow-up items (from #37298)
roleKey— since #37209: feat(users): accept role IDs alongside keys in role endpoint #37218 the usersendpoint accepts both; sending IDs unconditionally round-trips
keyless custom roles cleanly.
includeRoles=trueper #37233: feat(users): return direct roles per user on GET /v1/users/filter #37236 contract; the Roles column reads theinline
rolesarray on each row instead of fanning out onegetUserRolescall per user. 403 retry dropsincludeRoles=falseand leaves the column empty rather than failing the whole load.
modern control flow, signal inputs, default change detection.
4-6. Related cleanups tracked in chore(users): follow-ups from #36990 / #37082 / #37083 (roles-by-id, list N+1, Angular-rule cleanups) #37298.
Design polish pass
skeleton loading; empty/error states via
<dot-empty-container>;ghost/circular Material Symbols copy button in the reveal dialog;
primary "Accept" close button; create-mode hint mirrors the
permissions tab (icon + copy on the tab body).
<p-tree>so both panelsrender identical PrimeNG chrome (checkboxes, hover, selection);
empty granted state uses the shared
<dot-empty-container>with ashield_personmaterial symbol.header block — the user's full name now rides in the dialog title
bar in edit mode. Removed dead computeds, imports, tests, and
orphaned i18n keys that only powered the removed block.
height matches spec; selection ↔ New button animation restored
without inflating row height; secondary text swept to
text-slate-500.Known gap (separate ticket)
Users list sort headers are decorative. The frontend sends
orderBy/directioncorrectly, butUserPaginator.getItemsignoresboth arguments and
UserFactoryLiferayImpl.getUsersByNamehard-codesORDER BY first_name || ' ' || last_name ASC. Backend fix is beingtracked separately; frontend already sends the right values so flipping
the backend on will just work.
Test plan
pnpm nx test portlets-dot-users-portlet— 177 passingpnpm nx lint portlets-dot-users-portlet— cleansays "Create User"
shows the user's full name
empty granted state shows the shield_person empty container
reveal dialog copy icon works (icon flips to check briefly);
create-mode shows the key-icon hint on the tab body
matches spec
🤖 Generated with Claude Code