This is an automated email from the ASF dual-hosted git repository.

sandeepk318 pushed a commit to branch frontend-refactor
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/frontend-refactor by this push:
     new 36aa0ea5d4 AMBARI-26619 : Ambari Web React: Fix Polling Implementation 
- No Pending Request Check in Ambari Modern UI (#4142)
36aa0ea5d4 is described below

commit 36aa0ea5d4118e3e21fa228ac1ba30097ea0573b
Author: Himanshu Maurya <[email protected]>
AuthorDate: Thu Jun 25 17:43:12 2026 +0530

    AMBARI-26619 : Ambari Web React: Fix Polling Implementation - No Pending 
Request Check in Ambari Modern UI (#4142)
---
 ambari-web/latest/src/api/cachedServiceApi.ts      |  84 ++++++++----
 .../latest/src/api/centralizedServiceStateApi.ts   | 141 ++++++++++++---------
 ambari-web/latest/src/hooks/useHostChecks.tsx      |   5 +
 ambari-web/latest/src/hooks/usePolling.ts          |  46 +++++--
 ambari-web/latest/src/screens/Alerts/Alerts.tsx    |  31 +++--
 .../src/screens/Dashboard/DashboardHeatmaps.tsx    |   2 +-
 .../Dashboard/widgets/NameNodeCpuPieChartView.tsx  |  27 ++--
 ambari-web/latest/src/screens/Heatmaps/index.tsx   |   2 +-
 ambari-web/latest/src/store/ServiceContext.tsx     |  64 ++++++----
 ambari-web/latest/src/store/context.tsx            |  28 +++-
 10 files changed, 273 insertions(+), 157 deletions(-)

diff --git a/ambari-web/latest/src/api/cachedServiceApi.ts 
b/ambari-web/latest/src/api/cachedServiceApi.ts
index 0b9811aef4..7ee76b75f8 100644
--- a/ambari-web/latest/src/api/cachedServiceApi.ts
+++ b/ambari-web/latest/src/api/cachedServiceApi.ts
@@ -29,6 +29,7 @@ class CachedServiceApiManager {
   private isPolling = false;
   private pollingInterval: NodeJS.Timeout | null = null;
   private subscribers = new Set<(data: any) => void>();
+  private pendingRequest: Promise<any> | null = null;
 
   static getInstance(): CachedServiceApiManager {
     if (!CachedServiceApiManager.instance) {
@@ -69,10 +70,11 @@ class CachedServiceApiManager {
   /**
    * Centralized API call for all service components
    * Similar to Ember.js approach - one call for all services
+   * REQUEST DEDUPLICATION: If a request is already in progress, return the 
pending promise
    */
   async fetchAllServiceComponents(clusterName: string, forceRefresh: boolean = 
false): Promise<any> {
     const cacheKey = 'all_components_data';
-    
+
     // Check cache first (unless force refresh)
     if (!forceRefresh) {
       const cachedData = serviceCache.get(cacheKey);
@@ -81,21 +83,30 @@ class CachedServiceApiManager {
       }
     }
 
+    // REQUEST DEDUPLICATION: If a request is already pending, return that 
promise
+    // This prevents multiple simultaneous API calls
+    if (this.pendingRequest) {
+      return this.pendingRequest;
+    }
+
     try {
-      
+
       // OPTIMIZED: Use the same comprehensive fields as the main 
ServiceContext for consistency
       const fields = 
`ServiceComponentInfo/service_name,host_components/HostRoles/display_name,host_components/HostRoles/host_name,host_components/HostRoles/public_host_name,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,host_components/HostRoles/stale_configs,host_components/HostRoles/ha_state,host_components/HostRoles/desired_admin_state,host_components/metrics/jvm/memHeapUsedM,host_components/metrics/jvm/HeapMemoryMax,host_components/metrics/jvm/HeapMemory
 [...]
-      
-      const response = await 
ServiceApi.getAllServiceComponentsListAndInitialMetrics(
+
+      // Set pending request to prevent duplicate calls
+      this.pendingRequest = 
ServiceApi.getAllServiceComponentsListAndInitialMetrics(
         clusterName,
         fields
       );
 
+      const response = await this.pendingRequest;
+
       if (response?.data?.items) {
-        
+
         // Cache the consolidated data
         serviceCache.set(cacheKey, response.data, 30000); // 30 second TTL
-        
+
         // Also cache by individual service for quick access
         const serviceGroups = 
this.groupComponentsByService(response.data.items);
         Object.entries(serviceGroups).forEach(([serviceName, data]) => {
@@ -104,11 +115,17 @@ class CachedServiceApiManager {
 
         // Notify subscribers immediately
         this.notifySubscribers(response.data);
-        
+
         return response.data;
       }
+
+      return null;
     } catch (error) {
+      console.error('Error fetching service components:', error);
       return null;
+    } finally {
+      // Clear pending request when done (success or error)
+      this.pendingRequest = null;
     }
   }
 
@@ -133,20 +150,31 @@ class CachedServiceApiManager {
 
   /**
    * Start centralized polling - similar to Ember.js approach
+   * Uses timeout-based polling to prevent overlapping requests
    */
   startPolling(clusterName: string, intervalMs: number = 5000): void {
     if (this.isPolling) return;
-    
+
     this.isPolling = true;
-    
-    // Initial fetch
-    this.fetchAllServiceComponents(clusterName);
-    
-    // Set up polling interval
-    this.pollingInterval = setInterval(() => {
-      this.fetchAllServiceComponents(clusterName);
-    }, intervalMs);
-    
+
+    // Timeout-based polling to prevent overlapping requests
+    const poll = async () => {
+      if (!this.isPolling) return;
+
+      try {
+        await this.fetchAllServiceComponents(clusterName);
+      } catch (error) {
+        console.error('Polling error:', error);
+      } finally {
+        // Schedule next poll ONLY after current request completes
+        if (this.isPolling) {
+          this.pollingInterval = setTimeout(poll, intervalMs);
+        }
+      }
+    };
+
+    // Start initial poll
+    poll();
   }
 
   /**
@@ -155,7 +183,7 @@ class CachedServiceApiManager {
    */
   pausePolling(): void {
     if (this.pollingInterval) {
-      clearInterval(this.pollingInterval);
+      clearTimeout(this.pollingInterval);
       this.pollingInterval = null;
     }
   }
@@ -165,9 +193,21 @@ class CachedServiceApiManager {
    */
   resumePolling(clusterName?: string, intervalMs: number = 5000): void {
     if (this.isPolling && !this.pollingInterval && clusterName) {
-      this.pollingInterval = setInterval(() => {
-        this.fetchAllServiceComponents(clusterName);
-      }, intervalMs);
+      const poll = async () => {
+        if (!this.isPolling) return;
+
+        try {
+          await this.fetchAllServiceComponents(clusterName);
+        } catch (error) {
+          console.error('Polling error:', error);
+        } finally {
+          if (this.isPolling) {
+            this.pollingInterval = setTimeout(poll, intervalMs);
+          }
+        }
+      };
+
+      poll();
     }
   }
 
@@ -176,7 +216,7 @@ class CachedServiceApiManager {
    */
   stopPolling(): void {
     if (this.pollingInterval) {
-      clearInterval(this.pollingInterval);
+      clearTimeout(this.pollingInterval);
       this.pollingInterval = null;
     }
     this.isPolling = false;
diff --git a/ambari-web/latest/src/api/centralizedServiceStateApi.ts 
b/ambari-web/latest/src/api/centralizedServiceStateApi.ts
index 8de31317f5..3c3d03ed32 100644
--- a/ambari-web/latest/src/api/centralizedServiceStateApi.ts
+++ b/ambari-web/latest/src/api/centralizedServiceStateApi.ts
@@ -30,85 +30,102 @@ class CentralizedServiceStateApi {
   private lastFetchTime: number = 0;
   private readonly CACHE_DURATION = 5000; // 5 seconds cache
   private subscribers: ((data: Map<string, ServiceStateData>) => void)[] = [];
+  private pendingRequest: Promise<Map<string, ServiceStateData>> | null = null;
 
   /**
    * Fetches all service states and alerts in a single API call (Ember.js 
pattern)
    * This replaces individual ServiceApi.getServiceState() calls
+   * REQUEST DEDUPLICATION: If a request is already in progress, return the 
pending promise
    */
   async fetchAllServiceStatesAndAlerts(clusterName: string): 
Promise<Map<string, ServiceStateData>> {
     const now = Date.now();
-    
+
     // Return cached data if still fresh
     if (now - this.lastFetchTime < this.CACHE_DURATION && this.cache.size > 0) 
{
       return this.cache;
     }
 
-    try {
-      const response = await ambariApi.request({
-        url: 
`/clusters/${clusterName}/services?fields=ServiceInfo/state,ServiceInfo/maintenance_state&minimal_response=true`,
-        method: "GET",
-      });
-
-      // Get alerts with proper maintenance state filtering (following Ember 
pattern)
-      // This API call will return NO items for services/components in 
maintenance mode
-      const alertsResponse = await ambariApi.request({
-        url: 
`/clusters/${clusterName}/alerts?fields=Alert/service_name,Alert/state&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&minimal_response=true`,
-        method: "GET",
-      });
-
-      const newCache = new Map<string, ServiceStateData>();
-
-      // Count alerts per service - API already filters out maintenance mode 
alerts
-      const serviceAlertsCount: { [key: string]: { critical: number; warning: 
number } } = {};
-      
-      alertsResponse.data.items?.forEach((alert: any) => {
-        const serviceName = alert.Alert?.service_name;
-        const alertState = alert.Alert?.state;
-        
-        if (serviceName && alertState) {
-          if (!serviceAlertsCount[serviceName]) {
-            serviceAlertsCount[serviceName] = { critical: 0, warning: 0 };
-          }
-          
-          if (alertState === 'CRITICAL') {
-            serviceAlertsCount[serviceName].critical++;
-          } else if (alertState === 'WARNING') {
-            serviceAlertsCount[serviceName].warning++;
+    // REQUEST DEDUPLICATION: If a request is already pending, return that 
promise
+    if (this.pendingRequest) {
+      return this.pendingRequest;
+    }
+
+    const executeRequest = async (): Promise<Map<string, ServiceStateData>> => 
{
+      try {
+        const response = await ambariApi.request({
+          url: 
`/clusters/${clusterName}/services?fields=ServiceInfo/state,ServiceInfo/maintenance_state&minimal_response=true`,
+          method: "GET",
+        });
+
+        // Get alerts with proper maintenance state filtering (following Ember 
pattern)
+        // This API call will return NO items for services/components in 
maintenance mode
+        const alertsResponse = await ambariApi.request({
+          url: 
`/clusters/${clusterName}/alerts?fields=Alert/service_name,Alert/state&Alert/state.in(CRITICAL,WARNING)&Alert/maintenance_state.in(OFF)&minimal_response=true`,
+          method: "GET",
+        });
+
+        const newCache = new Map<string, ServiceStateData>();
+
+        // Count alerts per service - API already filters out maintenance mode 
alerts
+        const serviceAlertsCount: { [key: string]: { critical: number; 
warning: number } } = {};
+
+        alertsResponse.data.items?.forEach((alert: any) => {
+          const serviceName = alert.Alert?.service_name;
+          const alertState = alert.Alert?.state;
+
+          if (serviceName && alertState) {
+            if (!serviceAlertsCount[serviceName]) {
+              serviceAlertsCount[serviceName] = { critical: 0, warning: 0 };
+            }
+
+            if (alertState === 'CRITICAL') {
+              serviceAlertsCount[serviceName].critical++;
+            } else if (alertState === 'WARNING') {
+              serviceAlertsCount[serviceName].warning++;
+            }
           }
-        }
-      });
-
-      response.data.items?.forEach((service: any) => {
-        const serviceName = service.ServiceInfo.service_name;
-        const state = service.ServiceInfo.state;
-        
-        // Use API-filtered alert counts (already excludes maintenance mode 
alerts)
-        const serviceAlerts = serviceAlertsCount[serviceName] || { critical: 
0, warning: 0 };
-        const criticalAlerts = serviceAlerts.critical;
-        const warningAlerts = serviceAlerts.warning;
-        
-        const alertsCount = criticalAlerts + warningAlerts;
-        const hasCriticalAlerts = criticalAlerts > 0;
-
-        newCache.set(serviceName, {
-          serviceName,
-          state,
-          alertsCount,
-          hasCriticalAlerts,
         });
-      });
 
-      this.cache = newCache;
-      this.lastFetchTime = now;
+        response.data.items?.forEach((service: any) => {
+          const serviceName = service.ServiceInfo.service_name;
+          const state = service.ServiceInfo.state;
 
-      // Notify subscribers
-      this.notifySubscribers();
+          // Use API-filtered alert counts (already excludes maintenance mode 
alerts)
+          const serviceAlerts = serviceAlertsCount[serviceName] || { critical: 
0, warning: 0 };
+          const criticalAlerts = serviceAlerts.critical;
+          const warningAlerts = serviceAlerts.warning;
 
-      return this.cache;
-    } catch (error) {
-      // Return existing cache on error
-      return this.cache;
-    }
+          const alertsCount = criticalAlerts + warningAlerts;
+          const hasCriticalAlerts = criticalAlerts > 0;
+
+          newCache.set(serviceName, {
+            serviceName,
+            state,
+            alertsCount,
+            hasCriticalAlerts,
+          });
+        });
+
+        this.cache = newCache;
+        this.lastFetchTime = now;
+
+        // Notify subscribers
+        this.notifySubscribers();
+
+        return this.cache;
+      } catch (error) {
+        console.error('Error fetching service states and alerts:', error);
+        // Return existing cache on error
+        return this.cache;
+      } finally {
+        // Clear pending request when done
+        this.pendingRequest = null;
+      }
+    };
+
+    // Set and execute pending request
+    this.pendingRequest = executeRequest();
+    return this.pendingRequest;
   }
 
   /**
diff --git a/ambari-web/latest/src/hooks/useHostChecks.tsx 
b/ambari-web/latest/src/hooks/useHostChecks.tsx
index 5387279394..fb3df16cb5 100644
--- a/ambari-web/latest/src/hooks/useHostChecks.tsx
+++ b/ambari-web/latest/src/hooks/useHostChecks.tsx
@@ -62,6 +62,11 @@ export const useHostChecks = (
   const dataForHostCheck = useRef({});
 
   const getHostCheckTasks = async () => {
+    // Don't attempt API call with invalid requestID
+    if (requestID === -1) {
+      return;
+    }
+
     const response = await HostsApi.getRequestStatus(
       requestID,
       
"Requests/inputs,Requests/request_status,tasks/Tasks/host_name,tasks/Tasks/structured_out/host_resolution_check/hosts_with_failures,tasks/Tasks/structured_out/host_resolution_check/failed_count,tasks/Tasks/structured_out/installed_packages,tasks/Tasks/structured_out/last_agent_env_check,tasks/Tasks/structured_out/transparentHugePage,tasks/Tasks/stdout,tasks/Tasks/stderr,tasks/Tasks/error_log,tasks/Tasks/command_detail,tasks/Tasks/status&minimal_response=true"
diff --git a/ambari-web/latest/src/hooks/usePolling.ts 
b/ambari-web/latest/src/hooks/usePolling.ts
index 6cab3b6f5d..f9bec4faad 100644
--- a/ambari-web/latest/src/hooks/usePolling.ts
+++ b/ambari-web/latest/src/hooks/usePolling.ts
@@ -21,13 +21,14 @@ import { useEffect, useRef, useCallback, useState } from 
'react';
 
 function usePolling(apiFunction: Function, interval = 2000) {
   const savedCallback = useRef<Function | undefined>(undefined);
-  const intervalId = useRef<NodeJS.Timeout | null>(null);
+  const timeoutId = useRef<NodeJS.Timeout | null>(null);
+  const isPausedRef = useRef(false);
   const [isPaused, setIsPaused] = useState(false);
 
   const stopPolling = useCallback(() => {
-    if (intervalId.current) {
-      clearInterval(intervalId.current);
-      intervalId.current = null;
+    if (timeoutId.current) {
+      clearTimeout(timeoutId.current);
+      timeoutId.current = null;
     }
   }, []);
 
@@ -40,23 +41,44 @@ function usePolling(apiFunction: Function, interval = 2000) 
{
     setIsPaused(false);
   }, []);
 
+  useEffect(() => {
+    isPausedRef.current = isPaused;
+  }, [isPaused]);
+
   // Remember the latest callback.
   useEffect(() => {
     savedCallback.current = apiFunction;
   }, [apiFunction]);
 
-  // Set up the interval.
+  // Set up the timeout-based polling.
   useEffect(() => {
-    function tick() {
-      if (savedCallback.current) {
-        savedCallback.current();
+    async function tick() {
+      if (!savedCallback.current || isPausedRef.current) {
+        return;
+      }
+
+      try {
+        // Execute the API call and wait for it to complete
+        await Promise.resolve(savedCallback.current());
+      } catch (error) {
+        // Log error but don't break polling
+        console.error('Polling error:', error);
+      } finally {
+        // Schedule next poll only after current request completes
+        if (!isPausedRef.current && interval !== null) {
+          timeoutId.current = setTimeout(tick, interval);
+        }
       }
     }
-    
-    if (!isPaused && interval !== null) {
-      intervalId.current = setInterval(tick, interval);
-      return () => stopPolling();
+
+    // Start polling if not paused
+    if (!isPausedRef.current && interval !== null) {
+      // Initial call
+      tick();
     }
+
+    // Cleanup on unmount or when dependencies change
+    return () => stopPolling();
   }, [interval, isPaused, stopPolling]);
 
   return { stopPolling, pausePolling, resumePolling, isPaused };
diff --git a/ambari-web/latest/src/screens/Alerts/Alerts.tsx 
b/ambari-web/latest/src/screens/Alerts/Alerts.tsx
index 936755d57f..709479b15c 100644
--- a/ambari-web/latest/src/screens/Alerts/Alerts.tsx
+++ b/ambari-web/latest/src/screens/Alerts/Alerts.tsx
@@ -21,7 +21,7 @@ import LastStatusChanged from 
"../../components/LastStatusChanged";
 import Paginator from "../../components/Paginator";
 import usePagination from '../../hooks/usePagination';
 import Spinner from "../../components/Spinner";
-import {useEffect, useState, useContext} from 'react';
+import {useEffect, useState, useContext, useCallback} from 'react';
 import {AlertsApi} from "../../api/alertsApi";
 import {getCurrTimeInSec} from "../../Utils/Utility";
 import {AlertDefinition, AlertGroupItem, AlertRow, MergedAlert} from "./types";
@@ -37,6 +37,7 @@ import {SortingState} from "@tanstack/react-table";
 import MenuBar from './MenuBar'
 import {formatAlertStatusDisplay} from "./alertStatus";
 import { useAuth } from '../../hooks/useAuth';
+import usePolling from '../../hooks/usePolling';
 
 interface SearchFilter {
     category: string;
@@ -99,7 +100,7 @@ const Alerts = () => {
         return serviceCounts;
     };
 
-    const fetchData = async () => {
+    const fetchData = useCallback(async () => {
         if (!clusterName) {
             console.error('No cluster name provided');
             return;
@@ -135,26 +136,28 @@ const Alerts = () => {
         } finally {
             setIsLoading(false);
         }
-    };
+    }, [clusterName]);
+
+    // Use usePolling hook with pause/resume based on modal state
+    const { pausePolling, resumePolling } = usePolling(fetchData, 30000);
 
     useEffect(() => {
         console.log('Initial render with clusterName:', clusterName);
         if (clusterName) {
-            fetchData();
             fetchAlertDefinitions();
-
-            // Only set up polling if modal is not open
-            if (!isModalOpen) {
-                const pollInterval = setInterval(() => {
-                    fetchData();
-                }, 30000); // 30 seconds
-
-                return () => clearInterval(pollInterval);
-            }
         } else {
             console.error('No cluster name available');
         }
-    }, [clusterName, isModalOpen]);
+    }, [clusterName]);
+
+    // Control polling based on modal state
+    useEffect(() => {
+        if (isModalOpen) {
+            pausePolling();
+        } else {
+            resumePolling();
+        }
+    }, [isModalOpen, pausePolling, resumePolling]);
 
     const fetchAlertDefinitions = async () => {
         try {
diff --git a/ambari-web/latest/src/screens/Dashboard/DashboardHeatmaps.tsx 
b/ambari-web/latest/src/screens/Dashboard/DashboardHeatmaps.tsx
index a34a3b05b6..da7f9259a6 100644
--- a/ambari-web/latest/src/screens/Dashboard/DashboardHeatmaps.tsx
+++ b/ambari-web/latest/src/screens/Dashboard/DashboardHeatmaps.tsx
@@ -376,7 +376,7 @@ function DashboardHeatmaps() {
 
   const getSelectedMetricValue = useCallback(async () => {
     if (!selectedMetric) {
-      return null;
+      return;
     }
     loadMetrics();
   }, [selectedMetric, loadMetrics]);
diff --git 
a/ambari-web/latest/src/screens/Dashboard/widgets/NameNodeCpuPieChartView.tsx 
b/ambari-web/latest/src/screens/Dashboard/widgets/NameNodeCpuPieChartView.tsx
index cd385591a6..5854768632 100644
--- 
a/ambari-web/latest/src/screens/Dashboard/widgets/NameNodeCpuPieChartView.tsx
+++ 
b/ambari-web/latest/src/screens/Dashboard/widgets/NameNodeCpuPieChartView.tsx
@@ -16,13 +16,14 @@
  * limitations under the License.
  */
 
-import { useContext, useEffect, useState } from "react";
+import { useContext, useEffect, useState, useCallback } from "react";
 import { ServiceContext } from "../../../store/ServiceContext";
 import ChartContainer from "../ChartContainer";
 import { Doughnut } from "react-chartjs-2";
 import { ArcElement, Tooltip, Chart as ChartJs } from "chart.js";
 import { AppContext } from "../../../store/context";
 import metricsApi from "../../../api/metricsApi";
+import usePolling from "../../../hooks/usePolling";
 
 ChartJs.register(ArcElement, Tooltip);
 
@@ -31,15 +32,6 @@ export default function NameNodeCpuPieChartView({ subGroupId 
= "" }) {
   const { clusterName } = useContext(AppContext);
   const [cpuWio, setCpuWio] = useState<number | null>(null);
   const [nnHostName, setNnHostName] = useState("");
-  const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
-
-  useEffect(() => {
-    return () => {
-      if (intervalId) {
-        clearInterval(intervalId);
-      }
-    };
-  }, [intervalId]);
 
   useEffect(() => {
     const isHaEnabled = allServiceModels?.hdfs?.isHaEnabled;
@@ -58,15 +50,13 @@ export default function NameNodeCpuPieChartView({ 
subGroupId = "" }) {
         setNnHostName(nameNode.hostName);
       }
     }
+  }, [allServiceModels, subGroupId]);
 
-    if (nnHostName) {
-      getValue();
-      const id = setInterval(() => getValue(), 60000);
-      setIntervalId(id);
+  const getValue = useCallback(async () => {
+    if (!nnHostName || !clusterName) {
+      return;
     }
-  }, [allServiceModels, subGroupId, nnHostName]);
 
-  const getValue = async () => {
     try {
       const response = await metricsApi.getNameNodeCpuWio(
         clusterName,
@@ -76,7 +66,10 @@ export default function NameNodeCpuPieChartView({ subGroupId 
= "" }) {
     } catch (error) {
       console.error("Error fetching NameNode CPU data:", error);
     }
-  };
+  }, [clusterName, nnHostName]);
+
+  // Use usePolling hook for automatic polling
+  usePolling(getValue, 60000);
 
   const value = cpuWio !== null ? (cpuWio >= 100 ? 100 : cpuWio) : 0;
   const percent = value.toFixed(1);
diff --git a/ambari-web/latest/src/screens/Heatmaps/index.tsx 
b/ambari-web/latest/src/screens/Heatmaps/index.tsx
index 1374d0a09b..bf79bff9fb 100644
--- a/ambari-web/latest/src/screens/Heatmaps/index.tsx
+++ b/ambari-web/latest/src/screens/Heatmaps/index.tsx
@@ -313,7 +313,7 @@ function Heatmaps({ serviceName }: { serviceName: string }) 
{
 
   const getSelectedMetricValue = useCallback(async () => {
     if (!selectedMetric) {
-      return null;
+      return;
     }
     loadMetrics();
   }, [selectedMetric, loadMetrics]);
diff --git a/ambari-web/latest/src/store/ServiceContext.tsx 
b/ambari-web/latest/src/store/ServiceContext.tsx
index f272b39697..bc929d0a1c 100644
--- a/ambari-web/latest/src/store/ServiceContext.tsx
+++ b/ambari-web/latest/src/store/ServiceContext.tsx
@@ -172,22 +172,24 @@ const ServiceProvider: React.FC<ServiceProviderProps> = 
({ children }) => {
   }, [allModelsLoaded]);
 
   /**
-   * OPTIMIZED: Single API call for all maintenance and stale alerts data
-   * This replaces multiple separate API calls with one consolidated call
-   * Uses the exact API endpoint structure you provided in the task description
+   * OPTIMIZED: Use cached data from CachedServiceApi instead of making 
duplicate API call
+   * This replaces the duplicate API call with cached data usage
    */
   const fetchOptimizedMaintenanceAndStaleData = async () => {
     try {
-      // Single optimized API call with all required fields for maintenance 
and stale configs
-      const optimizedFields = 
`ServiceComponentInfo/service_name,host_components/HostRoles/display_name,host_components/HostRoles/host_name,host_components/HostRoles/public_host_name,host_components/HostRoles/state,host_components/HostRoles/maintenance_state,host_components/HostRoles/stale_configs,host_components/HostRoles/ha_state,host_components/HostRoles/desired_admin_state,host_components/metrics/jvm/memHeapUsedM,host_components/metrics/jvm/HeapMemoryMax,host_components/metrics/jvm/H
 [...]
-      
-      const optimizedResponse = await 
ServiceApi.getAllServiceComponentsListAndInitialMetrics(
-        clusterName,
-        optimizedFields
-      );
+      // USE CACHED DATA instead of making new API call
+      // CachedServiceApi is already polling this same endpoint
+      const cachedData = cachedServiceApi.getAllComponentData();
+
+      if (!cachedData) {
+        // If no cached data yet, fetch it once (will be cached for subsequent 
calls)
+        await cachedServiceApi.fetchAllServiceComponents(clusterName);
+        return;
+      }
 
-      if (!isEmpty(optimizedResponse?.data)) {
-        const responseData = optimizedResponse.data;
+      const responseData = cachedData;
+
+      if (!isEmpty(responseData)) {
         
         // Process stale configs for all services following Ember logic
         // In Ember: isRestartRequired = 
serviceComponents.filterProperty('staleConfigHosts.length').length > 0
@@ -384,28 +386,42 @@ const ServiceProvider: React.FC<ServiceProviderProps> = 
({ children }) => {
         }
       });
 
-      // Start centralized service state and alerts polling
-      const pollServiceStates = async () => {
-        const statesData = await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName);
-        setServiceStatesData(statesData);
-      };
-      
       // Subscribe to centralized service state updates
       const unsubscribeServiceStates = 
centralizedServiceStateApi.subscribe((data) => {
         setServiceStatesData(data);
       });
-      
-      // Initial fetch
+
+      // Start centralized service state and alerts polling with timeout-based 
approach
+      let serviceStateTimeout: NodeJS.Timeout | null = null;
+      let isPollingActive = true;
+
+      const pollServiceStates = async () => {
+        if (!isPollingActive) return;
+
+        try {
+          const statesData = await 
centralizedServiceStateApi.fetchAllServiceStatesAndAlerts(clusterName);
+          setServiceStatesData(statesData);
+        } catch (error) {
+          console.error('Error polling service states:', error);
+        } finally {
+          // Schedule next poll ONLY after current request completes
+          if (isPollingActive) {
+            serviceStateTimeout = setTimeout(pollServiceStates, 5000);
+          }
+        }
+      };
+
+      // Start initial poll
       pollServiceStates();
-      
-      // Set up polling interval
-      const serviceStateInterval = setInterval(pollServiceStates, 5000);
 
       return () => {
         unsubscribe();
         unsubscribeServiceStates();
         cachedServiceApi.stopPolling();
-        clearInterval(serviceStateInterval);
+        isPollingActive = false;
+        if (serviceStateTimeout) {
+          clearTimeout(serviceStateTimeout);
+        }
       };
     }
   }, [clusterName, allModelsLoaded]);
diff --git a/ambari-web/latest/src/store/context.tsx 
b/ambari-web/latest/src/store/context.tsx
index ae0438efb8..12a88acd9b 100644
--- a/ambari-web/latest/src/store/context.tsx
+++ b/ambari-web/latest/src/store/context.tsx
@@ -287,12 +287,32 @@ export const AppProvider: React.FC<{ children: 
React.ReactNode }> = ({
   useEffect(() => {
     if (!clusterName || !isClusterInstalled) return;
 
-    const pollInterval = setInterval(() => {
-      fetchBackgroundOperations();
-    }, 30000); // Poll every 30 seconds like Ember.js
+    let pollTimeout: NodeJS.Timeout | null = null;
+    let isPollingActive = true;
+
+    const poll = async () => {
+      if (!isPollingActive) return;
+
+      try {
+        await fetchBackgroundOperations();
+      } catch (error) {
+        console.error('Error polling background operations:', error);
+      } finally {
+        // Schedule next poll ONLY after current request completes
+        if (isPollingActive) {
+          pollTimeout = setTimeout(poll, 30000); // Poll every 30 seconds like 
Ember.js
+        }
+      }
+    };
+
+    // Start initial poll
+    poll();
 
     return () => {
-      clearInterval(pollInterval);
+      isPollingActive = false;
+      if (pollTimeout) {
+        clearTimeout(pollTimeout);
+      }
     };
   }, [clusterName, isClusterInstalled]);
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to