From 45deb8be7463dba668ee7ad39fb8bdb2cd7dc8ad Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 26 Jun 2026 12:18:58 +0530 Subject: [PATCH 1/3] fix: logs filters values encoding --- ui/app/workspace/logs/page.tsx | 34 ++++++++++++++++---------------- ui/lib/queryParamsParser.test.ts | 20 +++++++++++++++++++ ui/lib/queryParamsParser.ts | 8 ++++++-- 3 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 ui/lib/queryParamsParser.test.ts diff --git a/ui/app/workspace/logs/page.tsx b/ui/app/workspace/logs/page.tsx index 369b813aa09..57e714c078c 100644 --- a/ui/app/workspace/logs/page.tsx +++ b/ui/app/workspace/logs/page.tsx @@ -26,8 +26,8 @@ import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import NumberFlow from "@number-flow/react"; import { useLocation } from "@tanstack/react-router"; import { AlertCircle, BarChart, CheckCircle, Clock, DollarSign, Hash, Info } from "lucide-react"; -import { parseAsSafeString } from "@/lib/queryParamsParser"; -import { parseAsArrayOf, parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"; +import { parseAsSafeArrayOf, parseAsSafeString } from "@/lib/queryParamsParser"; +import { parseAsBoolean, parseAsInteger, parseAsString, useQueryStates } from "nuqs"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; export default function LogsPage() { @@ -72,20 +72,20 @@ export default function LogsPage() { const [urlState, setUrlState] = useQueryStates( { parent_request_id: parseAsString.withDefault(""), - providers: parseAsArrayOf(parseAsString).withDefault([]), - models: parseAsArrayOf(parseAsString).withDefault([]), - aliases: parseAsArrayOf(parseAsString).withDefault([]), - status: parseAsArrayOf(parseAsString).withDefault([]), - stop_reasons: parseAsArrayOf(parseAsString).withDefault([]), - objects: parseAsArrayOf(parseAsString).withDefault([]), - selected_key_ids: parseAsArrayOf(parseAsString).withDefault([]), - virtual_key_ids: parseAsArrayOf(parseAsString).withDefault([]), - routing_rule_ids: parseAsArrayOf(parseAsString).withDefault([]), - routing_engine_used: parseAsArrayOf(parseAsString).withDefault([]), - user_ids: parseAsArrayOf(parseAsString).withDefault([]), - team_ids: parseAsArrayOf(parseAsString).withDefault([]), - customer_ids: parseAsArrayOf(parseAsString).withDefault([]), - business_unit_ids: parseAsArrayOf(parseAsString).withDefault([]), + providers: parseAsSafeArrayOf.withDefault([]), + models: parseAsSafeArrayOf.withDefault([]), + aliases: parseAsSafeArrayOf.withDefault([]), + status: parseAsSafeArrayOf.withDefault([]), + stop_reasons: parseAsSafeArrayOf.withDefault([]), + objects: parseAsSafeArrayOf.withDefault([]), + selected_key_ids: parseAsSafeArrayOf.withDefault([]), + virtual_key_ids: parseAsSafeArrayOf.withDefault([]), + routing_rule_ids: parseAsSafeArrayOf.withDefault([]), + routing_engine_used: parseAsSafeArrayOf.withDefault([]), + user_ids: parseAsSafeArrayOf.withDefault([]), + team_ids: parseAsSafeArrayOf.withDefault([]), + customer_ids: parseAsSafeArrayOf.withDefault([]), + business_unit_ids: parseAsSafeArrayOf.withDefault([]), content_search: parseAsSafeString.withDefault(""), start_time: parseAsInteger.withDefault(defaultTimeRange.startTime), end_time: parseAsInteger.withDefault(defaultTimeRange.endTime), @@ -96,7 +96,7 @@ export default function LogsPage() { polling: parseAsBoolean.withDefault(true).withOptions({ clearOnDefault: false }), period: parseAsString.withDefault(hasExplicitTimeRange ? "" : "1h").withOptions({ clearOnDefault: false }), missing_cost_only: parseAsBoolean.withDefault(false), - cache_hit_types: parseAsArrayOf(parseAsString).withDefault([]), + cache_hit_types: parseAsSafeArrayOf.withDefault([]), metadata_filters: parseAsString.withDefault(""), selected_log: parseAsString.withDefault(""), }, diff --git a/ui/lib/queryParamsParser.test.ts b/ui/lib/queryParamsParser.test.ts new file mode 100644 index 00000000000..27580c7af24 --- /dev/null +++ b/ui/lib/queryParamsParser.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from "vitest"; +import { parseAsSafeArrayOf, parseAsSafeString } from "./queryParamsParser"; + +describe("parseAsSafeString", () => { + test("roundtrips model names with double slashes", () => { + const model = "gpt://b1g9n2rqsrnikgh0uofm/gpt-oss-120b"; + const serialized = parseAsSafeString.serialize(model); + expect(serialized).not.toContain("://"); + expect(parseAsSafeString.parse(serialized)).toBe(model); + }); +}); + +describe("parseAsSafeArrayOf", () => { + test("roundtrips multiple models with special characters", () => { + const models = ["gpt://b1g9n2rqsrnikgh0uofm/gpt-oss-20b", "gpt://b1g9n2rqsrnikgh0uofm/gpt-oss-120b"]; + const serialized = parseAsSafeArrayOf.serialize(models); + expect(serialized).not.toContain("://"); + expect(parseAsSafeArrayOf.parse(serialized)).toEqual(models); + }); +}); \ No newline at end of file diff --git a/ui/lib/queryParamsParser.ts b/ui/lib/queryParamsParser.ts index 8f85687d662..d4436f445e6 100644 --- a/ui/lib/queryParamsParser.ts +++ b/ui/lib/queryParamsParser.ts @@ -1,4 +1,4 @@ -import { createParser } from "nuqs"; +import { createParser, parseAsArrayOf } from "nuqs"; // nuqs's encodeQueryValue skips characters like "/" that TanStack Router's // navigate({ to }) interprets as path/query delimiters. Full URI-encoding @@ -18,4 +18,8 @@ export const parseAsSafeString = createParser({ return value; } }, -}); \ No newline at end of file +}); + +// Comma-separated filter values (models, providers, etc.) with the same +// encoding guarantees as parseAsSafeString. +export const parseAsSafeArrayOf = parseAsArrayOf(parseAsSafeString); \ No newline at end of file From 80962a57c64b18d14519a63a5ab2fba0dab19498 Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 26 Jun 2026 12:22:57 +0530 Subject: [PATCH 2/3] feat: TruncatedLabel ui component --- ui/components/ui/truncatedLabel.tsx | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 ui/components/ui/truncatedLabel.tsx diff --git a/ui/components/ui/truncatedLabel.tsx b/ui/components/ui/truncatedLabel.tsx new file mode 100644 index 00000000000..ecb2bf8c904 --- /dev/null +++ b/ui/components/ui/truncatedLabel.tsx @@ -0,0 +1,54 @@ +import * as React from "react"; +import { useCallback, useLayoutEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; + +import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip"; + +type TruncatedLabelProps = { + children: React.ReactNode; + className?: string; + tooltip?: React.ReactNode; + tooltipSide?: React.ComponentProps["side"]; +} & Omit, "children">; + +function TruncatedLabel({ children, className, tooltip, tooltipSide = "right", ...props }: TruncatedLabelProps) { + const [measureEl, setMeasureEl] = useState(null); + const [isTruncated, setIsTruncated] = useState(false); + + const setTextRef = useCallback((node: HTMLSpanElement | null) => { + setMeasureEl(node); + }, []); + + useLayoutEffect(() => { + if (!measureEl) return; + + const checkTruncation = () => { + setIsTruncated(measureEl.scrollWidth > measureEl.clientWidth); + }; + + checkTruncation(); + const observer = new ResizeObserver(checkTruncation); + observer.observe(measureEl); + return () => observer.disconnect(); + }, [measureEl, children]); + + const tooltipContent = tooltip ?? (typeof children === "string" ? children : undefined); + + const inner = ( + + {children} + + ); + + if (!isTruncated || tooltipContent == null) return inner; + + return ( + + {inner} + {tooltipContent} + + ); +} + +export { TruncatedLabel }; From 53c60fc0e34d5ebed18cf50c619d2e19144d746c Mon Sep 17 00:00:00 2001 From: Suresh Chaudhary Date: Fri, 26 Jun 2026 12:23:26 +0530 Subject: [PATCH 3/3] chore: show tooltip for logs filter sidebar for overflowing text --- ui/app/workspace/providers/page.tsx | 38 ++------------------- ui/components/filters/logsFilterSidebar.tsx | 3 +- ui/components/filters/mcpFilterSidebar.tsx | 3 +- 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/ui/app/workspace/providers/page.tsx b/ui/app/workspace/providers/page.tsx index 79446439941..3d3d94cd7df 100644 --- a/ui/app/workspace/providers/page.tsx +++ b/ui/app/workspace/providers/page.tsx @@ -2,6 +2,7 @@ import ModelProviderConfig from "@/app/workspace/providers/views/modelProviderCo import FullPageLoader from "@/components/fullPageLoader"; import { Badge } from "@/components/ui/badge"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { TruncatedLabel } from "@/components/ui/truncatedLabel"; import { DefaultNetworkConfig, DefaultPerformanceConfig } from "@/lib/constants/config"; import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"; import { ProviderLabels, ProviderNames } from "@/lib/constants/logs"; @@ -20,7 +21,7 @@ import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { useNavigate } from "@tanstack/react-router"; import { AlertCircle } from "lucide-react"; import { useQueryState } from "nuqs"; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; import AddCustomProviderSheet from "./dialogs/addNewCustomProviderSheet"; import ConfirmDeleteProviderDialog from "./dialogs/confirmDeleteProviderDialog"; @@ -227,7 +228,7 @@ export default function Providers() { size="sm" className="h-4 w-4 shrink-0" /> - + {label} {isCustom && ( @@ -272,39 +273,6 @@ export default function Providers() { ); } -function TruncatedName({ name }: { name: string }) { - const textRef = useRef(null); - const [isTruncated, setIsTruncated] = useState(false); - - const checkTruncation = useCallback(() => { - const el = textRef.current; - if (el) { - setIsTruncated(el.scrollWidth > el.clientWidth); - } - }, []); - - useEffect(() => { - checkTruncation(); - window.addEventListener("resize", checkTruncation); - return () => window.removeEventListener("resize", checkTruncation); - }, [checkTruncation, name]); - - const inner = ( -
- {name} -
- ); - - if (!isTruncated) return inner; - - return ( - - {inner} - {name} - - ); -} - function ProviderStatusBadge({ status }: { status: ProviderStatus }) { return status != "active" ? ( diff --git a/ui/components/filters/logsFilterSidebar.tsx b/ui/components/filters/logsFilterSidebar.tsx index bc3085bc388..dec306f51bd 100644 --- a/ui/components/filters/logsFilterSidebar.tsx +++ b/ui/components/filters/logsFilterSidebar.tsx @@ -4,6 +4,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scrollArea"; import { Skeleton } from "@/components/ui/skeleton"; +import { TruncatedLabel } from "@/components/ui/truncatedLabel"; import { RequestTypeLabels, RequestTypes, RoutingEngineUsedLabels, Statuses } from "@/lib/constants/logs"; import { useGetAvailableFilterDataQuery, useGetProvidersQuery } from "@/lib/store"; import type { LogFilters } from "@/lib/types/logs"; @@ -234,7 +235,7 @@ function CheckboxFilterItem({ return ( ); } diff --git a/ui/components/filters/mcpFilterSidebar.tsx b/ui/components/filters/mcpFilterSidebar.tsx index 6ee0ff18650..3b0267a2e5c 100644 --- a/ui/components/filters/mcpFilterSidebar.tsx +++ b/ui/components/filters/mcpFilterSidebar.tsx @@ -4,6 +4,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/component import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scrollArea"; import { Skeleton } from "@/components/ui/skeleton"; +import { TruncatedLabel } from "@/components/ui/truncatedLabel"; import { Statuses } from "@/lib/constants/logs"; import { useGetMCPLogsFilterDataQuery } from "@/lib/store"; import type { MCPToolLogFilters } from "@/lib/types/logs"; @@ -195,7 +196,7 @@ function CheckboxFilterItem({ return ( ); }