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]