From 2fa724caa088db1b9e99206204fa6dccef9ddac0 Mon Sep 17 00:00:00 2001 From: Dylan Huang Date: Mon, 13 Oct 2025 11:02:00 -0700 Subject: [PATCH] Enhance LogsSection component with improved log change detection and auto-scroll behavior - Introduced a function to detect changes in logs and conditionally update the state. - Added refs to manage scroll position and determine if the user is at the bottom of the logs. - Updated auto-scroll logic to ensure smooth scrolling when new logs arrive. - Minor UI adjustments for better layout and readability. --- vite-app/src/components/LogsSection.tsx | 62 ++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/vite-app/src/components/LogsSection.tsx b/vite-app/src/components/LogsSection.tsx index 65b0ad3f..ae6881e1 100644 --- a/vite-app/src/components/LogsSection.tsx +++ b/vite-app/src/components/LogsSection.tsx @@ -9,6 +9,10 @@ import { getApiUrl } from "../config"; import Select from "./Select"; import Button from "./Button"; +const haveLogsChanged = (prevLogs: LogEntry[], nextLogs: LogEntry[]) => { + return prevLogs.length !== nextLogs.length; +}; + interface LogsSectionProps { rolloutId?: string; } @@ -19,6 +23,8 @@ export const LogsSection = observer(({ rolloutId }: LogsSectionProps) => { const [error, setError] = useState(null); const [selectedLevel, setSelectedLevel] = useState(""); const scrollContainerRef = useRef(null); + const isAtBottomRef = useRef(true); + const shouldAutoScrollRef = useRef(false); const fetchLogs = async (isInitialLoad = false) => { if (!rolloutId) return; @@ -93,7 +99,19 @@ export const LogsSection = observer(({ rolloutId }: LogsSectionProps) => { const data: LogsResponse = LogsResponseSchema.parse( await response.json() ); - setLogs(data.logs); + setLogs((prevLogs) => { + const hasChanges = haveLogsChanged(prevLogs, data.logs); + + if (!hasChanges) { + return prevLogs; + } + + if (isAtBottomRef.current) { + shouldAutoScrollRef.current = true; + } + + return data.logs; + }); } catch (err) { if (err instanceof Error && err.message.includes("Unexpected token")) { setError( @@ -115,11 +133,43 @@ export const LogsSection = observer(({ rolloutId }: LogsSectionProps) => { } }, [rolloutId, selectedLevel]); - // Auto-scroll to bottom whenever logs update useEffect(() => { const el = scrollContainerRef.current; - if (!el) return; + + if (!el) { + isAtBottomRef.current = true; + return; + } + + const handleScroll = () => { + const distanceFromBottom = + el.scrollHeight - el.scrollTop - el.clientHeight; + isAtBottomRef.current = distanceFromBottom <= 8; + }; + + el.addEventListener("scroll", handleScroll); + handleScroll(); + + return () => { + el.removeEventListener("scroll", handleScroll); + }; + }, [logs.length]); + + // Auto-scroll to bottom when new logs arrive and user was already at bottom + useEffect(() => { + if (!shouldAutoScrollRef.current) { + return; + } + + const el = scrollContainerRef.current; + if (!el) { + shouldAutoScrollRef.current = false; + return; + } + el.scrollTo({ top: el.scrollHeight, behavior: "smooth" }); + shouldAutoScrollRef.current = false; + isAtBottomRef.current = true; }, [logs]); if (!rolloutId) { @@ -170,13 +220,13 @@ export const LogsSection = observer(({ rolloutId }: LogsSectionProps) => { {logs.length > 0 && (
-
+
{logs.map((log, index) => (