diff --git a/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.test.tsx
index 2530ce9fda7..e5740a89822 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.test.tsx
@@ -1,7 +1,8 @@
import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
-import { tagListCall } from "@/components/networking";
+import { tagDeleteCall, tagListCall } from "@/components/networking";
import TagManagement from "./index";
@@ -12,10 +13,23 @@ vi.mock("@/components/networking", () => ({
modelInfoCall: vi.fn(),
}));
+vi.mock("@/components/molecules/notifications_manager", () => ({
+ __esModule: true,
+ default: {
+ success: vi.fn(),
+ fromBackend: vi.fn(),
+ },
+}));
+
vi.mock("./TagTable", () => ({
__esModule: true,
- default: ({ isLoading }: { isLoading?: boolean }) => (
-
{isLoading ? "table-loading" : "table-loaded"}
+ default: ({ isLoading, onDelete }: { isLoading?: boolean; onDelete: (tagName: string) => void }) => (
+
+ {isLoading ? "table-loading" : "table-loaded"}
+
+
),
}));
@@ -30,6 +44,7 @@ vi.mock("./components/CreateTagModal", () => ({
}));
const mockTagListCall = vi.mocked(tagListCall);
+const mockTagDeleteCall = vi.mocked(tagDeleteCall);
describe("TagManagement loading state", () => {
beforeEach(() => {
@@ -57,3 +72,41 @@ describe("TagManagement loading state", () => {
expect(mockTagListCall).toHaveBeenCalledWith("sk-test");
});
});
+
+describe("TagManagement delete flow", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockTagListCall.mockResolvedValue({});
+ });
+
+ it("should confirm deletion through the shared DeleteResourceModal and call tagDeleteCall with the tag name", async () => {
+ const user = userEvent.setup();
+ mockTagDeleteCall.mockResolvedValue({});
+ render();
+ await screen.findByText("table-loaded");
+
+ expect(screen.queryByText("Tag Information")).not.toBeInTheDocument();
+
+ await user.click(screen.getByTestId("mock-delete-trigger"));
+
+ expect(await screen.findByText("Tag Information")).toBeInTheDocument();
+ expect(screen.getByText("test-tag")).toBeInTheDocument();
+
+ await user.click(screen.getByRole("button", { name: /delete/i }));
+
+ expect(mockTagDeleteCall).toHaveBeenCalledWith("sk-test", "test-tag");
+ });
+
+ it("should not call tagDeleteCall when the deletion is cancelled", async () => {
+ const user = userEvent.setup();
+ render();
+ await screen.findByText("table-loaded");
+
+ await user.click(screen.getByTestId("mock-delete-trigger"));
+ await screen.findByText("Tag Information");
+
+ await user.click(screen.getByRole("button", { name: "Cancel" }));
+
+ expect(mockTagDeleteCall).not.toHaveBeenCalled();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.tsx b/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.tsx
index 16b92bd4553..301d9264437 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/tag-management/_components/index.tsx
@@ -7,6 +7,7 @@ import { tagCreateCall, tagListCall, tagDeleteCall } from "@/components/networki
import { Tag } from "@/components/tag_management/types";
import TagTable from "./TagTable";
import NotificationsManager from "@/components/molecules/notifications_manager";
+import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import CreateTagModal from "./components/CreateTagModal";
interface ModelInfo {
@@ -33,6 +34,7 @@ const TagManagement: React.FC = ({ accessToken, userID, userRole }) =>
const [editTag, setEditTag] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [tagToDelete, setTagToDelete] = useState(null);
+ const [isDeleting, setIsDeleting] = useState(false);
const [lastRefreshed, setLastRefreshed] = useState("");
const [availableModels, setAvailableModels] = useState([]);
@@ -87,6 +89,7 @@ const TagManagement: React.FC = ({ accessToken, userID, userRole }) =>
const confirmDelete = async () => {
if (!accessToken || !tagToDelete) return;
+ setIsDeleting(true);
try {
await tagDeleteCall(accessToken, tagToDelete);
NotificationsManager.success("Tag deleted successfully");
@@ -94,9 +97,11 @@ const TagManagement: React.FC = ({ accessToken, userID, userRole }) =>
} catch (error) {
console.error("Error deleting tag:", error);
NotificationsManager.fromBackend("Error deleting tag: " + error);
+ } finally {
+ setIsDeleting(false);
+ setIsDeleteModalOpen(false);
+ setTagToDelete(null);
}
- setIsDeleteModalOpen(false);
- setTagToDelete(null);
};
useEffect(() => {
@@ -189,40 +194,19 @@ const TagManagement: React.FC = ({ accessToken, userID, userRole }) =>
/>
{/* Delete Confirmation Modal */}
- {isDeleteModalOpen && (
-
-
-
-
-
-
-
-
Delete Tag
-
-
Are you sure you want to delete this tag?
-
-
-
-
-
-
-
-
-
-
-
- )}
+ {
+ setIsDeleteModalOpen(false);
+ setTagToDelete(null);
+ }}
+ onOk={confirmDelete}
+ confirmLoading={isDeleting}
+ />
)}