Skip to content

refactor(ui): point invitation links at the dedicated /onboarding route - #30857

Merged
ryan-crabbe-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_repoint_invitation_onboarding
Jul 9, 2026
Merged

refactor(ui): point invitation links at the dedicated /onboarding route#30857
ryan-crabbe-berri merged 2 commits into
litellm_internal_stagingfrom
litellm_repoint_invitation_onboarding

Conversation

@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Refs LIT-3687

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all unit tests on make test-unit
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have requested a Greptile review by commenting @greptileai and received a Confidence Score of at least 4/5 before requesting a maintainer review

CI (LiteLLM team)

  • Branch creation CI run
    Link:

  • CI run for the last commit
    Link:

  • Merge / cherry-pick CI run
    Links:

Screenshots / Proof of Fix

This is a UI routing change with no LLM calls, so the proof is the onboarding flow itself against a live proxy. Run the proxy, then walk the suite below. Steps that need a raw invitation_id can mint one with curl; the rest are UI clicks. Use an Incognito window for every "open the link" step, because an authenticated session masks the login gate that the invitation flow is supposed to bypass.

Setup:

python litellm/proxy/proxy_cli.py --config litellm/proxy/dev_config.yaml --detailed_debug --reload --use_v2_migration_resolver 2>&1 | tee litellm.log
MASTER_KEY="<your master key from .env>"

Mint a user + invitation when a step needs a raw id:

