-
Notifications
You must be signed in to change notification settings - Fork 16.4k
Default to latest logs and fix scroll bar jump performance #54174 #54447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
423405a
ac154bb
9cc119a
c6a63cb
43c02f4
b2e1c8a
5af219a
23832d8
7784f43
2c70a60
d15ee49
4e5c4cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -79,7 +79,8 @@ const ScrollToButton = ({ | |||
|
|
||||
| export const TaskLogContent = ({ error, isLoading, logError, parsedLogs, wrap }: Props) => { | ||||
| const hash = location.hash.replace("#", ""); | ||||
| const parentRef = useRef(null); | ||||
| const parentRef = useRef<HTMLDivElement | null>(null); | ||||
|
|
||||
| const rowVirtualizer = useVirtualizer({ | ||||
| count: parsedLogs.length, | ||||
| estimateSize: () => 20, | ||||
|
|
@@ -95,15 +96,103 @@ export const TaskLogContent = ({ error, isLoading, logError, parsedLogs, wrap }: | |||
| }, [rowVirtualizer, parsedLogs]); | ||||
|
|
||||
| useLayoutEffect(() => { | ||||
| if (location.hash && !isLoading) { | ||||
| rowVirtualizer.scrollToIndex(Math.min(Number(hash) + 5, parsedLogs.length - 1)); | ||||
| if (!location.hash || isLoading || parsedLogs.length === 0) { | ||||
| return; | ||||
| } | ||||
|
|
||||
| const targetIndex = Math.max(0, Math.min(parsedLogs.length - 1, Number(hash) || 0)); | ||||
| const el = parentRef.current; | ||||
|
|
||||
| const total = rowVirtualizer.getTotalSize(); | ||||
| const clientH = el?.clientHeight ?? 0; | ||||
|
|
||||
| const vItem = rowVirtualizer.getVirtualItems().find((virtualRow) => virtualRow.index === targetIndex); | ||||
| const approxPerItem = 20; | ||||
| const anchor = vItem?.start ?? targetIndex * approxPerItem; | ||||
|
|
||||
| const offset = Math.max(0, Math.min(total - clientH, anchor)); | ||||
|
|
||||
| if (el) { | ||||
| if (typeof rowVirtualizer.scrollToOffset === "function") { | ||||
| try { | ||||
| rowVirtualizer.scrollToOffset(offset); | ||||
| } catch { | ||||
| rowVirtualizer.scrollToIndex(targetIndex, { align: "start" }); | ||||
| } | ||||
| } else { | ||||
| rowVirtualizer.scrollToIndex(targetIndex, { align: "start" }); | ||||
| } | ||||
|
|
||||
| el.scrollTop = offset; | ||||
|
|
||||
| requestAnimationFrame(() => { | ||||
| el.scrollTop = offset; | ||||
| }); | ||||
| } else { | ||||
| rowVirtualizer.scrollToIndex(targetIndex, { align: "start" }); | ||||
| } | ||||
| }, [isLoading, rowVirtualizer, hash, parsedLogs]); | ||||
|
|
||||
| const handleScrollTo = (to: "bottom" | "top") => { | ||||
| if (parsedLogs.length > 0) { | ||||
| rowVirtualizer.scrollToIndex(to === "bottom" ? parsedLogs.length - 1 : 0); | ||||
| if (parsedLogs.length === 0) { | ||||
| return; | ||||
| } | ||||
|
|
||||
| const el = rowVirtualizer.scrollElement ?? parentRef.current; | ||||
|
|
||||
| if (!el) { | ||||
| return; | ||||
| } | ||||
|
|
||||
| if (to === "top") { | ||||
| if (typeof rowVirtualizer.scrollToOffset === "function") { | ||||
| try { | ||||
| rowVirtualizer.scrollToOffset(0); | ||||
| } catch { | ||||
| rowVirtualizer.scrollToIndex(0, { align: "start" }); | ||||
| } | ||||
| } else { | ||||
| rowVirtualizer.scrollToIndex(0, { align: "start" }); | ||||
| } | ||||
| el.scrollTop = 0; | ||||
| requestAnimationFrame(() => { | ||||
| el.scrollTop = 0; | ||||
| }); | ||||
|
|
||||
| return; | ||||
| } | ||||
|
|
||||
| // === bottom === (instant jump even for huge lists) | ||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a comment for bottom, but not for top. Remove it
Suggested change
|
||||
| const total = rowVirtualizer.getTotalSize(); | ||||
| const clientH = el.clientHeight || 0; | ||||
| const offset = Math.max(0, Math.floor(total - clientH)); | ||||
|
|
||||
| // First tell the virtualizer where we want to be | ||||
| if (typeof rowVirtualizer.scrollToOffset === "function") { | ||||
| try { | ||||
| rowVirtualizer.scrollToOffset(offset); | ||||
| } catch { | ||||
| rowVirtualizer.scrollToIndex(parsedLogs.length - 1, { align: "end" }); | ||||
| } | ||||
|
Comment on lines
+172
to
+176
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be enough. We can remove the |
||||
| } else { | ||||
| rowVirtualizer.scrollToIndex(parsedLogs.length - 1, { align: "end" }); | ||||
| } | ||||
|
|
||||
| el.scrollTop = offset; | ||||
|
|
||||
| requestAnimationFrame(() => { | ||||
| el.scrollTop = offset; | ||||
| requestAnimationFrame(() => { | ||||
| el.scrollTop = offset; | ||||
| const lastItem = el.querySelector<HTMLElement>( | ||||
| `[data-testid="virtualized-item-${parsedLogs.length - 1}"]`, | ||||
| ); | ||||
|
|
||||
| if (lastItem) { | ||||
| lastItem.scrollIntoView({ behavior: "auto", block: "end" }); | ||||
| } | ||||
| }); | ||||
| }); | ||||
|
Comment on lines
+183
to
+195
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are those |
||||
| }; | ||||
|
|
||||
| useHotkeys("mod+ArrowDown", () => handleScrollTo("bottom"), { enabled: !isLoading }); | ||||
|
|
@@ -113,50 +202,54 @@ export const TaskLogContent = ({ error, isLoading, logError, parsedLogs, wrap }: | |||
| <Box display="flex" flexDirection="column" flexGrow={1} h="100%" minHeight={0} position="relative"> | ||||
| <ErrorAlert error={error ?? logError} /> | ||||
| <ProgressBar size="xs" visibility={isLoading ? "visible" : "hidden"} /> | ||||
| <Code | ||||
| css={{ | ||||
| "& *::selection": { | ||||
| bg: "blue.subtle", | ||||
| }, | ||||
| }} | ||||
| data-testid="virtualized-list" | ||||
|
|
||||
| <Box | ||||
| data-testid="virtual-scroll-container" | ||||
| flexGrow={1} | ||||
| h="auto" | ||||
| minHeight={0} | ||||
| overflow="auto" | ||||
| position="relative" | ||||
| py={3} | ||||
| ref={parentRef} | ||||
| textWrap={wrap ? "pre" : "nowrap"} | ||||
| width="100%" | ||||
| > | ||||
| <VStack alignItems="flex-start" gap={0} h={`${rowVirtualizer.getTotalSize()}px`}> | ||||
| {rowVirtualizer.getVirtualItems().map((virtualRow) => ( | ||||
| <Box | ||||
| _ltr={{ | ||||
| left: 0, | ||||
| right: "auto", | ||||
| }} | ||||
| _rtl={{ | ||||
| left: "auto", | ||||
| right: 0, | ||||
| }} | ||||
| bgColor={ | ||||
| Boolean(hash) && virtualRow.index === Number(hash) - 1 ? "blue.emphasized" : "transparent" | ||||
| } | ||||
| data-index={virtualRow.index} | ||||
| data-testid={`virtualized-item-${virtualRow.index}`} | ||||
| key={virtualRow.key} | ||||
| position="absolute" | ||||
| ref={rowVirtualizer.measureElement} | ||||
| top={0} | ||||
| transform={`translateY(${virtualRow.start}px)`} | ||||
| width={wrap ? "100%" : "max-content"} | ||||
| > | ||||
| {parsedLogs[virtualRow.index] ?? undefined} | ||||
| </Box> | ||||
| ))} | ||||
| </VStack> | ||||
| </Code> | ||||
| <Code | ||||
| css={{ | ||||
| "& *::selection": { bg: "blue.subtle" }, | ||||
| }} | ||||
| data-testid="virtualized-list" | ||||
| display="block" | ||||
| textWrap={wrap ? "pre" : "nowrap"} | ||||
| width="100%" | ||||
| > | ||||
| <VStack | ||||
| alignItems="flex-start" | ||||
| gap={0} | ||||
| h={`${rowVirtualizer.getTotalSize()}px`} | ||||
| position="relative" | ||||
| > | ||||
| {rowVirtualizer.getVirtualItems().map((virtualRow) => ( | ||||
| <Box | ||||
| _ltr={{ left: 0, right: "auto" }} | ||||
| _rtl={{ left: "auto", right: 0 }} | ||||
| bgColor={ | ||||
| Boolean(hash) && virtualRow.index === Number(hash) - 1 ? "blue.emphasized" : "transparent" | ||||
| } | ||||
| data-index={virtualRow.index} | ||||
| data-testid={`virtualized-item-${virtualRow.index}`} | ||||
| key={virtualRow.key} | ||||
| position="absolute" | ||||
| ref={rowVirtualizer.measureElement} | ||||
| top={0} | ||||
| transform={`translateY(${virtualRow.start}px)`} | ||||
| width={wrap ? "100%" : "max-content"} | ||||
| > | ||||
| {parsedLogs[virtualRow.index] ?? undefined} | ||||
| </Box> | ||||
| ))} | ||||
| </VStack> | ||||
| </Code> | ||||
| </Box> | ||||
|
|
||||
| {showScrollButtons ? ( | ||||
| <> | ||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This breaks the direct link to lines. We used to add 5 more lines so it wouldn't be on the
edgeof the container, f screenshot.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both clicking on a log line, or refreshing the page with a URL with a line number specified will not land where we want. http://localhost:28080/dags/big_logs/runs/manual__2025-09-02T12:02:44.060539+00:00/tasks/my_task?try_number=1#4806
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use something like https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-margin ?