Skip to content

refactor(ui): migrate access-groups, vector-stores, organizations to shadcn - #34466

Merged
yuneng-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_/sleepy-pascal-0e7ee6
Jul 24, 2026
Merged

refactor(ui): migrate access-groups, vector-stores, organizations to shadcn#34466
yuneng-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_/sleepy-pascal-0e7ee6

Conversation

@yuneng-berri

@yuneng-berri yuneng-berri commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Three admin pages still render antd and Tremor
  • Their tests asserted on antd class names
  • Tremor tabs hid a real test blind spot

How it solves it:

  • Migrates 9 route-exclusive files onto shadcn primitives
  • Rewrites the antd-coupled assertions first, in a separate commit
  • Adds the missing VectorStoreTester characterisation test

Relevant issues

Linear ticket

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 CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

This is markup-only, so the proof is that each page still behaves identically while rendering shadcn primitives. Steps to check by hand against a local proxy on localhost:4000 with the dashboard dev server on localhost:3000:

  1. Go to http://localhost:4000/ui/?page=access-groups and confirm the header, the "Create Access Group" button and the search box render; type into the search box and confirm the table filters, then click the clear (x) button and confirm the full list returns
  2. Click an access group ID to open the detail view; confirm the back arrow returns to the list, the copy button next to the ID copies it, the Group Details block shows description, created and last updated, and the Models / MCP Servers / Agents tabs each switch content
  3. On an access group with more than five keys or teams, confirm "View All (N)" expands and "Show Less" collapses
  4. Go to http://localhost:4000/ui/?page=vector-stores and confirm the three tabs switch, the refresh button updates "Last Refreshed", and "+ Add Vector Store" opens the create modal
  5. On the Test Vector Store tab, pick a store from the combobox (type to filter it, which the antd Select also did), run a query, expand a result row, then use "Clear History"
  6. Go to http://localhost:4000/ui/?page=organizations and confirm the search box filters, the Filters button shows its indicator dot when a filter is set, and "Reset Filters" clears them
  7. Open an organization and confirm the Overview, Members and Settings tabs each render, the organization ID copy button works, and "Edit Settings" opens the settings form

Before and after screenshots of all three routes are attached below.

Type

🧹 Refactoring
✅ Test

Changes

Scope came from the migration analyzer, which walks the real import closure from each route's page.tsx and buckets every antd or Tremor file it reaches. Only files a single route exclusively owns are touched here.

Migrated, grouped by route:

  • access-groups: AccessGroupsPage.tsx, AccessGroupsDetailsPage.tsx
  • vector-stores: _components/index.tsx, TestVectorStoreTab.tsx, VectorStoreTester.tsx
  • organizations: organization_view.tsx, Filters/FilterInput.tsx, Filters/FiltersButton.tsx, Filters/ResetFiltersButton.tsx

Deliberately not touched. Files the analyzer bucketed SHARED are reached by more than one route, so editing one from a page PR would silently change up to 34 other pages; that includes DeleteResourceModal.tsx (23 pages), message_manager.tsx and notifications_manager.tsx (53 pages each), MemberTable.tsx, ObjectPermissionsView, ModelSelect, MCPServerSelector, VectorStoreSelector, numerical_input.tsx, budget_duration_dropdown.tsx, DefaultProxyAdminTag.tsx and the IconActionButton pair. Files bucketed DEFERRED contain an antd Form, which stays blocked until #34195 lands; that covers the access group create and edit modals, CreateVectorStore.tsx, VectorStoreForm.tsx, S3VectorsConfig.tsx, vector_store_info.tsx, OrganizationsPanel.tsx, user_search_modal.tsx and EditMembership.tsx.

Because shared components stay on antd, these routes still pull antd in through their children; that is expected under this packaging and clears as the shared components migrate.

organization_view.tsx keeps one antd import, the ColumnsType type used to build the extra Spend and Created At columns it hands to the shared MemberTable. That is a type-only import dictated by the shared component's API, and it goes away when MemberTable migrates.

