diff --git a/frontend/src/container/GoToTop/index.tsx b/frontend/src/container/GoToTop/index.tsx
new file mode 100644
index 0000000000..c1cc57973c
--- /dev/null
+++ b/frontend/src/container/GoToTop/index.tsx
@@ -0,0 +1,29 @@
+import { ArrowUpOutlined } from '@ant-design/icons';
+import { FloatButton } from 'antd';
+import { PANEL_TYPES } from 'constants/queryBuilder';
+// hooks
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import useScrollToTop from 'hooks/useScrollToTop';
+
+function GoToTop(): JSX.Element | null {
+ const { isVisible, scrollToTop } = useScrollToTop();
+
+ const { panelType } = useQueryBuilder();
+
+ if (!isVisible) return null;
+
+ if (panelType === PANEL_TYPES.LIST) {
+ return (
+ }
+ />
+ );
+ }
+
+ return null;
+}
+
+export default GoToTop;
diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx
index 45cbceaa45..4536b1be49 100644
--- a/frontend/src/container/LogsExplorerViews/index.tsx
+++ b/frontend/src/container/LogsExplorerViews/index.tsx
@@ -1,5 +1,4 @@
import { TabsProps } from 'antd';
-import axios from 'axios';
import LogDetail from 'components/LogDetail';
import TabLabel from 'components/TabLabel';
import { QueryParams } from 'constants/query';
@@ -13,6 +12,7 @@ import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import ROUTES from 'constants/routes';
import { DEFAULT_PER_PAGE_VALUE } from 'container/Controls/config';
import ExportPanel from 'container/ExportPanel';
+import GoToTop from 'container/GoToTop';
import LogsExplorerChart from 'container/LogsExplorerChart';
import LogsExplorerList from 'container/LogsExplorerList';
import LogsExplorerTable from 'container/LogsExplorerTable';
@@ -22,6 +22,7 @@ import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import useAxiosError from 'hooks/useAxiosError';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { chooseAutocompleteFromCustomValue } from 'lib/newQueryBuilder/chooseAutocompleteFromCustomValue';
@@ -81,6 +82,8 @@ function LogsExplorerViews(): JSX.Element {
const [logs, setLogs] = useState([]);
const [requestData, setRequestData] = useState(null);
+ const handleAxisError = useAxiosError();
+
const currentStagedQueryData = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length !== 1) return null;
@@ -357,16 +360,16 @@ function LogsExplorerViews(): JSX.Element {
history.push(dashboardEditView);
},
- onError: (error) => {
- if (axios.isAxiosError(error)) {
- notifications.error({
- message: error.message,
- });
- }
- },
+ onError: handleAxisError,
});
},
- [exportDefaultQuery, history, notifications, updateDashboard],
+ [
+ exportDefaultQuery,
+ history,
+ notifications,
+ updateDashboard,
+ handleAxisError,
+ ],
);
useEffect(() => {
@@ -511,6 +514,8 @@ function LogsExplorerViews(): JSX.Element {
onAddToQuery={handleAddToQuery}
onClickActionItem={handleAddToQuery}
/>
+
+
>
);
}
diff --git a/frontend/src/hooks/useScrollToTop/index.tsx b/frontend/src/hooks/useScrollToTop/index.tsx
new file mode 100644
index 0000000000..f28fbd66eb
--- /dev/null
+++ b/frontend/src/hooks/useScrollToTop/index.tsx
@@ -0,0 +1,29 @@
+import throttle from 'lodash-es/throttle';
+import { useEffect, useState } from 'react';
+
+import { UseScrollToTop } from './types';
+
+function useScrollToTop(visibleOffset = 200): UseScrollToTop {
+ const [isVisible, setIsVisible] = useState(false);
+
+ const scrollToTop = (): void => {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ };
+
+ useEffect(() => {
+ const toggleVisibility = throttle(() => {
+ setIsVisible(window.pageYOffset > visibleOffset);
+ }, 300);
+
+ window.addEventListener('scroll', toggleVisibility);
+
+ return (): void => window.removeEventListener('scroll', toggleVisibility);
+ }, [visibleOffset]);
+
+ return { isVisible, scrollToTop };
+}
+
+export default useScrollToTop;
diff --git a/frontend/src/hooks/useScrollToTop/types.ts b/frontend/src/hooks/useScrollToTop/types.ts
new file mode 100644
index 0000000000..6c106a2be9
--- /dev/null
+++ b/frontend/src/hooks/useScrollToTop/types.ts
@@ -0,0 +1,4 @@
+export interface UseScrollToTop {
+ isVisible: boolean;
+ scrollToTop: VoidFunction;
+}
diff --git a/frontend/src/hooks/useScrollToTop/useScrollToTop.test.ts b/frontend/src/hooks/useScrollToTop/useScrollToTop.test.ts
new file mode 100644
index 0000000000..03820f5963
--- /dev/null
+++ b/frontend/src/hooks/useScrollToTop/useScrollToTop.test.ts
@@ -0,0 +1,58 @@
+import { act, renderHook } from '@testing-library/react';
+
+import useScrollToTop from './index';
+
+// Mocking window.scrollTo method
+global.scrollTo = jest.fn();
+
+describe('useScrollToTop hook', () => {
+ beforeAll(() => {
+ jest.useFakeTimers();
+ });
+
+ it('should change visibility and scroll to top on call', () => {
+ const { result } = renderHook(() => useScrollToTop(100));
+
+ // Simulate scrolling 150px down
+ act(() => {
+ global.pageYOffset = 150;
+ global.dispatchEvent(new Event('scroll'));
+ jest.advanceTimersByTime(300);
+ });
+
+ expect(result.current.isVisible).toBe(true);
+
+ // Simulate scrolling to top
+ act(() => {
+ result.current.scrollToTop();
+ });
+
+ expect(global.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
+ });
+
+ it('should be invisible when scrolled less than offset', () => {
+ const { result } = renderHook(() => useScrollToTop(100));
+
+ // Simulate scrolling 50px down
+ act(() => {
+ global.pageYOffset = 50;
+ global.dispatchEvent(new Event('scroll'));
+ jest.advanceTimersByTime(300);
+ });
+
+ expect(result.current.isVisible).toBe(false);
+ });
+
+ it('should be visible when scrolled more than offset', () => {
+ const { result } = renderHook(() => useScrollToTop(100));
+
+ // Simulate scrolling 50px down
+ act(() => {
+ global.pageYOffset = 200;
+ global.dispatchEvent(new Event('scroll'));
+ jest.advanceTimersByTime(300);
+ });
+
+ expect(result.current.isVisible).toBe(true);
+ });
+});