From 06ef9ff384ec412adf591e66c43207c60d26e1ec Mon Sep 17 00:00:00 2001
From: Amlan Kumar Nandy <45410599+amlannandy@users.noreply.github.com>
Date: Mon, 7 Jul 2025 23:51:06 +0700
Subject: [PATCH] fix: resolve ui full reload on auto-refresh (#8383)
---
.../HostMetricTraces/HostMetricTraces.tsx | 4 +-
.../HostMetricsDetail/HostMetricsDetails.tsx | 17 +++-
.../HostMetricsDetail/Metrics/Metrics.tsx | 24 +++--
.../InfraMonitoringHosts/HostsList.tsx | 33 ++++++-
.../HostsListControls.tsx | 4 +-
.../InfraMonitoringHosts/HostsListTable.tsx | 10 +-
.../__tests__/HostsListControl.test.tsx | 1 +
.../__tests__/HostsListTable.test.tsx | 38 ++++++-
.../ClusterDetails/ClusterDetails.tsx | 17 +++-
.../Clusters/K8sClustersList.tsx | 98 +++++++++++++++----
.../DaemonSetDetails/DaemonSetDetails.tsx | 17 +++-
.../DaemonSets/K8sDaemonSetsList.tsx | 98 +++++++++++++++----
.../DeploymentDetails/DeploymentDetails.tsx | 17 +++-
.../Deployments/K8sDeploymentsList.tsx | 98 +++++++++++++++----
.../EntityEvents/EntityEvents.tsx | 2 +-
.../EntityMetrics/EntityMetrics.tsx | 27 +++--
.../EntityTraces/EntityTraces.tsx | 4 +-
.../Jobs/JobDetails/JobDetails.tsx | 17 +++-
.../InfraMonitoringK8s/Jobs/K8sJobsList.tsx | 57 ++++++++++-
.../InfraMonitoringK8s/K8sHeader.tsx | 4 +-
.../Namespaces/K8sNamespacesList.tsx | 98 +++++++++++++++----
.../NamespaceDetails/NamespaceDetails.tsx | 17 +++-
.../InfraMonitoringK8s/Nodes/K8sNodesList.tsx | 98 +++++++++++++++----
.../Nodes/NodeDetails/NodeDetails.tsx | 17 +++-
.../InfraMonitoringK8s/Pods/K8sPodLists.tsx | 91 +++++++++++++----
.../Pods/PodDetails/PodDetails.tsx | 17 +++-
.../StatefulSets/K8sStatefulSetsList.tsx | 98 +++++++++++++++----
.../StatefulSetDetails/StatefulSetDetails.tsx | 17 +++-
.../Volumes/K8sVolumesList.tsx | 35 ++++---
.../Volumes/VolumeDetails/VolumeDetails.tsx | 17 +++-
.../src/hooks/useMultiIntersectionObserver.ts | 54 ++++++++++
31 files changed, 921 insertions(+), 225 deletions(-)
create mode 100644 frontend/src/hooks/useMultiIntersectionObserver.ts
diff --git a/frontend/src/components/HostMetricsDetail/HostMetricTraces/HostMetricTraces.tsx b/frontend/src/components/HostMetricsDetail/HostMetricTraces/HostMetricTraces.tsx
index e5a75cc9b037..482e19f9b93f 100644
--- a/frontend/src/components/HostMetricsDetail/HostMetricTraces/HostMetricTraces.tsx
+++ b/frontend/src/components/HostMetricsDetail/HostMetricTraces/HostMetricTraces.tsx
@@ -194,7 +194,7 @@ function HostMetricTraces({
{!isError && traces.length > 0 && (
=> ({
diff --git a/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx b/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx
index 28d055b2eb76..1c72ef9fa81a 100644
--- a/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx
+++ b/frontend/src/components/HostMetricsDetail/HostMetricsDetails.tsx
@@ -37,7 +37,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -86,8 +86,12 @@ function HostMetricsDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
{queries.map((query, idx) => (
-
+
{hostWidgetInfo[idx].title}
{renderCardContent(query, idx)}
diff --git a/frontend/src/container/InfraMonitoringHosts/HostsList.tsx b/frontend/src/container/InfraMonitoringHosts/HostsList.tsx
index f67b47c7f786..269a0f9d079d 100644
--- a/frontend/src/container/InfraMonitoringHosts/HostsList.tsx
+++ b/frontend/src/container/InfraMonitoringHosts/HostsList.tsx
@@ -96,11 +96,41 @@ function HostsList(): JSX.Element {
};
}, [pageSize, currentPage, filters, minTime, maxTime, orderBy]);
+ const queryKey = useMemo(() => {
+ if (selectedHostName) {
+ return [
+ 'hostList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(filters),
+ JSON.stringify(orderBy),
+ ];
+ }
+ return [
+ 'hostList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(filters),
+ JSON.stringify(orderBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ pageSize,
+ currentPage,
+ filters,
+ orderBy,
+ selectedHostName,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetHostList(
query as HostListPayload,
{
- queryKey: ['hostList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
);
@@ -212,6 +242,7 @@ function HostsList(): JSX.Element {
void;
filters: IBuilderQuery['filters'];
+ showAutoRefresh: boolean;
}): JSX.Element {
const currentQuery = initialQueriesMap[DataSource.METRICS];
const updatedCurrentQuery = useMemo(
@@ -58,7 +60,7 @@ function HostsListControls({
diff --git a/frontend/src/container/InfraMonitoringHosts/HostsListTable.tsx b/frontend/src/container/InfraMonitoringHosts/HostsListTable.tsx
index 5c7d3bbe17c7..513444f69e57 100644
--- a/frontend/src/container/InfraMonitoringHosts/HostsListTable.tsx
+++ b/frontend/src/container/InfraMonitoringHosts/HostsListTable.tsx
@@ -93,9 +93,13 @@ export default function HostsListTable({
const showHostsEmptyState =
!isFetching &&
!isLoading &&
+ formattedHostMetricsData.length === 0 &&
(!sentAnyHostMetricsData || isSendingIncorrectK8SAgentMetrics) &&
!filters.items.length;
+ const showTableLoadingState =
+ (isLoading || isFetching) && formattedHostMetricsData.length === 0;
+
if (isError) {
return
{data?.error || 'Something went wrong'};
}
@@ -127,7 +131,7 @@ export default function HostsListTable({
);
}
- if (isLoading || isFetching) {
+ if (showTableLoadingState) {
return (
} />,
}}
tableLayout="fixed"
diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx
index d5bad4c81f8f..be299a253ce1 100644
--- a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListControl.test.tsx
@@ -28,6 +28,7 @@ describe('HostsListControls', () => {
,
);
diff --git a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx
index 1f71b947c51a..8736b1740d87 100644
--- a/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx
+++ b/frontend/src/container/InfraMonitoringHosts/__tests__/HostsListTable.test.tsx
@@ -59,13 +59,27 @@ describe('HostsListTable', () => {
setPageSize: mockSetPageSize,
} as any;
- it('renders loading state if isLoading is true', () => {
- const { container } = render(
);
+ it('renders loading state if isLoading is true and tableData is empty', () => {
+ const { container } = render(
+
,
+ );
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
});
- it('renders loading state if isFetching is true', () => {
- const { container } = render(
);
+ it('renders loading state if isFetching is true and tableData is empty', () => {
+ const { container } = render(
+
,
+ );
expect(container.querySelector('.hosts-list-loading-state')).toBeTruthy();
});
@@ -75,7 +89,17 @@ describe('HostsListTable', () => {
});
it('renders empty state if no hosts are found', () => {
- const { container } = render(
);
+ const { container } = render(
+
,
+ );
expect(container.querySelector(EMPTY_STATE_CONTAINER_CLASS)).toBeTruthy();
});
@@ -83,6 +107,7 @@ describe('HostsListTable', () => {
const { container } = render(
{
data: {
...mockTableData.payload.data,
sentAnyHostMetricsData: false,
+ hosts: [],
},
},
}}
@@ -102,6 +128,7 @@ describe('HostsListTable', () => {
const { container } = render(
{
data: {
...mockTableData.payload.data,
isSendingIncorrectK8SAgentMetrics: true,
+ hosts: [],
},
},
}}
diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx
index b0712c94e4d6..b528983448da 100644
--- a/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails.tsx
@@ -38,7 +38,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -85,8 +85,12 @@ function ClusterDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -195,10 +199,11 @@ function ClusterDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -226,6 +231,7 @@ function ClusterDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -462,6 +468,7 @@ function ClusterDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx
index 9fe85939aa9d..9f114b7149cf 100644
--- a/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Clusters/K8sClustersList.tsx
@@ -189,6 +189,32 @@ function K8sClustersList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedClusterName) {
+ return [
+ 'clusterList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'clusterList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ queryFilters,
+ orderBy,
+ selectedClusterName,
+ minTime,
+ maxTime,
+ selectedRowData,
+ ]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -198,7 +224,7 @@ function K8sClustersList({
} = useGetK8sClustersList(
fetchGroupedByRowDataQuery as K8sClustersListPayload,
{
- queryKey: ['clusterList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -254,11 +280,44 @@ function K8sClustersList({
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
+ const queryKey = useMemo(() => {
+ if (selectedClusterName) {
+ return [
+ 'clusterList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'clusterList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedClusterName,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sClustersList(
query as K8sClustersListPayload,
{
- queryKey: ['clusterList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -583,6 +642,9 @@ function K8sClustersList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedClustersData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx
index 7b347835249a..37a512fd74c1 100644
--- a/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails.tsx
@@ -33,7 +33,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -84,8 +84,12 @@ function DaemonSetDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -211,10 +215,11 @@ function DaemonSetDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -242,6 +247,7 @@ function DaemonSetDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -476,6 +482,7 @@ function DaemonSetDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/DaemonSets/K8sDaemonSetsList.tsx b/frontend/src/container/InfraMonitoringK8s/DaemonSets/K8sDaemonSetsList.tsx
index 7084c4ba096b..2f4230943221 100644
--- a/frontend/src/container/InfraMonitoringK8s/DaemonSets/K8sDaemonSetsList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/DaemonSets/K8sDaemonSetsList.tsx
@@ -191,6 +191,32 @@ function K8sDaemonSetsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedDaemonSetUID) {
+ return [
+ 'daemonSetList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'daemonSetList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ queryFilters,
+ orderBy,
+ selectedDaemonSetUID,
+ minTime,
+ maxTime,
+ selectedRowData,
+ ]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -200,7 +226,7 @@ function K8sDaemonSetsList({
} = useGetK8sDaemonSetsList(
fetchGroupedByRowDataQuery as K8sDaemonSetsListPayload,
{
- queryKey: ['daemonSetList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -251,11 +277,44 @@ function K8sDaemonSetsList({
[groupedByRowData, groupBy],
);
+ const queryKey = useMemo(() => {
+ if (selectedDaemonSetUID) {
+ return [
+ 'daemonSetList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'daemonSetList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedDaemonSetUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sDaemonSetsList(
query as K8sDaemonSetsListPayload,
{
- queryKey: ['daemonSetList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -591,6 +650,9 @@ function K8sDaemonSetsList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedDaemonSetsData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
@@ -610,7 +673,7 @@ function K8sDaemonSetsList({
className={classNames('k8s-list-table', 'daemonSets-list-table', {
'expanded-daemonsets-list-table': isGroupedByAttribute,
})}
- dataSource={isFetching || isLoading ? [] : formattedDaemonSetsData}
+ dataSource={showTableLoadingState ? [] : formattedDaemonSetsData}
columns={columns}
pagination={{
current: currentPage,
@@ -622,26 +685,25 @@ function K8sDaemonSetsList({
}}
scroll={{ x: true }}
loading={{
- spinning: isFetching || isLoading,
+ spinning: showTableLoadingState,
indicator:
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx
index 951b4cf7dd89..a3cfd58dd6e8 100644
--- a/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails.tsx
@@ -38,7 +38,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -88,8 +88,12 @@ function DeploymentDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -215,10 +219,11 @@ function DeploymentDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -246,6 +251,7 @@ function DeploymentDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -487,6 +493,7 @@ function DeploymentDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx
index 215842b89735..54f6bf605882 100644
--- a/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Deployments/K8sDeploymentsList.tsx
@@ -192,6 +192,32 @@ function K8sDeploymentsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedDeploymentUID) {
+ return [
+ 'deploymentList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'deploymentList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ queryFilters,
+ orderBy,
+ selectedDeploymentUID,
+ minTime,
+ maxTime,
+ selectedRowData,
+ ]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -201,7 +227,7 @@ function K8sDeploymentsList({
} = useGetK8sDeploymentsList(
fetchGroupedByRowDataQuery as K8sDeploymentsListPayload,
{
- queryKey: ['deploymentList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -252,11 +278,44 @@ function K8sDeploymentsList({
[groupedByRowData, groupBy],
);
+ const queryKey = useMemo(() => {
+ if (selectedDeploymentUID) {
+ return [
+ 'deploymentList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'deploymentList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedDeploymentUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sDeploymentsList(
query as K8sDeploymentsListPayload,
{
- queryKey: ['deploymentList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -596,6 +655,9 @@ function K8sDeploymentsList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedDeploymentsData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
@@ -615,7 +678,7 @@ function K8sDeploymentsList({
className={classNames('k8s-list-table', 'deployments-list-table', {
'expanded-deployments-list-table': isGroupedByAttribute,
})}
- dataSource={isFetching || isLoading ? [] : formattedDeploymentsData}
+ dataSource={showTableLoadingState ? [] : formattedDeploymentsData}
columns={columns}
pagination={{
current: currentPage,
@@ -627,26 +690,25 @@ function K8sDeploymentsList({
}}
scroll={{ x: true }}
loading={{
- spinning: isFetching || isLoading,
+ spinning: showTableLoadingState,
indicator:
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx
index 20037b8956b9..ce6d34b98a88 100644
--- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents/EntityEvents.tsx
@@ -270,7 +270,7 @@ export default function Events({
- {isLoading && }
+ {isLoading && formattedEntityEvents.length === 0 && }
{!isLoading && !isError && formattedEntityEvents.length === 0 && (
diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/EntityMetrics.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/EntityMetrics.tsx
index d90ad708e077..5bcd0bb2f7b1 100644
--- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/EntityMetrics.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics/EntityMetrics.tsx
@@ -24,12 +24,13 @@ import {
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { useQueries, UseQueryResult } from 'react-query';
+import { QueryFunctionContext, useQueries, UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Options } from 'uplot';
import { FeatureKeys } from '../../../../constants/features';
+import { useMultiIntersectionObserver } from '../../../../hooks/useMultiIntersectionObserver';
import { useAppContext } from '../../../../providers/App/App';
interface EntityMetricsProps {
@@ -73,6 +74,12 @@ function EntityMetrics({
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
+
+ const {
+ visibilities,
+ setElement,
+ } = useMultiIntersectionObserver(entityWidgetInfo.length, { threshold: 0.1 });
+
const queryPayloads = useMemo(
() =>
getEntityQueryPayload(
@@ -91,11 +98,15 @@ function EntityMetrics({
);
const queries = useQueries(
- queryPayloads.map((payload) => ({
+ queryPayloads.map((payload, index) => ({
queryKey: [queryKey, payload, ENTITY_VERSION_V4, category],
- queryFn: (): Promise> =>
- GetMetricQueryRange(payload, ENTITY_VERSION_V4),
- enabled: !!payload,
+ queryFn: ({
+ signal,
+ }: QueryFunctionContext): Promise<
+ SuccessResponse
+ > => GetMetricQueryRange(payload, ENTITY_VERSION_V4, signal),
+ enabled: !!payload && visibilities[index],
+ keepPreviousData: true,
})),
);
@@ -186,7 +197,7 @@ function EntityMetrics({
query: UseQueryResult, unknown>,
idx: number,
): JSX.Element => {
- if (query.isLoading) {
+ if ((!query.data && query.isLoading) || !visibilities[idx]) {
return ;
}
@@ -196,7 +207,7 @@ function EntityMetrics({
return {errorMessage}
;
}
- const { panelType } = (query.data?.params as any).compositeQuery;
+ const panelType = (query.data?.params as any)?.compositeQuery?.panelType;
return (
({
{queries.map((query, idx) => (
-
+
{entityWidgetInfo[idx].title}
{renderCardContent(query, idx)}
diff --git a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx
index e0c367bf0c79..41a5ee014c41 100644
--- a/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces/EntityTraces.tsx
@@ -203,7 +203,7 @@ function EntityTraces({
{!isError && traces.length > 0 && (
=> ({
diff --git a/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails.tsx
index 47063f487db1..a5abf40d3995 100644
--- a/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails.tsx
@@ -33,7 +33,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -81,8 +81,12 @@ function JobDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -204,10 +208,11 @@ function JobDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -235,6 +240,7 @@ function JobDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -469,6 +475,7 @@ function JobDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx b/frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx
index b413cb536379..95b674268de1 100644
--- a/frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Jobs/K8sJobsList.tsx
@@ -186,6 +186,25 @@ function K8sJobsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedJobUID) {
+ return [
+ 'jobList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'jobList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [queryFilters, orderBy, selectedJobUID, minTime, maxTime, selectedRowData]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -195,7 +214,7 @@ function K8sJobsList({
} = useGetK8sJobsList(
fetchGroupedByRowDataQuery as K8sJobsListPayload,
{
- queryKey: ['jobList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -251,11 +270,44 @@ function K8sJobsList({
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
+ const queryKey = useMemo(() => {
+ if (selectedJobUID) {
+ return [
+ 'jobList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'jobList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedJobUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sJobsList(
query as K8sJobsListPayload,
{
- queryKey: ['jobList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -581,6 +633,7 @@ function K8sJobsList({
handleGroupByChange={handleGroupByChange}
selectedGroupBy={groupBy}
entity={K8sCategory.JOBS}
+ showAutoRefresh={!selectedJobData}
/>
{isError && {data?.error || 'Something went wrong'}}
diff --git a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx
index f22519285f94..273ee018579d 100644
--- a/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/K8sHeader.tsx
@@ -30,6 +30,7 @@ interface K8sHeaderProps {
handleFilterVisibilityChange: () => void;
isFiltersVisible: boolean;
entity: K8sCategory;
+ showAutoRefresh: boolean;
}
function K8sHeader({
@@ -46,6 +47,7 @@ function K8sHeader({
handleFilterVisibilityChange,
isFiltersVisible,
entity,
+ showAutoRefresh,
}: K8sHeaderProps): JSX.Element {
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
@@ -136,7 +138,7 @@ function K8sHeader({
diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx
index e1c1b6ecbac9..6bb3476f6a59 100644
--- a/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/K8sNamespacesList.tsx
@@ -190,6 +190,32 @@ function K8sNamespacesList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedNamespaceUID) {
+ return [
+ 'namespaceList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'namespaceList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ queryFilters,
+ orderBy,
+ selectedNamespaceUID,
+ minTime,
+ maxTime,
+ selectedRowData,
+ ]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -199,7 +225,7 @@ function K8sNamespacesList({
} = useGetK8sNamespacesList(
fetchGroupedByRowDataQuery as K8sNamespacesListPayload,
{
- queryKey: ['namespaceList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -250,11 +276,44 @@ function K8sNamespacesList({
[groupedByRowData, groupBy],
);
+ const queryKey = useMemo(() => {
+ if (selectedNamespaceUID) {
+ return [
+ 'namespaceList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'namespaceList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedNamespaceUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sNamespacesList(
query as K8sNamespacesListPayload,
{
- queryKey: ['namespaceList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -592,6 +651,9 @@ function K8sNamespacesList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedNamespacesData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx
index f64057e4ce41..802821b27422 100644
--- a/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails.tsx
@@ -35,7 +35,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -85,8 +85,12 @@ function NamespaceDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -195,10 +199,11 @@ function NamespaceDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -226,6 +231,7 @@ function NamespaceDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -461,6 +467,7 @@ function NamespaceDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx
index ca83a54e9b29..84e374e23ff8 100644
--- a/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Nodes/K8sNodesList.tsx
@@ -184,6 +184,32 @@ function K8sNodesList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedNodeUID) {
+ return [
+ 'nodeList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'nodeList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ queryFilters,
+ orderBy,
+ selectedNodeUID,
+ minTime,
+ maxTime,
+ selectedRowData,
+ ]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -193,7 +219,7 @@ function K8sNodesList({
} = useGetK8sNodesList(
fetchGroupedByRowDataQuery as K8sNodesListPayload,
{
- queryKey: ['nodeList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -249,11 +275,44 @@ function K8sNodesList({
[groupedByRowData, groupBy],
);
+ const queryKey = useMemo(() => {
+ if (selectedNodeUID) {
+ return [
+ 'nodeList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'nodeList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedNodeUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sNodesList(
query as K8sNodesListPayload,
{
- queryKey: ['nodeList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -571,6 +630,9 @@ function K8sNodesList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedNodesData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx
index a216ea010cbe..5d66aaae2ecb 100644
--- a/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails.tsx
@@ -38,7 +38,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -85,8 +85,12 @@ function NodeDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -195,10 +199,11 @@ function NodeDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -226,6 +231,7 @@ function NodeDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -464,6 +470,7 @@ function NodeDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx
index 6173ffef770d..6a0084ee8e21 100644
--- a/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Pods/K8sPodLists.tsx
@@ -205,11 +205,44 @@ function K8sPodsList({
return queryPayload;
}, [pageSize, currentPage, queryFilters, minTime, maxTime, orderBy, groupBy]);
+ const queryKey = useMemo(() => {
+ if (selectedPodUID) {
+ return [
+ 'podList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'podList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedPodUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sPodsList(
query as K8sPodsListPayload,
{
- queryKey: ['hostList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -261,6 +294,25 @@ function K8sPodsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedPodUID) {
+ return [
+ 'podList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'podList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [queryFilters, orderBy, selectedPodUID, minTime, maxTime, selectedRowData]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -270,7 +322,7 @@ function K8sPodsList({
} = useGetK8sPodsList(
fetchGroupedByRowDataQuery as K8sPodsListPayload,
{
- queryKey: ['hostList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -629,6 +681,9 @@ function K8sPodsList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedPodsData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
@@ -652,7 +708,7 @@ function K8sPodsList({
className={classNames('k8s-list-table', {
'expanded-k8s-list-table': isGroupedByAttribute,
})}
- dataSource={isFetching || isLoading ? [] : formattedPodsData}
+ dataSource={showTableLoadingState ? [] : formattedPodsData}
columns={columns}
pagination={{
current: currentPage,
@@ -663,26 +719,25 @@ function K8sPodsList({
onChange: onPaginationChange,
}}
loading={{
- spinning: isFetching || isLoading,
+ spinning: showTableLoadingState,
indicator:
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
scroll={{ x: true }}
tableLayout="fixed"
diff --git a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx
index aefb5a0bb1d4..66f5d914e01f 100644
--- a/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Pods/PodDetails/PodDetails.tsx
@@ -39,7 +39,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -89,8 +89,12 @@ function PodDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -212,10 +216,11 @@ function PodDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / TimeRangeOffset),
@@ -243,6 +248,7 @@ function PodDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -485,6 +491,7 @@ function PodDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/StatefulSets/K8sStatefulSetsList.tsx b/frontend/src/container/InfraMonitoringK8s/StatefulSets/K8sStatefulSetsList.tsx
index 1fb608b74c70..b360b5c32a5e 100644
--- a/frontend/src/container/InfraMonitoringK8s/StatefulSets/K8sStatefulSetsList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/StatefulSets/K8sStatefulSetsList.tsx
@@ -191,6 +191,32 @@ function K8sStatefulSetsList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
+ const groupedByRowDataQueryKey = useMemo(() => {
+ if (selectedStatefulSetUID) {
+ return [
+ 'statefulSetList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ ];
+ }
+ return [
+ 'statefulSetList',
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(selectedRowData),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ queryFilters,
+ orderBy,
+ selectedStatefulSetUID,
+ minTime,
+ maxTime,
+ selectedRowData,
+ ]);
+
const {
data: groupedByRowData,
isFetching: isFetchingGroupedByRowData,
@@ -200,7 +226,7 @@ function K8sStatefulSetsList({
} = useGetK8sStatefulSetsList(
fetchGroupedByRowDataQuery as K8sStatefulSetsListPayload,
{
- queryKey: ['statefulSetList', fetchGroupedByRowDataQuery],
+ queryKey: groupedByRowDataQueryKey,
enabled: !!fetchGroupedByRowDataQuery && !!selectedRowData,
},
undefined,
@@ -256,11 +282,44 @@ function K8sStatefulSetsList({
return groupedByRowData?.payload?.data?.records || [];
}, [groupedByRowData, selectedRowData]);
+ const queryKey = useMemo(() => {
+ if (selectedStatefulSetUID) {
+ return [
+ 'statefulSetList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ ];
+ }
+ return [
+ 'statefulSetList',
+ String(pageSize),
+ String(currentPage),
+ JSON.stringify(queryFilters),
+ JSON.stringify(orderBy),
+ JSON.stringify(groupBy),
+ String(minTime),
+ String(maxTime),
+ ];
+ }, [
+ selectedStatefulSetUID,
+ pageSize,
+ currentPage,
+ queryFilters,
+ orderBy,
+ groupBy,
+ minTime,
+ maxTime,
+ ]);
+
const { data, isFetching, isLoading, isError } = useGetK8sStatefulSetsList(
query as K8sStatefulSetsListPayload,
{
- queryKey: ['statefulSetList', query],
+ queryKey,
enabled: !!query,
+ keepPreviousData: true,
},
undefined,
dotMetricsEnabled,
@@ -592,6 +651,9 @@ function K8sStatefulSetsList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedStatefulSetsData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
@@ -611,7 +674,7 @@ function K8sStatefulSetsList({
className={classNames('k8s-list-table', 'statefulSets-list-table', {
'expanded-statefulsets-list-table': isGroupedByAttribute,
})}
- dataSource={isFetching || isLoading ? [] : formattedStatefulSetsData}
+ dataSource={showTableLoadingState ? [] : formattedStatefulSetsData}
columns={columns}
pagination={{
current: currentPage,
@@ -623,26 +686,25 @@ function K8sStatefulSetsList({
}}
scroll={{ x: true }}
loading={{
- spinning: isFetching || isLoading,
+ spinning: showTableLoadingState,
indicator:
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails.tsx b/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails.tsx
index aac8c1c60fc7..51648326b662 100644
--- a/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails.tsx
@@ -38,7 +38,7 @@ import {
ScrollText,
X,
} from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { AppState } from 'store/reducers';
@@ -84,8 +84,12 @@ function StatefulSetDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const [searchParams, setSearchParams] = useSearchParams();
@@ -211,10 +215,11 @@ function StatefulSetDetails({
}, [initialFilters, initialEventsFilters]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -242,6 +247,7 @@ function StatefulSetDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -477,6 +483,7 @@ function StatefulSetDetails({
};
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/container/InfraMonitoringK8s/Volumes/K8sVolumesList.tsx b/frontend/src/container/InfraMonitoringK8s/Volumes/K8sVolumesList.tsx
index dea58590f7ac..47b4663ed059 100644
--- a/frontend/src/container/InfraMonitoringK8s/Volumes/K8sVolumesList.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Volumes/K8sVolumesList.tsx
@@ -574,6 +574,9 @@ function K8sVolumesList({
});
};
+ const showTableLoadingState =
+ (isFetching || isLoading) && formattedVolumesData.length === 0;
+
return (
{isError &&
{data?.error || 'Something went wrong'}}
@@ -593,7 +597,7 @@ function K8sVolumesList({
className={classNames('k8s-list-table', 'volumes-list-table', {
'expanded-volumes-list-table': isGroupedByAttribute,
})}
- dataSource={isFetching || isLoading ? [] : formattedVolumesData}
+ dataSource={showTableLoadingState ? [] : formattedVolumesData}
columns={columns}
pagination={{
current: currentPage,
@@ -605,26 +609,25 @@ function K8sVolumesList({
}}
scroll={{ x: true }}
loading={{
- spinning: isFetching || isLoading,
+ spinning: showTableLoadingState,
indicator:
} />,
}}
locale={{
- emptyText:
- isFetching || isLoading ? null : (
-
-
-

+ emptyText: showTableLoadingState ? null : (
+
+
+

-
- This query had no results. Edit your query and try again!
-
-
+
+ This query had no results. Edit your query and try again!
+
- ),
+
+ ),
}}
tableLayout="fixed"
onChange={handleTableChange}
diff --git a/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/VolumeDetails.tsx b/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/VolumeDetails.tsx
index 480a131043a3..1be6cdbb7dd0 100644
--- a/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/VolumeDetails.tsx
+++ b/frontend/src/container/InfraMonitoringK8s/Volumes/VolumeDetails/VolumeDetails.tsx
@@ -13,7 +13,7 @@ import {
import { useIsDarkMode } from 'hooks/useDarkMode';
import GetMinMax from 'lib/getMinMax';
import { X } from 'lucide-react';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
@@ -44,8 +44,12 @@ function VolumeDetails({
endTime: endMs,
}));
+ const lastSelectedInterval = useRef
(null);
+
const [selectedInterval, setSelectedInterval] = useState(
- selectedTime as Time,
+ lastSelectedInterval.current
+ ? lastSelectedInterval.current
+ : (selectedTime as Time),
);
const isDarkMode = useIsDarkMode();
@@ -62,10 +66,11 @@ function VolumeDetails({
}, [volume]);
useEffect(() => {
- setSelectedInterval(selectedTime as Time);
+ const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
+ setSelectedInterval(currentSelectedInterval as Time);
- if (selectedTime !== 'custom') {
- const { maxTime, minTime } = GetMinMax(selectedTime);
+ if (currentSelectedInterval !== 'custom') {
+ const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
@@ -76,6 +81,7 @@ function VolumeDetails({
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
+ lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
@@ -104,6 +110,7 @@ function VolumeDetails({
);
const handleClose = (): void => {
+ lastSelectedInterval.current = null;
setSelectedInterval(selectedTime as Time);
if (selectedTime !== 'custom') {
diff --git a/frontend/src/hooks/useMultiIntersectionObserver.ts b/frontend/src/hooks/useMultiIntersectionObserver.ts
new file mode 100644
index 000000000000..8c8ce59b4384
--- /dev/null
+++ b/frontend/src/hooks/useMultiIntersectionObserver.ts
@@ -0,0 +1,54 @@
+import { useCallback, useEffect, useRef, useState } from 'react';
+
+type SetElement = (el: Element | null) => void;
+
+// To manage intersection observers for multiple items
+export function useMultiIntersectionObserver(
+ itemCount: number,
+ options: IntersectionObserverInit = { threshold: 0.1 },
+): {
+ visibilities: boolean[];
+ setElement: (index: number) => SetElement;
+} {
+ const elementsRef = useRef<(Element | null)[]>([]);
+
+ const [everVisibles, setEverVisibles] = useState(
+ new Array(itemCount).fill(false),
+ );
+
+ const setElement = useCallback<(index: number) => SetElement>(
+ (index) => (el): void => {
+ elementsRef.current[index] = el;
+ },
+ [],
+ );
+
+ useEffect(() => {
+ if (!elementsRef.current.length) return;
+
+ const observer = new IntersectionObserver((entries) => {
+ setEverVisibles((prev) => {
+ const newVis = [...prev];
+ let changed = false;
+ entries.forEach((entry) => {
+ const idx = elementsRef.current.indexOf(entry.target);
+ if (idx !== -1 && entry.isIntersecting && !newVis[idx]) {
+ newVis[idx] = true;
+ changed = true;
+ }
+ });
+ return changed ? newVis : prev;
+ });
+ }, options);
+
+ elementsRef.current.forEach((el) => {
+ if (el) observer.observe(el);
+ });
+
+ return (): void => {
+ observer.disconnect();
+ };
+ }, [itemCount, options]);
+
+ return { visibilities: everVisibles, setElement };
+}