fix: describe every permission, keep custom team roles on team edit, create goals from presets (#3912, #6569, #4825)
Merged
EV
14d ago+6408+6408+0123456789012345678901234567890123456789-120-120-012345678901234567890123456789
Summary
Three fixes in the web UI and the team-member service:
- #3912 Roles & Permissions now shows a short description under every permission name: 368 permissions, in all 14 languages. Search also matches the translated name and the description.
- #6569 Editing a team no longer wipes a custom team role a member was given.
- #4825 "Create from Preset" creates its own goal, KPIs and key results every time. It no longer fails for team or organization objectives, and it no longer overwrites the records an earlier use of the same preset created.
Each issue is a separate commit:
| Commit | Issue |
|---|---|
b426752aa6 | #3912 |
f733f2e961 | #6569 |
347bf885a0 | #4825 |
3655cfad92 | #6569: review fix, the manager flag is cleared even when the custom role is kept |
96fd14c00b | #4825: review fix, a KPI key result is never saved without its KPI |
55a31ca92b | #4825: review fix, the required update value starts at 0 when a template has no initial value |
3dd4cc4f0e | #4825: review fix, the preset is read once, so picking another preset mid-creation cannot mix two presets |
Why
All three were confirmed on develop (f0e9f0ac54) before any change.
- #3912. The page gave each toggle only the permission's name, and many names don't say what they allow. For example, "Change Selected Employee" really means "see and change any employee's data".
- #6569.
updateOrganizationTeamrewrote every existing member to "manager role, or no role" (isManager ? managerRole : null), so a custom team role was lost on every team edit. #6570 fixed this in 2023; a later rewrite of the method brought it back. - #4825. There were three causes:
- Template ids were reused. The dialog spread the goal template, its key-result templates and their KPI templates into its create payloads, ids included.
POST /goals,POST /goal-kpiandPOST /key-results/bulksave the id they are given (create is an upsert), so the second use of a preset rewrote the first one's records. - KPIs had no organization. KPIs were sent without an
organizationId. - Key results got a non-employee owner. The objective's owner id was sent as each key result's
ownerId. At team or organization level that is a team or organization id, butkey_result.ownerIdis a foreign key toemployee. The bulk insert failed, which is the error in the issue's screenshots.
- Template ids were reused. The dialog spread the goal template, its key-result templates and their KPI templates into its create payloads, ids included.
What changed
apps/gauzy: Roles & Permissions (#3912)
permission-texts.ts(new). Pure helpers: the i18n keys of a permission's label and description,toPermissionTexts, andmatchesPermissionSearch.roles-permissions.component.ts.- One
TranslateService.streamcall fetches every label and description. - The texts update when the language changes, and the filtered lists are rebuilt.
- While translations load, every permission is still listed.
- Search matches the code words as before, plus the translated label and the description, so results can only grow.
- A leftover
console.log(permissions)is removed.
- One
.html/.scss. The description is shown as small help text under the name, in the General and Administration groups. Nothing existing was removed or moved. The e2e page object reads.custom-permission-view strong, which is unchanged.
packages/ui-core/i18n (#3912, #4825)
ORGANIZATIONS_PAGE.PERMISSIONS.DESCRIPTIONS.<value>. Added for all 368 permissions the page lists, in every language file.- Keys are added only, with no reformatting: +370 lines per file.
- Descriptions are keyed by the enum value, like the page's existing label keys. For example,
APPROVAL_POLICY_VIEWhas the valueAPPROVALS_POLICY_VIEW. - For plugin-contributed permissions, the English text follows the
descriptioneach plugin already declares.
TOASTR.MESSAGE.KEY_RESULTS_OWNER_REQUIRED. Added in every language file: +1 line per file.
packages/core: team members (#6569)
team-manager-membership.ts. NewteamRoleAfterEdit(member, isManager, managerRole)decides the role of a member who stays on the team:- Named as a manager: gets the MANAGER role, as before.
- Any other member: keeps the role they already hold.
- Exception: if the role they hold is MANAGER or another of
TEAM_MANAGER_ROLES(ADMIN, SUPER_ADMIN), they lose it, as before. - Manager flag: a member the edit leaves out of the managers loses the
isManagerflag even when they keep a custom role, whoever edits. Before this PR, clearing the role cleared the flag with it. Setting the flag still needs CHANGE_SELECTED_EMPLOYEE.
organization-team-employee.service.ts. Step 2 of the team edit uses that helper. A row is still written only when its role (or, for a caller who sets it, its manager flag) changes.
apps/gauzy: Goals (#4825)
goal-from-template.ts(new). Builds each payload field by field: goal, KPI and key result.- No template
id, timestamps or scope reach the requests. - Every record is created in the selected organization and tenant.
- A KPI's missing
currentValuestarts at 0, and so does a key result's requiredupdatevalue when its template has no initial value.
- No template
goal-template-select.component.ts.createGoaluses the builders.- The preset's KPIs are created first, and each KPI key result is linked to its own new KPI.
- If a KPI can't be created, a warning names it, a "Goal could not be created" error follows, and no objective or key results are created. A KPI key result is never saved without its KPI, because the key-result screens read the linked KPI. KPIs created before the failure stay listed in Goal Settings, where they can be reused.
- The selected template object is no longer mutated, and it is read once when creation starts, so picking another preset meanwhile cannot mix two presets.
Decisions
These were settled for this PR:
- Ignoring a client-supplied
idon create (goals, KPIs, key results) is out of scope. Create-as-upsert is the shared base-service pattern, so it gets its own consistent change later. It is listed under follow-ups. - The key-result owner order is kept as described below: owning employee, then lead, then the current employee, else a prompt to pick a lead.
- The permission descriptions are machine-assisted translations. That includes the Hebrew term for "tenant". They stay as they are; native speakers can polish the wording later through Crowdin.
Authorization and behaviour notes
- #3912 is display only. No permission, guard, role default or API changed.
- #6569 changes no authorization.
- Who can edit a team, and who can set roles, is unchanged: no DTO, guard or permission change.
- The edit can't grant anything. It only decides whether a member keeps a role they already hold.
- Manager authority still follows the edit. A member not named as a manager loses MANAGER, and also ADMIN or SUPER_ADMIN if that was their team role. They also lose the
isManagerflag when they keep a custom role, whoever edits.
- #4825: same endpoints and permissions, smaller payloads.
- Ids are no longer sent from the client.
- I did not add server-side id stripping to the create DTOs. API clients may rely on sending an id today, and stripping it would make the API more restrictive. It is listed below as a follow-up.
- #4825: who owns the key results. The owner of key results created from a preset is chosen in this order:
- At employee level, the owning employee.
- Otherwise, the objective's lead, if one was picked.
- Otherwise, the employee creating it.
- If none of these is an employee (for example an admin user with no employee record and no lead picked), a translated toast asks for a lead, before anything is created. A preset is never left half-made.
orgId/orgNamekept. They are still on the component because callers set them through the dialog context.
Flags / settings and their defaults
None. There are no new settings, environment variables, migrations or dependencies.
Tests
| Command | Result |
|---|---|
npx jest -c apps/gauzy/jest.config.ts --rootDir apps/gauzy roles-permissions goals | 9 suites, 114 tests passed |
NODE_OPTIONS=--experimental-vm-modules npx jest -c packages/core/jest.config.ts --rootDir packages/core organization-team-employee organization-team | 16 suites, 266 tests passed |
npx tsc -p packages/core/tsconfig.lib.json --noEmit | clean |
Type-check of the changed app files and their specs against apps/gauzy/tsconfig.spec.json | no errors in apps/gauzy |
| ESLint on the changed files | no findings on new or changed lines |
The ESLint findings that remain are on untouched lines.
New or extended specs:
- #3912
permission-texts.spec.tsroles-permissions.component.descriptions.spec.ts: one lookup, re-filters on a language switch, code-word search kept, everything listed while translations load.permission-i18n.spec.ts: every language describes every listed permission, with no empty text and keys by value only, and English descriptions don't repeat the label.
- #6569
team-manager-membership.spec.tsorganization-team-employee.service.spec.ts("custom team roles"): custom role kept with no write, flag cleared for editors with and without CHANGE_SELECTED_EMPLOYEE, promotion, demotion, ADMIN or SUPER_ADMIN team role stripped, unchanged team writes nothing.
- #4825
goal-from-template.spec.tsgoal-template-select.component.spec.ts: no template id in any payload, KPI in the selected organization and linked, a second use creates new records and leaves the template as it was, owner rule, block toast, a KPI failure creates nothing more, a preset switched mid-creation does not mix in.
Manual verification still needed
- Roles & Permissions in a browser:
- Layout of the descriptions in the toggle grid at a few widths and in RTL (ar, he).
- Switching language updates the descriptions and the search.
- Create from Preset in a running app:
- Use the same preset twice, at employee, team and organization level, with and without a lead, including a preset with a KPI key result.
- Check that the first goal's key results are left untouched.
- Team edit: a member holding a custom team role keeps it after the team's name or members are edited.
- Translations: machine-assisted, then validated for coverage, placeholders and structure. Native speakers can polish the wording through Crowdin.
Risk and rollback
- Risk is low. The core change is one function, used in one step of the team edit. The UI changes are confined to two pages.
- i18n size. Each language file gains about 26–33 KB of description text.
- Rollback. Each issue is its own commit and can be reverted alone. No migration is involved.
Follow-ups suggested
- Ignore client-supplied ids on create, as one change across the shared base service. Make the create endpoints (goals, KPIs and key results among them) ignore a client-supplied
id, in a single consistent change to the shared base service.
Closes #3912
Closes #6569
Closes #4825
🤖 Generated with Claude Code