diff --git a/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.test.tsx b/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.test.tsx new file mode 100644 index 00000000000..76b90bdca90 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.test.tsx @@ -0,0 +1,39 @@ +import { readFileSync } from "fs"; +import { resolve } from "path"; +import React from "react"; +import { describe, it, expect, vi } from "vitest"; +import { renderWithProviders, screen } from "../../../tests/test-utils"; +import PremiumLoggingSettings from "./PremiumLoggingSettings"; + +const SOURCE_PATH = resolve(process.cwd(), "src/components/common_components/PremiumLoggingSettings.tsx"); + +const HARDCODED_PALETTE = + /\b(?:text|bg|border|hover:bg|hover:text|hover:border|dark:bg|dark:text|dark:border|ring|divide|fill|stroke)-(?:gray|slate|zinc|neutral|stone|red|blue|green|yellow|amber|orange|indigo|purple|pink|rose|teal|cyan|sky|violet|fuchsia|lime|emerald)-\d+(?:\/\d+)?\b/g; + +const SEMANTIC_TOKEN = + /\b(?:text|bg|border|hover:bg|hover:text|ring|divide|fill|stroke)-(?:foreground|muted-foreground|muted|background|card|popover|primary|secondary|destructive|border|input|accent|ring)(?:-foreground)?(?:\/\d+)?\b/g; + +describe("PremiumLoggingSettings", () => { + it("styles itself from semantic tokens instead of hardcoded palette classes", () => { + const source = readFileSync(SOURCE_PATH, "utf8"); + + expect(source).toContain("export function PremiumLoggingSettings"); + expect(source.match(SEMANTIC_TOKEN) ?? []).not.toHaveLength(0); + expect(source.match(HARDCODED_PALETTE) ?? []).toHaveLength(0); + }); + + it("shows the enterprise notice and withholds the editor from a free user", () => { + renderWithProviders(); + + expect(screen.getByText(/LiteLLM Enterprise feature/)).toBeInTheDocument(); + expect(screen.getByText("✨ langfuse-logging")).toBeInTheDocument(); + expect(screen.queryByText("Logging Integrations")).not.toBeInTheDocument(); + }); + + it("renders the editor for a premium user", () => { + renderWithProviders(); + + expect(screen.getByText("Logging Integrations")).toBeInTheDocument(); + expect(screen.queryByText(/LiteLLM Enterprise feature/)).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx b/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx index 62d2f182120..d1f3cfe6712 100644 --- a/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx +++ b/ui/litellm-dashboard/src/components/common_components/PremiumLoggingSettings.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { Badge } from "@/components/ui/badge"; import LoggingSettings from "../team/LoggingSettings"; interface PremiumLoggingSettingsProps { @@ -20,15 +21,15 @@ export function PremiumLoggingSettings({ return (
-
+ ✨ langfuse-logging -
-
+ + ✨ datadog-logging -
+
-
-

+

+

Setting Key/Team logging settings is a LiteLLM Enterprise feature. Global Logging Settings are available for all free users. Get a trial key{" "} diff --git a/ui/litellm-dashboard/src/components/team/LoggingSettings.test.tsx b/ui/litellm-dashboard/src/components/team/LoggingSettings.test.tsx index 17f20ad0dcc..ae02a26ddb0 100644 --- a/ui/litellm-dashboard/src/components/team/LoggingSettings.test.tsx +++ b/ui/litellm-dashboard/src/components/team/LoggingSettings.test.tsx @@ -1,9 +1,19 @@ +import { readFileSync } from "fs"; +import { resolve } from "path"; import React from "react"; import { describe, it, expect, vi, beforeEach } from "vitest"; import userEvent from "@testing-library/user-event"; import { renderWithProviders, screen, fireEvent } from "../../../tests/test-utils"; import LoggingSettings from "./LoggingSettings"; +const SOURCE_PATH = resolve(process.cwd(), "src/components/team/LoggingSettings.tsx"); + +const HARDCODED_PALETTE = + /\b(?:text|bg|border|hover:bg|hover:text|hover:border|dark:bg|dark:text|dark:border|ring|divide|fill|stroke)-(?:gray|slate|zinc|neutral|stone|red|blue|green|yellow|amber|orange|indigo|purple|pink|rose|teal|cyan|sky|violet|fuchsia|lime|emerald)-\d+(?:\/\d+)?\b/g; + +const SEMANTIC_TOKEN = + /\b(?:text|bg|border|hover:bg|hover:text|ring|divide|fill|stroke)-(?:foreground|muted-foreground|muted|background|card|popover|primary|secondary|destructive|border|input|accent|ring)(?:-foreground)?(?:\/\d+)?\b/g; + describe("LoggingSettings", () => { beforeEach(() => { vi.clearAllMocks(); @@ -163,6 +173,33 @@ describe("LoggingSettings", () => { expect(screen.getByText("C")).toBeInTheDocument(); }); + it("styles itself from semantic tokens instead of hardcoded palette classes", () => { + const source = readFileSync(SOURCE_PATH, "utf8"); + + expect(source).toContain("const LoggingSettings"); + expect(source.match(SEMANTIC_TOKEN) ?? []).not.toHaveLength(0); + expect(source.match(HARDCODED_PALETTE) ?? []).toHaveLength(0); + }); + + it("reports the chosen event type when a different option is picked", async () => { + const user = userEvent.setup({ delay: null }); + const mockOnChange = vi.fn(); + const initialValue = [ + { + callback_name: "langsmith", + callback_type: "success", + callback_vars: {}, + }, + ]; + + renderWithProviders(); + + await user.click(screen.getByTitle("Success Only")); + await user.click(await screen.findByTitle("Failure Only")); + + expect(mockOnChange).toHaveBeenCalledWith([expect.objectContaining({ callback_type: "failure" })]); + }); + it("correctly handles numerical input with decimal values", () => { const mockOnChange = vi.fn(); diff --git a/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx b/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx index eee6e76569d..0fc936159fa 100644 --- a/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx +++ b/ui/litellm-dashboard/src/components/team/LoggingSettings.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Select, Tooltip, Divider } from "antd"; import { InfoCircleOutlined } from "@ant-design/icons"; import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; import { Card } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group"; @@ -142,30 +143,24 @@ const LoggingSettings: React.FC = ({ if (Object.keys(dynamicParams).length === 0) return null; return ( -

+
-
-
+
+
- Integration Parameters + Integration Parameters
{Object.entries(dynamicParams).map(([paramName, paramType]) => (
-