Skip to content

feat(my-org): wire invitation details modal to invitation roles endpoint [2/2] - #457

Merged
grandmaester merged 4 commits into
feat/my-org-ea-branchfrom
feat/invitation-roles-api-integration
Aug 14, 2026
Merged

feat(my-org): wire invitation details modal to invitation roles endpoint [2/2]#457
grandmaester merged 4 commits into
feat/my-org-ea-branchfrom
feat/invitation-roles-api-integration

Conversation

@grandmaester

@grandmaester grandmaester commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Wires the invitation details modal to GET /my-org/member-invitations/{invitation_id}/roles, replacing the tenant-wide roles fetch and client-side intersection that resolved role names before.

This is part 2 of 2 and targets feat/invitation-roles-api-setup (#456). Merge this into #456 first, then #456 into feat/my-org-ea-branch.

Why

Opening the details modal used to fetch up to 100 tenant roles (MAX_ROLES_AVAILABLE_FOR_ASSIGNMENT) and intersect them against the role IDs on the invitation, purely to turn IDs into display names. Two consequences:

  • Silent truncation. Any role outside the first 100 could not be resolved, so the modal rendered the raw role ID — role_a1b2c3 instead of Billing Admin — with nothing signalling that the lookup had failed rather than that the name was genuinely missing.
  • Wasted work. ~100 roles fetched per open to display the one or two actually assigned.

The new endpoint returns the invitation's roles directly, so the intersection disappears and the cap becomes irrelevant.

What

Service queryrolesQuery (bulk organization.roles.list({ take: 100 })) is replaced by a scoped, gated query:

const invitationRolesQuery = useQuery({
  queryKey: memberManagementQueryKeys.invitationRoles(invitationRolesId ?? ''),
  queryFn: async () => {
    const response = await coreClient!
      .getMyOrganizationApiClient()
      .organization.invitations.roles.list(invitationRolesId!);
    return response.roles ?? [];
  },
  enabled: !!coreClient && !!invitationRolesId,
});

invitationRolesId is both the request parameter and the enablement gate, so no request fires unless a details modal is open. The ?? '' fallback exists because queryKey is evaluated eagerly on every render even while disabled; the ! in queryFn is safe because queryFn only runs once enabled is true.

Parent hook — derives the ID from modal state and exposes the results:

const invitationRolesId =
  modalState.type === 'details' ? (modalState.invitation.id ?? null) : null;

Hidden coupling fixed — the assign-roles optimistic update read the now-deleted bulk roles cache to build its new-roles list. It is repointed at the role search cache, which is where the assign modal's options actually come from:

const searchedRoles =
  queryClient.getQueryData<Role[]>(
    memberManagementQueryKeys.rolesSearch(debouncedRoleSearchTerm),
  ) ?? [];
const newRoles = searchedRoles.filter((r) => roleIds.includes(r.id));

Without this the optimistic update would have silently produced an empty list after the bulk query was removed.

Modal rendering — the 9-line intersection memo collapses to roles.map((role) => role.name), and the Roles field gains a loading branch: spinner while fetching, chips when roles exist, - when empty.

Error handling — failures surface through the shared useQueryErrorToast hook with the invitation.error.fetch_roles_failed fallback, matching the existing /member/{user_id}/roles behaviour as requested in review. This replaced a hand-rolled useEffect + ref that turned out to be byte-for-byte identical to that existing hook.

Cleanup — removes the dead enableRolesList: false option from use-member-detail-service, which only existed to suppress the bulk fetch.

Packages

  • packages/core
  • packages/react
  • examples

References

Testing

packages/react: 111 files, 1729 tests passing. tsc --noEmit and ESLint clean on every file in this PR.

New and updated coverage:

use-member-management-service.test.ts — the rolesQuery block becomes invitationRolesQuery:

  • fetches and calls invitations.roles.list('uinv_1') with the correct ID
  • no bulk tenant-roles fetch happens — asserts every roles.list call is still a DEFAULT_ROLES_PAGE_SIZE search page, which is the regression guard for the truncation bug
  • stays idle and never calls the endpoint when invitationRolesId is null
  • caches per invitation ID (uinv_1 populated, uinv_2 untouched)
  • defaults to [] when the response omits roles
  • surfaces isError on failure

use-organization-member-management.test.ts (new file — this hook had no coverage at all, and it owns the error handling):

  • invitationRolesId is null until a details modal opens, then becomes the invitation's ID
  • exposes invitationRoles / isFetchingInvitationRoles, defaulting to []
  • fires exactly one error toast per failure, none on success, and fires again only after a recovery-then-refail — pinning the one-shot guard semantics

organization-invitation-details-modal.test.tsx — rewritten for the roles prop, plus an isLoadingRoles spinner test and a tightened empty-state assertion.

Intentionally not covered, and why:

  • The "role ID as fallback" test was deleted, not ported. With the endpoint returning full role objects there is no ID-to-name resolution left to fall back from. It is replaced by a test that roles absent from the response simply are not rendered.
  • No view-level (OrganizationMemberManagementView) prop-forwarding tests. Per review, this is being covered from the parent component instead once the latest changes are pulled from main.
  • fr.json locale. See feat(my-org): invitation roles API types, constants and locales [1/2] #456 — the file has no member_management.invitation block at all, so this key is deliberately absent rather than stranded alone.

One pre-existing failure unrelated to this work: packages/react/src/components/ui/dropdown-menu.tsx fails typecheck on a Radix align union mismatch (Content vs SubContent props). Present on the base branch too; being resolved by taking upstream from main.

  • This change adds unit test coverage
  • Tested for both SPA and RWA flows, all example apps working
  • All existing and new tests complete without errors

Checklist

  • Breaking change
  • Requires docs update
  • Backward compatible

The breaking public-API change (the useOrganizationMemberManagement return type) is declared in #456, which carries the type edits. This PR only updates implementations to match.

Contributing

Summary by CodeRabbit

  • New Features

    • Invitation details now load and display roles specific to the selected invitation.
    • A loading indicator appears while invitation roles are being fetched.
    • Empty role assignments are shown with a dash.
  • Bug Fixes

    • Role names now resolve correctly, while unavailable roles are omitted.
    • Role-loading errors are surfaced through error notifications.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The member-management service now fetches roles for the selected invitation. The organization hook exposes those roles and loading state. The invitation details modal renders resolved role names, a loading spinner, or a dash when no roles are assigned.

Changes

Invitation role retrieval and display

Layer / File(s) Summary
Invitation role query service
packages/react/src/hooks/my-organization/shared/services/*, packages/react/src/hooks/my-organization/__tests__/use-member-management-service.test.ts
The service replaces the general roles query with an invitation-specific query keyed by invitationRolesId. Tests cover fetching, pagination, caching, disabled states, empty responses, errors, and role-search behavior.
Organization hook wiring
packages/react/src/hooks/my-organization/use-organization-member-management.ts, packages/react/src/hooks/my-organization/__tests__/use-organization-member-management.test.ts, packages/react/src/tests/utils/__mocks__/core/core-client.mocks.ts
The hook requests roles for the selected invitation, exposes invitationRoles and isFetchingInvitationRoles, and reports query errors. Tests cover modal state, returned data, loading, empty results, and error recovery.
Invitation details role rendering
packages/react/src/components/auth0/my-organization/shared/member-management/invitations/invitation-details/*, packages/react/src/tests/utils/__mocks__/my-organization/member-management/invitation.mocks.ts
The modal accepts roles and isLoadingRoles, renders role names from fetched role objects, shows a spinner during loading, omits unavailable roles, and shows a dash for empty results.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: chakrihacker, rax7389

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: connecting the invitation details modal to the invitation-specific roles endpoint.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/invitation-roles-api-integration

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rax7389

rax7389 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Base automatically changed from feat/invitation-roles-api-setup to feat/my-org-ea-branch August 14, 2026 06:10
@grandmaester
grandmaester merged commit f0ff5ed into feat/my-org-ea-branch Aug 14, 2026
2 checks passed
@grandmaester
grandmaester deleted the feat/invitation-roles-api-integration branch August 14, 2026 06:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants