diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 1ffd128b30..983c20decb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -694,6 +694,20 @@ function TasksTreeView({ const [showDurations, setShowDurations] = useState(true); const [showQueueTime, setShowQueueTime] = useState(false); const [scale, setScale] = useState(0); + const [hoveredNodeId, setHoveredNodeId] = useState(null); + const [isScrolling, setIsScrolling] = useState(false); + const scrollTimeoutRef = useRef(null); + const pendingHoverIdRef = useRef(null); + + useEffect(() => { + return () => { + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + scrollTimeoutRef.current = null; + } + }; + }, []); + const parentRef = useRef(null); const treeScrollRef = useRef(null); const timelineScrollRef = useRef(null); @@ -705,6 +719,24 @@ function TasksTreeView({ const displayEvents = events; const queuedTime = showQueueTime ? undefined : queuedDuration; + const handleHoverChange = useCallback((nodeId: string | null) => { + pendingHoverIdRef.current = nodeId; + if (!isScrolling) { + setHoveredNodeId(nodeId); + } + }, [isScrolling]); + + const handleScroll = useCallback((scrollTop: number) => { + setIsScrolling(true); + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); + } + scrollTimeoutRef.current = setTimeout(() => { + setIsScrolling(false); + setHoveredNodeId(pendingHoverIdRef.current); + }, 150); + }, []); + const { nodes, getTreeProps, @@ -819,19 +851,27 @@ function TasksTreeView({ getNodeProps={getNodeProps} getTreeProps={getTreeProps} parentClassName="pl-3" - renderNode={({ node, state, index }) => ( - <> -
{ - selectNode(node.id); - }} - > + renderNode={({ node, state, index }) => { + const isHovered = hoveredNodeId === node.id; + return ( + <> +
{ + selectNode(node.id); + }} + onMouseEnter={() => handleHoverChange(node.id)} + onMouseLeave={() => handleHoverChange(null)} + >
{Array.from({ length: node.level }).map((_, index) => (
- )} + ); + }} onScroll={(scrollTop) => { + handleScroll(scrollTop); //sync the scroll to the tree if (timelineScrollRef.current) { timelineScrollRef.current.scrollTop = scrollTop; @@ -920,6 +962,10 @@ function TasksTreeView({ treeScrollRef={treeScrollRef} virtualizer={virtualizer} toggleNodeSelection={toggleNodeSelection} + hoveredNodeId={hoveredNodeId} + setHoveredNodeId={setHoveredNodeId} + handleHoverChange={handleHoverChange} + handleScroll={handleScroll} /> @@ -985,6 +1031,10 @@ type TimelineViewProps = Pick< toggleNodeSelection: UseTreeStateOutput["toggleNodeSelection"]; showDurations: boolean; treeScrollRef: React.RefObject; + hoveredNodeId: string | null; + setHoveredNodeId: (nodeId: string | null) => void; + handleHoverChange: (nodeId: string | null) => void; + handleScroll: (scrollTop: number) => void; }; const tickCount = 5; @@ -1004,6 +1054,10 @@ function TimelineView({ showDurations, treeScrollRef, queuedDuration, + hoveredNodeId, + setHoveredNodeId, + handleHoverChange, + handleScroll, }: TimelineViewProps) { const timelineContainerRef = useRef(null); const initialTimelineDimensions = useInitialDimensions(timelineContainerRef); @@ -1149,6 +1203,7 @@ function TimelineView({ parentClassName="h-full scrollbar-hide" renderNode={({ node, state, index, virtualizer, virtualItem }) => { const isTopSpan = node.id === events[0]?.id; + const isHovered = hoveredNodeId === node.id; return ( console.log(`hover ${index}`)} onClick={(e) => { toggleNodeSelection(node.id); }} + onMouseEnter={() => handleHoverChange(node.id)} + onMouseLeave={() => handleHoverChange(null)} > {node.data.level === "TRACE" ? ( <> @@ -1273,6 +1333,7 @@ function TimelineView({ ); }} onScroll={(scrollTop) => { + handleScroll(scrollTop); //sync the scroll to the tree if (treeScrollRef.current) { treeScrollRef.current.scrollTop = scrollTop;