eslint-suppressions.json ratchets down accordingly: eight files drop their no-restricted-imports suppression entirely and organization_view.tsx goes from three to one.

Two behavioural notes. The antd Select on the Test Vector Store tab becomes a shadcn combobox rather than a plain select, so its showSearch type-ahead is preserved.

Tab panel mounting needed care. antd Tabs and Tremor TabGroup mount a panel lazily and then keep it mounted, so a half-filled form or a search history survives switching tabs and coming back. Base UI unmounts inactive panels, and its keepMounted escape hatch mounts every panel eagerly, which is not the same thing. useVisitedTabs reproduces the original semantics (mount on first visit, keep thereafter) and is applied to the two tab strips whose panels wrap stateful children: organization Settings, and the vector-stores Create and Test tabs. The access-group detail tabs render derived lists with no state, so they stay lazy. A regression test covers the organization settings case, which is the one a user would actually lose typing to.

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

…ts from antd markup

Prepares the shadcn migration of these three routes by removing every assertion that
depends on the current component library, so the same tests can gate the migration
without being edited.

FiltersButton and its OrganizationFilters consumer both asserted on the ".ant-badge"
wrapper class; they now assert the active-filter indicator element itself, and
FiltersButton additionally asserts that it is absent when there are no active filters.
TestVectorStoreTab drove the antd Select with fireEvent.mouseDown and picked options by
node; it now clicks through the combobox role and the option text, which works against
any listbox implementation.

The vector-stores index test relied on Tremor mounting every TabPanel at once, so it
read the Manage tab's table without ever opening that tab. It now clicks the tab
first, which is what a user does and what any tabs implementation supports.

VectorStoreTester had no test at all, so this adds a characterisation suite covering
the empty state, the blank-query guard, the search call and its rendered result,
result expansion, Enter versus Shift+Enter, the failure path and clearing history.

All of these pass against the current antd and Tremor components
…shadcn

Moves the nine files these three routes exclusively own off antd and Tremor onto the
shadcn primitives in src/components/ui. Scope came from the migration analyzer's import
closure, so nothing reached by a second route is touched and every file carrying an antd
Form is left alone until #34195 lands.

access-groups gets the page header, search box and the whole detail view; vector-stores
gets the tab shell, the store picker and the tester panel; organizations gets the
organization detail view and the three filter controls.

Two changes are behavioural rather than cosmetic. The vector-stores tab strip moves from
Tremor, which mounts every panel at once, to Base UI, which mounts only the active panel;
that is the correct behaviour and the reworked test now opens the tab it asserts on. The
antd Select on the Test Vector Store tab becomes a combobox rather than a plain select so
its showSearch type-ahead survives.

organization_view keeps one antd import, the ColumnsType used to build the extra columns
it hands to the shared MemberTable; that is dictated by the shared component's API and
goes away when MemberTable migrates. eslint-suppressions.json ratchets down accordingly:
eight files lose their no-restricted-imports entry and organization_view drops from three
to one.

Every test passes unedited across the migration, and the visual gate reports the three
migrated routes changed with the other 32 pixel-identical
@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Migrates the access-groups, vector-stores, and organizations interfaces from antd and Tremor to shared shadcn primitives

  • Adds visited-tab tracking to preserve stateful organization and vector-store panels after first activation
  • Rewrites UI tests around user-visible behavior and adds VectorStoreTester characterization coverage
  • Reduces obsolete restricted-import suppressions

Confidence Score: 5/5

The PR appears safe to merge with no blocking failures remaining from the reviewed follow-up threads

No blocking failures remain

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/organization/organization_view.tsx Migrates the organization detail tabs and content to shadcn while preserving settings form state across tab switches
ui/litellm-dashboard/src/hooks/useVisitedTabs.ts Adds visited-tab tracking used to retain stateful panels after their first activation
ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/TestVectorStoreTab.tsx Replaces the vector-store selector with a searchable shadcn combobox
ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/VectorStoreTester.tsx Migrates the vector-store testing interface to shared shadcn controls
ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx Migrates the vector-store page tabs and actions while preserving visited stateful panels

