diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx index 7137da201d8..3224496b13a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.test.tsx @@ -45,7 +45,7 @@ describe("VectorStoreManagement loading state", () => { it("should resolve the loading state when accessToken is null instead of showing the skeleton forever", async () => { const user = userEvent.setup(); - render(); + render(); await openManageTab(user); expect(await screen.findByText("table-loaded")).toBeInTheDocument(); expect(mockVectorStoreListCall).not.toHaveBeenCalled(); @@ -59,7 +59,7 @@ describe("VectorStoreManagement loading state", () => { resolveFetch = resolve; }), ); - render(); + render(); await openManageTab(user); expect(screen.getByText("table-loading")).toBeInTheDocument(); @@ -69,6 +69,43 @@ describe("VectorStoreManagement loading state", () => { }); }); +describe("VectorStoreManagement create flow visibility", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockVectorStoreListCall.mockResolvedValue({ data: [] }); + mockCredentialListCall.mockResolvedValue({ credentials: [] }); + }); + + it.each([ + { label: "Internal User", userRole: "Internal User", isViewOnly: false }, + { label: "Internal Viewer", userRole: "Internal Viewer", isViewOnly: true }, + { label: "proxy_admin_viewer session (userRole Admin, isViewOnly)", userRole: "Admin", isViewOnly: true }, + { label: "Org Admin", userRole: "Org Admin", isViewOnly: false }, + ])( + "should hide the Create Vector Store tab and button and skip /credentials for $label", + async ({ userRole, isViewOnly }) => { + render( + , + ); + await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test")); + expect(screen.queryByRole("tab", { name: "Create Vector Store" })).not.toBeInTheDocument(); + expect(screen.getByRole("tab", { name: "Manage Vector Stores" })).toHaveAttribute("aria-selected", "true"); + expect(await screen.findByText("table-loaded")).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "+ Add Vector Store" })).not.toBeInTheDocument(); + expect(mockCredentialListCall).not.toHaveBeenCalled(); + }, + ); + + it("should keep the Create Vector Store tab and button and fetch /credentials for a proxy admin", async () => { + const user = userEvent.setup(); + render(); + await waitFor(() => expect(mockCredentialListCall).toHaveBeenCalledWith("sk-test")); + expect(screen.getByRole("tab", { name: "Create Vector Store" })).toHaveAttribute("aria-selected", "true"); + await openManageTab(user); + expect(screen.getByRole("button", { name: "+ Add Vector Store" })).toBeInTheDocument(); + }); +}); + describe("VectorStoreManagement Indexes tab", () => { beforeEach(() => { vi.clearAllMocks(); @@ -88,7 +125,7 @@ describe("VectorStoreManagement Indexes tab", () => { }, ], }); - render(); + render(); await user.click(screen.getByRole("tab", { name: "Indexes" })); expect(await screen.findByText("support-docs-index")).toBeInTheDocument(); expect(screen.getByText("support-docs-store")).toBeInTheDocument(); @@ -96,7 +133,7 @@ describe("VectorStoreManagement Indexes tab", () => { }); it("should not render the Indexes tab for an Admin Viewer", async () => { - render(); + render(); await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test")); expect(screen.getByRole("tab", { name: "Manage Vector Stores" })).toBeInTheDocument(); expect(screen.queryByRole("tab", { name: "Indexes" })).not.toBeInTheDocument(); @@ -125,7 +162,7 @@ describe("VectorStoreManagement Indexes tab", () => { }, ], }); - render(); + render(); await user.click(screen.getByRole("tab", { name: "Indexes" })); await user.click(await screen.findByRole("button", { name: "support-docs-store" })); expect(await screen.findByTestId("vector-store-info-view")).toHaveTextContent("vs-1"); @@ -135,7 +172,7 @@ describe("VectorStoreManagement Indexes tab", () => { it("should link to the feature docs and a GitHub issue for unsupported providers on the Indexes tab", async () => { const user = userEvent.setup(); mockIndexesListCall.mockResolvedValue({ object: "list", data: [] }); - render(); + render(); await user.click(screen.getByRole("tab", { name: "Indexes" })); expect(screen.getByRole("link", { name: "vector store index docs" })).toHaveAttribute( "href", @@ -149,7 +186,7 @@ describe("VectorStoreManagement Indexes tab", () => { }); it("should not call indexesListCall until the Indexes tab is clicked", async () => { - render(); + render(); await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test")); expect(screen.getByRole("tab", { name: "Indexes" })).toBeInTheDocument(); expect(mockIndexesListCall).not.toHaveBeenCalled(); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx index 1745c710c51..285a96520bd 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/vector-stores/_components/index.tsx @@ -24,9 +24,10 @@ interface VectorStoreProps { accessToken: string | null; userID: string | null; userRole: string | null; + isViewOnly: boolean; } -const VectorStoreManagement: React.FC = ({ accessToken, userID, userRole }) => { +const VectorStoreManagement: React.FC = ({ accessToken, userID, userRole, isViewOnly }) => { const [vectorStores, setVectorStores] = useState([]); const [isLoadingVectorStores, setIsLoadingVectorStores] = useState(true); const [isCreateModalVisible, setIsCreateModalVisible] = useState(false); @@ -37,7 +38,9 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID const [selectedVectorStoreId, setSelectedVectorStoreId] = useState(null); const [editVectorStore, setEditVectorStore] = useState(false); const [isDeleting, setIsDeleting] = useState(false); - const { onTabChange, hasVisited } = useVisitedTabs("create"); + const canCreateVectorStores = isProxyAdminRole(userRole || "") && !isViewOnly; + const defaultTab = canCreateVectorStores ? "create" : "manage"; + const { onTabChange, hasVisited } = useVisitedTabs(defaultTab); const fetchVectorStores = async () => { if (!accessToken) { @@ -56,7 +59,7 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID }; const fetchCredentials = async () => { - if (!accessToken) return; + if (!accessToken || !canCreateVectorStores) return; try { const response = await credentialListCall(accessToken); setCredentials(response.credentials || []); @@ -153,11 +156,13 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID You can use vector stores to store and retrieve LLM embeddings.

- + - - Create Vector Store - + {canCreateVectorStores && ( + + Create Vector Store + + )} Manage Vector Stores @@ -171,14 +176,18 @@ const VectorStoreManagement: React.FC = ({ accessToken, userID )} - - - + {canCreateVectorStores && ( + + + + )} - + {canCreateVectorStores && ( + + )}
; + const { accessToken, userRole, userId, isViewOnly } = useAuthorized(); + return ( + + ); }