Fix the logs after call not triggering when scrolled to bottom (#22617)

This commit is contained in:
Aniket Katkar 2025-08-03 17:47:45 +05:30 committed by GitHub
parent 787ab807b1
commit 6b2b8d2ea0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 193 additions and 38 deletions

View File

@ -36,6 +36,7 @@ import {
getExternalApplicationRuns,
getLatestApplicationRuns,
} from '../../rest/applicationAPI';
import { getIngestionPipelineLogById } from '../../rest/ingestionPipelineAPI';
import LogsViewerPage from './LogsViewerPage';
const mockScrollToIndex = jest.fn();
@ -106,24 +107,53 @@ jest.mock('../../hooks/useDownloadProgressStore', () => ({
})),
}));
jest.mock('@melloware/react-logviewer', () => ({
LazyLog: React.forwardRef(({ text }: { text: string }, ref) => {
// Mock the ref structure that the component expects
if (ref) {
(ref as { current: Record<string, unknown> }).current = {
state: {
count: 230,
},
listRef: {
current: {
scrollToIndex: mockScrollToIndex,
},
},
};
}
let mockScrollPosition = {
scrollTop: 80,
scrollHeight: 100,
clientHeight: 20,
};
return <div data-testid="lazy-log">{text}</div>;
}),
jest.mock('@melloware/react-logviewer', () => ({
LazyLog: React.forwardRef(
(
{
text,
onScroll,
}: {
text: string;
onScroll: (args: {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}) => void;
},
ref
) => {
// Mock the ref structure that the component expects
if (ref) {
(ref as { current: Record<string, unknown> }).current = {
state: {
count: 230,
},
listRef: {
current: {
scrollToIndex: mockScrollToIndex,
},
},
};
}
return (
<div data-testid="lazy-log">
{text}
<div
data-testid="scroll-container"
onClick={() => onScroll(mockScrollPosition)}
/>
</div>
);
}
),
}));
describe('LogsViewerPage.component', () => {
@ -198,4 +228,138 @@ describe('LogsViewerPage.component', () => {
// Verify that scrollToIndex was called with the correct parameter (totalLines - 1)
expect(mockScrollToIndex).toHaveBeenCalledWith(229);
});
it('should call handleScroll when user scrolls at the bottom', async () => {
(useParams as jest.Mock).mockReturnValue({
logEntityType: 'TestSuite',
ingestionName: 'ingestion_123456',
});
await act(async () => {
render(<LogsViewerPage />);
});
await waitFor(() => {
expect(screen.getByTestId('scroll-container')).toBeInTheDocument();
});
const scrollContainer = screen.getByTestId('scroll-container');
await act(async () => {
fireEvent.click(scrollContainer);
});
expect(getIngestionPipelineLogById).toHaveBeenCalledWith(
'c379d75a-43cd-4d93-a799-0bba4a22c690',
'1'
);
});
it('should not call handleScroll when user scrolls and is not at the bottom', async () => {
// Set scroll position to NOT be at the bottom
mockScrollPosition = { scrollTop: 10, scrollHeight: 100, clientHeight: 20 };
await act(async () => {
render(<LogsViewerPage />);
});
await waitFor(() => {
expect(screen.getByTestId('scroll-container')).toBeInTheDocument();
});
const scrollContainer = screen.getByTestId('scroll-container');
// Simulate scroll that is NOT at the bottom
await act(async () => {
fireEvent.click(scrollContainer);
});
// Verify that the API was not called since we're not at the bottom
expect(getIngestionPipelineLogById).not.toHaveBeenCalledWith(
'c379d75a-43cd-4d93-a799-0bba4a22c690',
'1'
);
});
it('should not call handleScroll when user scrolls and is at the bottom with 40- margin', async () => {
mockScrollPosition = { scrollTop: 41, scrollHeight: 100, clientHeight: 20 };
await act(async () => {
render(<LogsViewerPage />);
});
await waitFor(() => {
expect(screen.getByTestId('scroll-container')).toBeInTheDocument();
});
const scrollContainer = screen.getByTestId('scroll-container');
// Simulate scroll that is NOT at the bottom
await act(async () => {
fireEvent.click(scrollContainer);
});
// Verify that the API was not called since we're not at the bottom
expect(getIngestionPipelineLogById).toHaveBeenCalledWith(
'c379d75a-43cd-4d93-a799-0bba4a22c690',
'1'
);
});
it('should not call handleScroll when user scrolls and is at the bottom with 40 margin', async () => {
mockScrollPosition = { scrollTop: 40, scrollHeight: 100, clientHeight: 20 };
await act(async () => {
render(<LogsViewerPage />);
});
await waitFor(() => {
expect(screen.getByTestId('scroll-container')).toBeInTheDocument();
});
const scrollContainer = screen.getByTestId('scroll-container');
// Simulate scroll that is NOT at the bottom
await act(async () => {
fireEvent.click(scrollContainer);
});
// Verify that the API was not called since we're not at the bottom
expect(getIngestionPipelineLogById).not.toHaveBeenCalledWith(
'c379d75a-43cd-4d93-a799-0bba4a22c690',
'1'
);
});
it('should not call handleScroll when user scrolls and is at the bottom but after is undefined', async () => {
mockScrollPosition = { scrollTop: 80, scrollHeight: 100, clientHeight: 20 };
(getIngestionPipelineLogById as jest.Mock).mockImplementation(() =>
Promise.resolve({
data: {
...mockLogsData,
after: undefined,
},
})
);
await act(async () => {
render(<LogsViewerPage />);
});
await waitFor(() => {
expect(screen.getByTestId('scroll-container')).toBeInTheDocument();
});
const scrollContainer = screen.getByTestId('scroll-container');
// Simulate scroll that is NOT at the bottom
await act(async () => {
fireEvent.click(scrollContainer);
});
// Verify that the API was not called since we're not at the bottom
expect(getIngestionPipelineLogById).not.toHaveBeenCalledWith(
'c379d75a-43cd-4d93-a799-0bba4a22c690',
'1'
);
});
});

View File

@ -217,13 +217,17 @@ const LogsViewerPage = () => {
}
}, []);
const handleScroll = (event: Event) => {
const targetElement = event.target as HTMLDivElement;
const scrollTop = targetElement.scrollTop;
const scrollHeight = targetElement.scrollHeight;
const clientHeight = targetElement.clientHeight;
const isBottom = clientHeight + scrollTop === scrollHeight;
const handleScroll = (scrollValues: {
scrollTop: number;
scrollHeight: number;
clientHeight: number;
}) => {
const scrollTop = scrollValues.scrollTop;
const scrollHeight = scrollValues.scrollHeight;
const clientHeight = scrollValues.clientHeight;
// Fetch more logs when user is at the bottom of the log
// with a margin of about 40px (approximate height of one line)
const isBottom = Math.abs(clientHeight + scrollTop - scrollHeight) < 40;
if (
!isLogsLoading &&
@ -238,20 +242,6 @@ const LogsViewerPage = () => {
return;
};
useLayoutEffect(() => {
const logBody = document.getElementsByClassName(
'ReactVirtualized__Grid'
)[0];
if (logBody) {
logBody.addEventListener('scroll', handleScroll, { passive: true });
}
return () => {
logBody && logBody.removeEventListener('scroll', handleScroll);
};
});
useLayoutEffect(() => {
const lazyLogSearchBarInput = document.getElementsByClassName(
'react-lazylog-searchbar-input'
@ -420,6 +410,7 @@ const LogsViewerPage = () => {
extraLines={1} // 1 is to be add so that linux users can see last line of the log
ref={lazyLogRef}
text={logs}
onScroll={handleScroll}
/>
</Col>
</Row>