Reviews (2): Last reviewed commit: "fix(ui): keep tab panel state across tab..." | Re-trigger Greptile

const [selectedVectorStoreId, setSelectedVectorStoreId] = useState<string | undefined>(
vectorStores.length > 0 ? vectorStores[0].vector_store_id : undefined,
);
const [selectedVectorStore, setSelectedVectorStore] = useState<VectorStore | null>(vectorStores[0] ?? null);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Async store selection stays empty

When the Test tab mounts before the initial vector-store request completes, this state initializes to null and does not follow the populated vectorStores prop, leaving the combobox unselected and VectorStoreTester hidden until the user manually selects a store.

Knowledge Base Used: Admin dashboard (ui/litellm-dashboard)

Comment on lines 160 to 164
<div
className="flex justify-between items-center p-3 cursor-pointer hover:bg-gray-100 transition-colors"
className="flex cursor-pointer items-center justify-between p-3 transition-colors hover:bg-muted"
onClick={() => toggleResultExpansion(index, resultIndex)}
>
<div className="flex items-center">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Result disclosure lacks keyboard access

The result header is a non-focusable div whose expansion behavior is available only through onClick; keyboard users cannot open the result content, and assistive technology receives no button or expanded-state semantics.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Greptile caught a real regression in the shadcn migration: starting to edit organization
settings and switching to another tab silently discarded the unsaved input.

antd Tabs and Tremor TabGroup mount a panel lazily and then keep it mounted, so a
half-filled form or a search history survives leaving the tab and coming back. Base UI
unmounts inactive panels instead. Its keepMounted escape hatch is not equivalent either:
it mounts every panel eagerly, which renders work the user may never ask for and, on the
organization view, put the organization name on screen twice.

useVisitedTabs reproduces the original semantics by tracking which tabs have been opened
and keeping only those mounted. It is applied to the two tab strips whose panels wrap
stateful children: organization Settings, and the vector-stores Create and Test tabs,
where an in-progress upload or a search history was equally exposed. The access-group
detail tabs render lists derived from props, so they stay lazy.

The added regression test fails without the fix and passes with it, and it also passes
against the pre-migration antd component, so it pins parity rather than the new markup.
@yuneng-berri

Copy link
Copy Markdown
Contributor Author

@greptile

@yuneng-berri
yuneng-berri enabled auto-merge July 24, 2026 06:32
@devin-ai-integration

Copy link
Copy Markdown
Contributor

QA — all runbook scenarios pass ✅

Ran the proxy on :4000 (real OpenAI-backed vector store) + the dashboard dev server on :3000, logged in as admin, seeded data, and exercised all 7 runbook scenarios plus both tab-state-preservation spot-checks through the UI. Every migrated page renders shadcn primitives and behaves identically — no regressions.

Results: S1 list/search/clear ✅ · S2 detail/copy/sub-tabs ✅ (clipboard verified) · S3 View All(7)/Show Less ✅ · S4 vs tabs/refresh/add modal ✅ · S5 combobox/query/expand/clear ✅ · S6 org search/dot/reset ✅ · S7 org tabs/copy/edit ✅ · tab-state preservation (org Settings + vs Create) ✅

Full walkthrough recording

qa recording

Vector Store combobox type-to-filter + real query (Scenario 5)

The new shadcn combobox filters as you type, and a real query returns a live result:

combobox
result

access-groups (list, filter, detail, View All 7 teams)

list
filter
detail
teams

vector-stores (refresh timestamp, add modal, expanded result)

refresh
add modal
expanded result

organizations (Filters indicator dot, detail members, settings tab-state preserved)

dot
members
settings preserved

Note: some pre-existing antd deprecation console warnings remain (Modal 'visible', useForm not connected, Select value null) — these come from antd usage elsewhere in the app, not this migration, and did not affect any tested behavior.

@yuneng-berri
yuneng-berri merged commit afa8fff into litellm_internal_staging Jul 24, 2026
76 checks passed
@yuneng-berri
yuneng-berri deleted the litellm_/sleepy-pascal-0e7ee6 branch July 24, 2026 14:07
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