fix: describe every permission, keep custom team roles on team edit, create goals from presets (#3912, #6569, #4825)

Merged
EV
14d ago
+6408-120

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:

CommitIssue
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. updateOrganizationTeam rewrote 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:
    1. 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-kpi and POST /key-results/bulk save the id they are given (create is an upsert), so the second use of a preset rewrote the first one's records.
    2. KPIs had no organization. KPIs were sent without an organizationId.
    3. 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, but key_result.ownerId is a foreign key to employee. The bulk insert failed, which is the error in the issue's screenshots.

What changed

apps/gauzy: Roles & Permissions (#3912)

  • permission-texts.ts (new). Pure helpers: the i18n keys of a permission's label and description, toPermissionTexts, and matchesPermissionSearch.
  • roles-permissions.component.ts.
    • One TranslateService.stream call 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.
  • .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_VIEW has the value APPROVALS_POLICY_VIEW.
    • For plugin-contributed permissions, the English text follows the description each 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. New teamRoleAfterEdit(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 isManager flag 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 currentValue starts at 0, and so does a key result's required update value when its template has no initial value.
  • goal-template-select.component.ts.
    • createGoal uses 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:

  1. Ignoring a client-supplied id on 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.
  2. 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.
  3. 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 isManager flag 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:
    1. At employee level, the owning employee.
    2. Otherwise, the objective's lead, if one was picked.
    3. Otherwise, the employee creating it.
    4. 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 / orgName kept. 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

CommandResult
npx jest -c apps/gauzy/jest.config.ts --rootDir apps/gauzy roles-permissions goals9 suites, 114 tests passed
NODE_OPTIONS=--experimental-vm-modules npx jest -c packages/core/jest.config.ts --rootDir packages/core organization-team-employee organization-team16 suites, 266 tests passed
npx tsc -p packages/core/tsconfig.lib.json --noEmitclean
Type-check of the changed app files and their specs against apps/gauzy/tsconfig.spec.jsonno errors in apps/gauzy
ESLint on the changed filesno findings on new or changed lines

The ESLint findings that remain are on untouched lines.

New or extended specs:

  • #3912
    • permission-texts.spec.ts
    • roles-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.ts
    • organization-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.ts
    • goal-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