USER_ID=$(curl -s -X POST http://localhost:4000/user/new \
  -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' \
  -d '{"user_email":"invitee-local@example.com","user_role":"internal_user"}' | jq -r .user_id)
INVITE_ID=$(curl -s -X POST http://localhost:4000/invitation/new \
  -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' \
  -d "{\"user_id\":\"$USER_ID\"}" | jq -r .id)
echo "new format : http://localhost:4000/ui/onboarding?invitation_id=$INVITE_ID"
echo "old format : http://localhost:4000/ui/?invitation_id=$INVITE_ID"

A. New invitation link uses the dedicated route (UI)

  1. Go to http://localhost:4000/ui/?page=users (Internal Users) with SSO disabled, click + Invite User, submit. The invitation modal link must read .../ui/onboarding?invitation_id=... and no longer .../ui?invitation_id=...
  2. Repeat via Bulk Create; confirm the generated invitation_link in the results table is also the /ui/onboarding form

B. New link works end to end (Incognito)
3. Open the new format URL printed above. Expect: the set-password form renders, no sidebar/navbar, and no redirect to /ui/login
4. Set a password and submit. Expect a redirect to .../ui/?login=success
5. Log in as invitee-local@example.com with that password; you should land in the dashboard

C. Reset-password variant
6. For an existing user, generate a reset-password link (Internal Users -> the user -> reset password). Confirm the link is .../ui/onboarding?invitation_id=...&action=reset_password
7. Open it in Incognito; the form header should read "Reset Password" (the reset variant), not "Sign Up"

D. Backward compatibility for already-sent emails
8. Open the old format URL (/ui/?invitation_id=...) in Incognito. It must still render the onboarding form inline with no login bounce, so links already delivered to users keep working

E. SSO and server_root_path (optional)
9. With an SSO-enabled user, the invitation modal link should point at /ui (the SSO login entry), not /ui/onboarding
10. If you run the proxy under SERVER_ROOT_PATH=/litellm, the modal link should preserve the prefix: .../litellm/ui/onboarding?invitation_id=...

Negative control: open http://localhost:4000/ui/ in Incognito with no invitation_id and no cookie; it should bounce to login, confirming the gate only opens for invitations.

Type

🧹 Refactoring

Changes

Invitation and reset-password links were generated as /ui?invitation_id=..., which lands on the dashboard index ((dashboard)/page.tsx) and renders the onboarding form inline through UserDashboard. This points all three generators at the standalone /ui/onboarding route that already serves that same form, so the index no longer has to special-case invitation_id. It is the first step of the App Router migration closeout (decoupling onboarding from the index); the index keeps rendering onboarding inline for old ?invitation_id links, so nothing already delivered to a user breaks. Removing that inline branch and adding a forwarding redirect is a later closeout step.

Generators updated: the enterprise email builder (base_email.py:_construct_invitation_link), bulk user create (bulk_create_users_button.tsx), and the invitation/reset-password modal (onboarding_link.tsx). The modal's URL building is extracted into a pure buildOnboardingUrl so the server_root_path prefix, the reset_password action, and the SSO branch are covered by a focused unit test. The seven backend assertions that pinned the old /ui?invitation_id= string are updated to the new format, locking it in as a regression anchor.

Invitation and reset-password links were built as /ui?invitation_id=..., which lands on the dashboard index and renders the onboarding form inline. They now point at the standalone /ui/onboarding route, so the index no longer has to special-case invitations. Old links keep working unchanged; the index still renders onboarding inline for ?invitation_id until the migration closeout removes that branch.

Updates the three generators (the enterprise email builder, bulk user create, and the invitation/reset-password modal) and extracts the modal's URL building into a pure, unit-tested buildOnboardingUrl

Refs LIT-3687
@greptile-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR updates all three invitation/reset-password link generators to point at the dedicated /ui/onboarding route instead of the dashboard index (/ui?invitation_id=...), and extracts the URL-building logic in onboarding_link.tsx into a testable buildOnboardingUrl pure function. Old links remain functional because the index still renders the onboarding form inline for backward compatibility.

  • Backend (base_email.py): single-line change to _construct_invitation_link; all affected test assertions updated to the new URL format.
  • Frontend (bulk_create_users_button.tsx, onboarding_link.tsx): URL path updated in bulk-create flow; modal URL builder refactored into buildOnboardingUrl with guards for missing baseUrl and undefined invitation ID, covered by six new unit tests.

Confidence Score: 5/5

Safe to merge — all three link generators are updated consistently, old links remain functional, and the new URL construction logic is verified by dedicated unit tests.

The change is narrow and mechanical: a path segment is replaced in three places, an existing inline function is extracted and made testable, and every backend assertion that pinned the old format is updated in lock-step. The backward-compatibility guarantee (index still renders the onboarding form for old links) is preserved by design. No auth paths, data-handling logic, or critical request paths are touched.

No files require special attention.

Important Files Changed

Filename Overview
enterprise/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py Single-line URL change: /ui?invitation_id=/ui/onboarding?invitation_id= in _construct_invitation_link; straightforward and correct.
tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_base_email.py Seven assertion strings updated from the old /ui?invitation_id= format to the new /ui/onboarding?invitation_id= format; one mock return value updated in parallel — all changes correctly track the new intended behavior without weakening coverage.
ui/litellm-dashboard/src/components/bulk_create_users_button.tsx One-line URL path update from /ui?invitation_id= to /ui/onboarding?invitation_id= in the bulk-create invitation URL builder.
ui/litellm-dashboard/src/components/onboarding_link.tsx Extracts the URL-building logic into an exported buildOnboardingUrl pure function (with guards for missing baseUrl and undefined invitationId) and delegates getInvitationUrl to it; the routing path changes from /ui to /ui/onboarding for non-SSO users.
ui/litellm-dashboard/src/components/onboarding_link.test.tsx New test file covering all key branches of buildOnboardingUrl: basic case, server_root_path prefix, reset_password action, SSO redirect, empty baseUrl, and undefined invitationId guard.

Reviews (2): Last reviewed commit: "refactor(ui): guard buildOnboardingUrl a..." | Re-trigger Greptile

Comment thread ui/litellm-dashboard/src/components/onboarding_link.tsx
@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Return "" instead of emitting an invitation_id=undefined link when the id is not yet available, matching the existing empty-baseUrl guard. Placed after the SSO branch so the SSO link, which does not use the id, is unaffected

Refs LIT-3687
@ryan-crabbe-berri

Copy link
Copy Markdown
Contributor Author

@greptileai re review

@ryan-crabbe-berri
ryan-crabbe-berri merged commit febb276 into litellm_internal_staging Jul 9, 2026
120 of 121 checks passed
@ryan-crabbe-berri
ryan-crabbe-berri deleted the litellm_repoint_invitation_onboarding branch July 9, 2026 02:47
edelauna pushed a commit to edelauna/litellm that referenced this pull request Jul 22, 2026
…te (BerriAI#30857)

* refactor(ui): point invitation links at the dedicated /onboarding route

Invitation and reset-password links were built as /ui?invitation_id=..., which lands on the dashboard index and renders the onboarding form inline. They now point at the standalone /ui/onboarding route, so the index no longer has to special-case invitations. Old links keep working unchanged; the index still renders onboarding inline for ?invitation_id until the migration closeout removes that branch.

Updates the three generators (the enterprise email builder, bulk user create, and the invitation/reset-password modal) and extracts the modal's URL building into a pure, unit-tested buildOnboardingUrl

Refs LIT-3687

* refactor(ui): guard buildOnboardingUrl against a missing invitation id

Return "" instead of emitting an invitation_id=undefined link when the id is not yet available, matching the existing empty-baseUrl guard. Placed after the SSO branch so the SSO link, which does not use the id, is unaffected

Refs LIT-3687
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants