mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(ui): hide the Create Vector Store flow from non proxy admins (#40148)
* fix(ui): hide the Create Vector Store flow from non proxy admins The vector stores page rendered the Create Vector Store tab, the + Add Vector Store button and a GET /credentials call for every role, while the proxy only lets proxy admins call POST /vector_store/new and GET /credentials. Internal users landed on the create form and got an Only proxy admin error toast. Gate all three on isProxyAdminRole and default everyone else to the Manage tab, matching the Indexes tab and the Add Model gating. Resolves LIT-7131 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(ui): exclude view-only admin sessions from the vector store create flow Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: yassin <yassin@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
eea4c860f7
commit
cc287a7d8f
3 changed files with 70 additions and 22 deletions
|
|
@ -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(<VectorStoreManagement accessToken={null} userID={null} userRole={null} />);
|
||||
render(<VectorStoreManagement accessToken={null} userID={null} userRole={null} isViewOnly={false} />);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" />);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" isViewOnly={false} />);
|
||||
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(
|
||||
<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole={userRole} isViewOnly={isViewOnly} />,
|
||||
);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" isViewOnly={false} />);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" />);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" isViewOnly={false} />);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin Viewer" />);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin Viewer" isViewOnly={true} />);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" />);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" isViewOnly={false} />);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" />);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" isViewOnly={false} />);
|
||||
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(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" />);
|
||||
render(<VectorStoreManagement accessToken="sk-test" userID="user-1" userRole="Admin" isViewOnly={false} />);
|
||||
await waitFor(() => expect(mockVectorStoreListCall).toHaveBeenCalledWith("sk-test"));
|
||||
expect(screen.getByRole("tab", { name: "Indexes" })).toBeInTheDocument();
|
||||
expect(mockIndexesListCall).not.toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -24,9 +24,10 @@ interface VectorStoreProps {
|
|||
accessToken: string | null;
|
||||
userID: string | null;
|
||||
userRole: string | null;
|
||||
isViewOnly: boolean;
|
||||
}
|
||||
|
||||
const VectorStoreManagement: React.FC<VectorStoreProps> = ({ accessToken, userID, userRole }) => {
|
||||
const VectorStoreManagement: React.FC<VectorStoreProps> = ({ accessToken, userID, userRole, isViewOnly }) => {
|
||||
const [vectorStores, setVectorStores] = useState<VectorStore[]>([]);
|
||||
const [isLoadingVectorStores, setIsLoadingVectorStores] = useState(true);
|
||||
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
|
||||
|
|
@ -37,7 +38,9 @@ const VectorStoreManagement: React.FC<VectorStoreProps> = ({ accessToken, userID
|
|||
const [selectedVectorStoreId, setSelectedVectorStoreId] = useState<string | null>(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<VectorStoreProps> = ({ 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<VectorStoreProps> = ({ accessToken, userID
|
|||
You can use vector stores to store and retrieve LLM embeddings.
|
||||
</p>
|
||||
|
||||
<Tabs defaultValue="create" onValueChange={onTabChange}>
|
||||
<Tabs defaultValue={defaultTab} onValueChange={onTabChange}>
|
||||
<TabsList variant="line" className="mb-6 h-auto w-full justify-start rounded-none p-0">
|
||||
<TabsTrigger value="create" className="flex-none rounded-none px-4 py-2">
|
||||
Create Vector Store
|
||||
</TabsTrigger>
|
||||
{canCreateVectorStores && (
|
||||
<TabsTrigger value="create" className="flex-none rounded-none px-4 py-2">
|
||||
Create Vector Store
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="manage" className="flex-none rounded-none px-4 py-2">
|
||||
Manage Vector Stores
|
||||
</TabsTrigger>
|
||||
|
|
@ -171,14 +176,18 @@ const VectorStoreManagement: React.FC<VectorStoreProps> = ({ accessToken, userID
|
|||
)}
|
||||
</TabsList>
|
||||
|
||||
<TabsContent keepMounted={hasVisited("create")} value="create">
|
||||
<CreateVectorStore accessToken={accessToken} onSuccess={handleVectorStoreCreated} />
|
||||
</TabsContent>
|
||||
{canCreateVectorStores && (
|
||||
<TabsContent keepMounted={hasVisited("create")} value="create">
|
||||
<CreateVectorStore accessToken={accessToken} onSuccess={handleVectorStoreCreated} />
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
<TabsContent keepMounted={hasVisited("manage")} value="manage">
|
||||
<Button className="mb-4" onClick={() => setIsCreateModalVisible(true)}>
|
||||
+ Add Vector Store
|
||||
</Button>
|
||||
{canCreateVectorStores && (
|
||||
<Button className="mb-4" onClick={() => setIsCreateModalVisible(true)}>
|
||||
+ Add Vector Store
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 gap-2 pt-2 pb-2 w-full mt-2">
|
||||
<VectorStoreTable
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import VectorStoreManagement from "./_components";
|
|||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
|
||||
export default function VectorStores() {
|
||||
const { accessToken, userRole, userId } = useAuthorized();
|
||||
return <VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userId} />;
|
||||
const { accessToken, userRole, userId, isViewOnly } = useAuthorized();
|
||||
return (
|
||||
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userId} isViewOnly={isViewOnly} />
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue