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

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


The following commit(s) were added to refs/heads/trunk by this push:
     new 5b2bf36b12 AMBARI-26646: Fix Host Summary page not listing host 
components when opened via the Hosts List page (#4203)
5b2bf36b12 is described below

commit 5b2bf36b126b0f1836f8a12c6b8df5251a54f36e
Author: Himanshu Maurya <[email protected]>
AuthorDate: Fri Sep 4 19:30:21 2026 +0530

    AMBARI-26646: Fix Host Summary page not listing host components when opened 
via the Hosts List page (#4203)
---
 .../latest/src/hooks/useHostConfigUpdater.test.tsx |  13 +-
 .../latest/src/hooks/useHostConfigUpdater.ts       |  88 ++++-----
 ambari-web/latest/src/router/RoutesList.tsx        |   2 -
 .../ClusterAdmin/StackAndVersions/ListVersion.tsx  |  10 +-
 .../latest/src/screens/Hosts/HostComboSearch.tsx   |   5 +-
 .../latest/src/screens/Hosts/HostSummary.tsx       |   4 +-
 ambari-web/latest/src/screens/Hosts/HostsList.tsx  | 200 ++++++---------------
 .../src/screens/Hosts/hostsFilterNavigation.ts     | 110 ++++++++++++
 ambari-web/latest/src/screens/Hosts/index.tsx      |   1 -
 .../src/screens/Services/HDFSFederationSummary.tsx |  12 +-
 .../src/screens/Services/ServiceComponents.tsx     | 140 ++++++++-------
 11 files changed, 299 insertions(+), 286 deletions(-)

diff --git a/ambari-web/latest/src/hooks/useHostConfigUpdater.test.tsx 
b/ambari-web/latest/src/hooks/useHostConfigUpdater.test.tsx
index 8104c12a0e..69eae0f4ee 100644
--- a/ambari-web/latest/src/hooks/useHostConfigUpdater.test.tsx
+++ b/ambari-web/latest/src/hooks/useHostConfigUpdater.test.tsx
@@ -64,7 +64,7 @@ describe("useHostConfigUpdater realtime initialization", () 
=> {
     const setAllHostModels = vi.fn();
 
     renderHook(
-      () => useHostConfigUpdater({}, [], setAllHostModels),
+      () => useHostConfigUpdater({}, setAllHostModels),
       {
         wrapper: wrapperWithMessage({
           destination: "/events/hosts",
@@ -74,7 +74,10 @@ describe("useHostConfigUpdater realtime initialization", () 
=> {
       },
     );
 
-    expect(setAllHostModels).not.toHaveBeenCalled();
+    expect(setAllHostModels).toHaveBeenCalled();
+    // The functional update bails out (returns prevModels unchanged) when
+    // there are no models loaded yet.
+    expect(setAllHostModels.mock.calls[0][0]([])).toEqual([]);
   });
 
   it("applies host events after the initial REST models are available", async 
() => {
@@ -84,7 +87,7 @@ describe("useHostConfigUpdater realtime initialization", () 
=> {
     const setAllHostModels = vi.fn();
 
     renderHook(
-      () => useHostConfigUpdater({}, [host], setAllHostModels),
+      () => useHostConfigUpdater({}, setAllHostModels),
       {
         wrapper: wrapperWithMessage({
           destination: "/events/hosts",
@@ -95,7 +98,8 @@ describe("useHostConfigUpdater realtime initialization", () 
=> {
     );
 
     await waitFor(() => expect(setAllHostModels).toHaveBeenCalled());
-    expect(setAllHostModels.mock.calls[0][0][0].state).toBe("HEARTBEAT_LOST");
+    const updateFn = setAllHostModels.mock.calls[0][0];
+    expect(updateFn([host])[0].state).toBe("HEARTBEAT_LOST");
   });
 
   it("does not report a loaded host as an empty REST result", async () => {
@@ -114,7 +118,6 @@ describe("useHostConfigUpdater realtime initialization", () 
=> {
     const { result } = renderHook(
       () => useHostConfigUpdater(
         queryParams,
-        [],
         setAllHostModels,
       ),
       {
diff --git a/ambari-web/latest/src/hooks/useHostConfigUpdater.ts 
b/ambari-web/latest/src/hooks/useHostConfigUpdater.ts
index 334014a1de..413b6971ef 100644
--- a/ambari-web/latest/src/hooks/useHostConfigUpdater.ts
+++ b/ambari-web/latest/src/hooks/useHostConfigUpdater.ts
@@ -52,7 +52,6 @@ const cloneHostModels = (hosts: Host[]): Host[] =>
 
 export const useHostConfigUpdater = (
   hostApiQueryParams: any,
-  allHostModels: Host[],
   setAllHostModels: Function,
   setTotalItems?: Function,
   setPaginationLoading?: Function
@@ -62,7 +61,6 @@ export const useHostConfigUpdater = (
   const [isEmptyResult, setIsEmptyResult] = useState<boolean | null>(null);
   const [retryCount, setRetryCount] = useState(0);
   const queryData = useRef({});
-  const allHostModelsRef = useRef<Host[]>(allHostModels);
 
   const {
     cluster,
@@ -73,41 +71,29 @@ export const useHostConfigUpdater = (
   } =
     useContext(AppContext);
 
-  // Keep the ref updated with the latest allHostModels
   useEffect(() => {
-    allHostModelsRef.current = allHostModels;
-  }, [allHostModels]);
-
-  useEffect(() => {
-    // Ember ignores realtime updates for records that are not loaded yet. An
-    // empty update here can otherwise overwrite the initial REST response and
-    // make a valid Host Details route look like a missing host.
-    if (parsedSocketMessages.length && allHostModelsRef.current.length) {
+    if (!parsedSocketMessages.length) return;
+    // Merge into committed models via a functional update; a snapshot taken
+    // outside setAllHostModels would write back stale data.
+    setAllHostModels((prevModels: Host[]) => {
+      // Ember ignores realtime updates for records that are not loaded yet. An
+      // empty update here can otherwise overwrite the initial REST response 
and
+      // make a valid Host Details route look like a missing host.
+      if (!prevModels.length) return prevModels;
       switch (get(parsedSocketMessages[0], "destination", "")) {
         case "/events/hostcomponents":
-          setAllHostModels(
-            applyHostComponentEvent(
-              allHostModelsRef.current,
-              parsedSocketMessages[0],
-            ),
-          );
-          break;
+          return applyHostComponentEvent(prevModels, parsedSocketMessages[0]);
         case "/events/hosts":
-          setAllHostModels(
-            applyHostEvent(allHostModelsRef.current, parsedSocketMessages[0]),
-          );
-          break;
+          return applyHostEvent(prevModels, parsedSocketMessages[0]);
         case "/events/requests":
-          setAllHostModels(
-            applyCompletedDecommissionRequest(
-              allHostModelsRef.current,
-              parsedSocketMessages[0],
-            ),
+          return applyCompletedDecommissionRequest(
+            prevModels,
+            parsedSocketMessages[0],
           );
-          break;
         default:
+          return prevModels;
       }
-    }
+    });
   }, [parsedSocketMessages]);
 
   useEffect(() => {
@@ -166,28 +152,30 @@ export const useHostConfigUpdater = (
     );
 
     if (get(response, "items", []).length) {
-      // Use the ref to get the latest allHostModels value, avoiding stale 
closure
-      const allHostModelsCopy = cloneHostModels(allHostModelsRef.current);
-      get(response, "items", []).forEach((host: any) => {
-        const hostName = get(host, "Hosts.host_name", "");
-        const hostModel = allHostModelsCopy.find(
-          (h: Host) => h.hostName === hostName
-        );
-        if (hostModel) {
-          (
-            Object.keys(hostMapper.hostConfig) as Array<
-              keyof typeof hostMapper.hostConfig
-            >
-          ).forEach((key) => {
-            set(
-              hostModel,
-              key,
-              get(host, hostMapper.hostConfig[key], get(hostModel, key))
-            );
-          });
-        }
+      // Merge into committed models; a snapshot would write back stale data.
+      setAllHostModels((prevModels: Host[]) => {
+        const allHostModelsCopy = cloneHostModels(prevModels);
+        get(response, "items", []).forEach((host: any) => {
+          const hostName = get(host, "Hosts.host_name", "");
+          const hostModel = allHostModelsCopy.find(
+            (h: Host) => h.hostName === hostName
+          );
+          if (hostModel) {
+            (
+              Object.keys(hostMapper.hostConfig) as Array<
+                keyof typeof hostMapper.hostConfig
+              >
+            ).forEach((key) => {
+              set(
+                hostModel,
+                key,
+                get(host, hostMapper.hostConfig[key], get(hostModel, key))
+              );
+            });
+          }
+        });
+        return allHostModelsCopy;
       });
-      setAllHostModels(allHostModelsCopy);
     }
   };
 
diff --git a/ambari-web/latest/src/router/RoutesList.tsx 
b/ambari-web/latest/src/router/RoutesList.tsx
index 5091e09feb..2debf986c0 100644
--- a/ambari-web/latest/src/router/RoutesList.tsx
+++ b/ambari-web/latest/src/router/RoutesList.tsx
@@ -287,8 +287,6 @@ const RoutesList: RouteObject[] = [
                 ),
               },
               { path: "hosts", element: <HostsList /> },
-              { path: "hosts/component/:componentName", element: <HostsList /> 
},
-              { path: "hosts/version/:versionName/:versionStatus", element: 
<HostsList /> },
               { path: "hosts/:hostname/:tab", element: <Hosts /> },
               {
                 path: "host/add/:stepNumber",
diff --git 
a/ambari-web/latest/src/screens/ClusterAdmin/StackAndVersions/ListVersion.tsx 
b/ambari-web/latest/src/screens/ClusterAdmin/StackAndVersions/ListVersion.tsx
index 9121a7af43..d29e612fe2 100644
--- 
a/ambari-web/latest/src/screens/ClusterAdmin/StackAndVersions/ListVersion.tsx
+++ 
b/ambari-web/latest/src/screens/ClusterAdmin/StackAndVersions/ListVersion.tsx
@@ -44,7 +44,8 @@ import {
 } from "@fortawesome/free-solid-svg-icons";
 import { cloneDeep, get, set } from "lodash";
 import { RequestApi } from "../../../api/requestApi";
-import { Link, useNavigate } from "react-router-dom";
+import { Link } from "react-router-dom";
+import { useHostsFilterNavigation } from "../../Hosts/hostsFilterNavigation";
 import { StackVersion, Item, Response, ClusterCheckPopupData } from "./types";
 import VersionsApi from "../../../api/versionsApi";
 import toast from "react-hot-toast";
@@ -160,7 +161,7 @@ export default function Versions() {
   // Refs for fast switching between upgrade methods
   const methodTypeRef = useRef("");
   const isUpgradeInProgress = upgradeIsRunning && !upgradeSuspended;
-  const navigate = useNavigate();
+  const { goToHostsFilteredByVersion } = useHostsFilterNavigation();
   const { getKDCSessionState } = useKDCSessionState(null);
 
   const {} = usePolling(fetchServices, 6000);
@@ -2248,8 +2249,9 @@ export default function Versions() {
           cancelButtonText: "CLOSE",
         }}
         successCallback={() => {
-          navigate(
-            
`/main/hosts/version/${hostModalData.current.versionName}/${hostModalData.current.versionStatus}`
+          goToHostsFilteredByVersion(
+            hostModalData.current.versionName,
+            hostModalData.current.versionStatus
           );
         }}
       />
diff --git a/ambari-web/latest/src/screens/Hosts/HostComboSearch.tsx 
b/ambari-web/latest/src/screens/Hosts/HostComboSearch.tsx
index c146de797c..3bbe13b459 100644
--- a/ambari-web/latest/src/screens/Hosts/HostComboSearch.tsx
+++ b/ambari-web/latest/src/screens/Hosts/HostComboSearch.tsx
@@ -57,7 +57,6 @@ function HostComboSearch({
   showFilters,
   allHostModels,
   clusterComponents,
-  searchCallback,
   selectedFilters,
   setSelectedFilters,
   onResetFilters,
@@ -81,7 +80,9 @@ function HostComboSearch({
   }, [selectedValue]);
 
   useEffect(() => {
-    searchCallback(selectedFilters);
+    // Deliberately does not echo selectedFilters back through searchCallback: 
that
+    // stale write wiped filters seeded by another page. The add/remove/reset
+    // handlers already call setSelectedFilters.
     updateGroupedFieldOptions();
   }, [selectedFilters.length]);
 
diff --git a/ambari-web/latest/src/screens/Hosts/HostSummary.tsx 
b/ambari-web/latest/src/screens/Hosts/HostSummary.tsx
index 4b8ffa99ad..7dfcb3db93 100644
--- a/ambari-web/latest/src/screens/Hosts/HostSummary.tsx
+++ b/ambari-web/latest/src/screens/Hosts/HostSummary.tsx
@@ -310,7 +310,9 @@ export default function HostsSummary({
   }, [allHostModels]);
 
   useEffect(() => {
-    if (!isEmpty(clusterComponents) && summary.Hostname) {
+    // Gate on this host's own data; the cluster-wide poll can lag on a large
+    // cluster and leave the page spinning after this host has loaded.
+    if (summary.Hostname) {
       setLoading(false);
     }
   }, [clusterComponents, summary]);
diff --git a/ambari-web/latest/src/screens/Hosts/HostsList.tsx 
b/ambari-web/latest/src/screens/Hosts/HostsList.tsx
index 1b43017ede..ff8f982aea 100644
--- a/ambari-web/latest/src/screens/Hosts/HostsList.tsx
+++ b/ambari-web/latest/src/screens/Hosts/HostsList.tsx
@@ -25,9 +25,8 @@ import {
   useRef,
   useState,
 } from "react";
-import { useNavigate, useParams } from "react-router-dom";
 import { Alert, Button, Card, Form, ProgressBar } from "react-bootstrap";
-import { cloneDeep, get, isEmpty, startCase } from "lodash";
+import { cloneDeep, get, startCase } from "lodash";
 import DefaultButton from "../../components/DefaultButton";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import {
@@ -75,7 +74,7 @@ import Spinner from "../../components/Spinner";
 import { HostsApi } from "../../api/hostsApi";
 import useKDCSessionState from "../../hooks/useKDCSessionState";
 import Paginator from "../../components/Paginator";
-import HostComboSearch, { SelectedFilters } from "./HostComboSearch";
+import HostComboSearch from "./HostComboSearch";
 import { getQueryParameters } from "./host";
 import { computeParameters } from "../../globals/updateControl";
 import { translate, translateWithVariables } from "../../Utils/Utility";
@@ -88,12 +87,6 @@ export const getCmponentsToBeRestarted = (data: IHost) => {
 };
 
 export default function HostsList() {
-  const params = useParams<{
-    componentName?: string;
-    versionName?: string;
-    versionStatus?: string;
-  }>();
-  const navigate = useNavigate();
   const { clusterName, serviceComponentInfo } = useContext(AppContext);
   const { allServiceModels: serviceModels, polledHostComponentsData } = 
useContext(ServiceContext);
   const [loading, setLoading] = useState(true);
@@ -107,41 +100,36 @@ export default function HostsList() {
     selectedHosts,
     setSelectedHosts,
   } = useHostsListState();
-  // Open the filter bar when filters are already applied, so restored filters
-  // stay visible instead of being hidden behind a collapsed bar.
+  // Keep restored filters visible instead of hidden behind a collapsed bar.
   const [showFilters, setShowFilters] = useState(
     () => selectedFilters.length > 0
   );
-  // Derive from the restored filters up front: the query effect runs on 
mount, so
-  // starting empty would fire an unfiltered request and show unfiltered hosts 
even
-  // though the filters are shown as applied.
   const [filterString, setFilterString] = useState<string>(() =>
     selectedFilters.length > 0
       ? computeParameters(getQueryParameters(selectedFilters))
       : ""
   );
-  // A component/version deep link seeds the filters from the URL, so clearing 
the
-  // filters has to drop those params too or the effect just re-applies them.
   const clearFilters = useCallback(() => {
     setSelectedFilters([]);
-    if (params.componentName || params.versionName || params.versionStatus) {
-      navigate("/main/hosts", { replace: true });
-    }
-  }, [
-    navigate,
-    params.componentName,
-    params.versionName,
-    params.versionStatus,
-    setSelectedFilters,
-  ]);
-  const [hostApiQueryParams, setHostApiQueryParams] = useState<any>({
-    pageSize: 10,
-    startFrom: 0,
-    sortBy: "Hosts/host_name",
-    sortOrder: "asc",
-    RequestInfo: {
-      query: `page_size=10&from=0`,
-    },
+  }, [setSelectedFilters]);
+  // Must carry the filter: the fetch keys on this object, so without it mount
+  // sends an unfiltered request and every host shows for one cycle.
+  const [hostApiQueryParams, setHostApiQueryParams] = useState<any>(() => {
+    const initialFilter =
+      selectedFilters.length > 0
+        ? computeParameters(getQueryParameters(selectedFilters))
+        : "";
+    return {
+      pageSize: 10,
+      startFrom: 0,
+      sortBy: "Hosts/host_name",
+      sortOrder: "asc",
+      RequestInfo: {
+        query: initialFilter
+          ? `page_size=10&from=0&${initialFilter}`
+          : `page_size=10&from=0`,
+      },
+    };
   });
   const [clusterComponents, setClusterComponents] = useState<any>({});
   const [clusterLoadError, setClusterLoadError] = useState<string | 
null>(null);
@@ -168,10 +156,19 @@ export default function HostsList() {
   // {{#havePermissions "HOST.ADD_DELETE_COMPONENTS, HOST.TOGGLE_MAINTENANCE, 
HOST.ADD_DELETE_HOSTS"}}
   const canShowHostActions = havePermissions("HOST.ADD_DELETE_COMPONENTS, 
HOST.TOGGLE_MAINTENANCE, HOST.ADD_DELETE_HOSTS");
 
+  // Clear the spinner when a host response lands - not on the cluster-wide
+  // components poll, which can arrive first and show an empty table.
+  const applyHostModels = useCallback(
+    (models: Host[] | ((prev: Host[]) => Host[])) => {
+      setCurrentHostModels(models);
+      setLoading(false);
+    },
+    []
+  );
+
   const hostData = useHostConfigUpdater(
     hostApiQueryParams,
-    currentHostModels,
-    setCurrentHostModels,
+    applyHostModels,
     setTotalItems,
     setPaginationLoading
   );
@@ -185,8 +182,7 @@ export default function HostsList() {
     order: "asc",
   });
 
-  // Filters can also arrive after mount (for example from a component or 
version
-  // deep link), so reveal the bar whenever any filter becomes active.
+  // Filters can arrive after mount, set by another page before navigating 
here.
   useEffect(() => {
     if (selectedFilters.length > 0) {
       setShowFilters(true);
@@ -199,7 +195,6 @@ export default function HostsList() {
     if (polledHostComponentsData?.items) {
       setClusterComponents(polledHostComponentsData);
       setClusterLoadError(null);
-      setLoading(false);
     }
   }, [polledHostComponentsData]);
 
@@ -209,114 +204,6 @@ export default function HostsList() {
     }
   }, [clusterName, clusterRetryCount]);
 
-  useEffect(() => {
-    if (params.componentName && !isEmpty(clusterComponents)) {
-      // clusterComponents comes from a poll, so this effect re-runs regularly.
-      // Seed only while no filter is set: that still applies the component 
from
-      // the URL on landing, but a later re-run leaves the user's filters 
alone.
-      if (selectedFilters.length > 0) {
-        return;
-      }
-      const component = get(clusterComponents, "items", []).find(
-        (component: any) =>
-          get(component, "ServiceComponentInfo.component_name", "") ===
-          params.componentName
-      );
-      if (component) {
-        const newFilter: SelectedFilters = [
-          {
-            field: {
-              label: get(
-                component,
-                "host_components.[0].HostRoles.display_name",
-                get(component, "ServiceComponentInfo.component_name", "")
-              ),
-              value: get(component, "ServiceComponentInfo.component_name", ""),
-              name: "componentState",
-            },
-            value: {
-              label: "All",
-              value: "ALL",
-            },
-          },
-        ];
-        setSelectedFilters(newFilter);
-      }
-    }
-  }, [params.componentName, clusterComponents, selectedFilters.length]);
-
-  useEffect(() => {
-    if (
-      params.versionName &&
-      params.versionStatus &&
-      !isEmpty(stackVersionList)
-    ) {
-      // Seed only while no filter is set, so a refreshed stackVersionList does
-      // not discard filters the user added on top of the ones from the URL.
-      if (selectedFilters.length > 0) {
-        return;
-      }
-      const versionExists = stackVersionList.some(
-        (version: any) =>
-          version.displayName === params.versionName &&
-          version.state === params.versionStatus
-      );
-      if (versionExists || params.versionStatus === "NOT_INSTALLED") {
-        const status =
-          params.versionStatus === "NOT_INSTALLED"
-            ? ["INSTALLING", "INSTALL_FAILED", "OUT_OF_SYNC"]
-            : params.versionStatus;
-        const newFilter = [
-          {
-            field: {
-              label: "Stack Version",
-              value: "version",
-              name: "host",
-            },
-            value: {
-              label: params.versionName,
-              value: params.versionName,
-            },
-          },
-          {
-            field: {
-              label: "Version State",
-              value: "versionState",
-              name: "host",
-            },
-            value: {
-              label: status,
-              value: status,
-            },
-          },
-        ];
-        setSelectedFilters(newFilter);
-      }
-    }
-  }, [
-    params.versionName,
-    params.versionStatus,
-    stackVersionList,
-    selectedFilters.length,
-  ]);
-
-  useEffect(() => {
-    if (
-      (!params.componentName && !isEmpty(currentHostModels)) ||
-      (params.componentName &&
-        !isEmpty(clusterComponents) &&
-        filterString.includes(params.componentName)) ||
-      (params.versionName &&
-        params.versionStatus &&
-        !isEmpty(clusterComponents) &&
-        !isEmpty(stackVersionList) &&
-        filterString.includes(params.versionName) &&
-        filterString.includes(params.versionStatus))
-    ) {
-      setLoading(false);
-    }
-  }, [currentHostModels]);
-
   useEffect(() => {
     if (!filterString) {
       setAllHostCount(totalItems);
@@ -344,6 +231,13 @@ export default function HostsList() {
         (currentPage - 1) * itemsPerPage
       }`;
     }
+    // Skip no-op rebuilds; the fetch effect keys on this object by reference.
+    if (
+      hostApiQueryParamsCopy.RequestInfo.query ===
+      get(hostApiQueryParams, "RequestInfo.query", "")
+    ) {
+      return;
+    }
     setPaginationLoading(true);
     setHostApiQueryParams(hostApiQueryParamsCopy);
     setCurrentPage(1);
@@ -359,6 +253,12 @@ export default function HostsList() {
       } else {
         hostApiQueryParamsCopy.RequestInfo.query = 
`page_size=${itemsPerPage}&from=0`;
       }
+      if (
+        hostApiQueryParamsCopy.RequestInfo.query ===
+        get(hostApiQueryParams, "RequestInfo.query", "")
+      ) {
+        return;
+      }
       setPaginationLoading(true);
       setHostApiQueryParams(hostApiQueryParamsCopy);
     } else {
@@ -379,6 +279,12 @@ export default function HostsList() {
         (currentPage - 1) * itemsPerPage
       }`;
     }
+    if (
+      hostApiQueryParamsCopy.RequestInfo.query ===
+      get(hostApiQueryParams, "RequestInfo.query", "")
+    ) {
+      return;
+    }
     setPaginationLoading(true);
     setHostApiQueryParams(hostApiQueryParamsCopy);
   }, [currentPage]);
diff --git a/ambari-web/latest/src/screens/Hosts/hostsFilterNavigation.ts 
b/ambari-web/latest/src/screens/Hosts/hostsFilterNavigation.ts
new file mode 100644
index 0000000000..b401f92b8e
--- /dev/null
+++ b/ambari-web/latest/src/screens/Hosts/hostsFilterNavigation.ts
@@ -0,0 +1,110 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Filters applied when the Hosts List is opened from elsewhere in the app.
+ *
+ * Mirrors Ember's routes/main.js `filterHosts`, which sets the filter before
+ * transitioning so the page mounts already filtered. A URL-param route (e.g.
+ * hosts/component/:componentName) would remount HostsList and reset the
+ * shared filter/selection state from HostsListStateContext, and the filter is
+ * only derivable after cluster data loads - so an unfiltered request would go
+ * out first anyway. Setting the filter via context and navigating to the
+ * plain /main/hosts route avoids both problems.
+ */
+
+import { useNavigate } from "react-router-dom";
+import { useHostsListState } from "../../store/HostsListStateContext";
+
+export type HostsFilter = {
+  field: { label: string; value: string; name?: string };
+  value: { label: any; value: any };
+};
+
+export const HOSTS_LIST_PATH = "/main/hosts";
+
+/**
+ * Filter matching every host that runs the given component.
+ */
+export const buildComponentHostsFilter = (
+  componentName: string,
+  displayName?: string
+): HostsFilter[] => [
+  {
+    field: {
+      label: displayName || componentName,
+      value: componentName,
+      name: "componentState",
+    },
+    value: {
+      label: "All",
+      value: "ALL",
+    },
+  },
+];
+
+/**
+ * Filter matching every host on the given stack version and state. 
NOT_INSTALLED
+ * expands to what the versions page counts as "not installed".
+ */
+export const buildVersionHostsFilter = (
+  versionName: string,
+  versionStatus: string
+): HostsFilter[] => {
+  const status =
+    versionStatus === "NOT_INSTALLED"
+      ? ["INSTALLING", "INSTALL_FAILED", "OUT_OF_SYNC"]
+      : versionStatus;
+  return [
+    {
+      field: { label: "Stack Version", value: "version", name: "host" },
+      value: { label: versionName, value: versionName },
+    },
+    {
+      field: { label: "Version State", value: "versionState", name: "host" },
+      value: { label: status, value: status },
+    },
+  ];
+};
+
+/**
+ * Opens the Hosts List pre-filtered, applying the filter before navigating so
+ * the page never issues an unfiltered request first.
+ */
+export const useHostsFilterNavigation = () => {
+  const navigate = useNavigate();
+  const { setSelectedFilters } = useHostsListState();
+
+  const goToHostsFilteredByComponent = (
+    componentName: string,
+    displayName?: string
+  ) => {
+    setSelectedFilters(buildComponentHostsFilter(componentName, displayName));
+    navigate(HOSTS_LIST_PATH);
+  };
+
+  const goToHostsFilteredByVersion = (
+    versionName: string,
+    versionStatus: string
+  ) => {
+    setSelectedFilters(buildVersionHostsFilter(versionName, versionStatus));
+    navigate(HOSTS_LIST_PATH);
+  };
+
+  return { goToHostsFilteredByComponent, goToHostsFilteredByVersion };
+};
diff --git a/ambari-web/latest/src/screens/Hosts/index.tsx 
b/ambari-web/latest/src/screens/Hosts/index.tsx
index 6488d0ada4..7a433365c7 100644
--- a/ambari-web/latest/src/screens/Hosts/index.tsx
+++ b/ambari-web/latest/src/screens/Hosts/index.tsx
@@ -101,7 +101,6 @@ export function Hosts() {
 
   const hostData = useHostConfigUpdater(
     hostApiQueryParams,
-    allHostModels,
     setAllHostModels,
   );
   const { startHostCheck, stopHostCheck, isHostCheckRunning, hostCheckResult } 
=
diff --git a/ambari-web/latest/src/screens/Services/HDFSFederationSummary.tsx 
b/ambari-web/latest/src/screens/Services/HDFSFederationSummary.tsx
index 0747ba345c..ab9ffe4ace 100644
--- a/ambari-web/latest/src/screens/Services/HDFSFederationSummary.tsx
+++ b/ambari-web/latest/src/screens/Services/HDFSFederationSummary.tsx
@@ -18,8 +18,9 @@
 
 import { Badge, Col, Row, Stack } from "react-bootstrap";
 import { filter, find, lowerCase, startCase } from "lodash";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { useNavigate } from "react-router-dom";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useHostsFilterNavigation } from "../Hosts/hostsFilterNavigation";
 import { statusIconMap } from "./constants";
 import modalManager from "../../store/ModalManager";
 import { AlertsModal } from "./ServiceAlerts";
@@ -36,6 +37,7 @@ function HDFSFederationSummary({
   alerts,
 }: HDFSFederationSummaryProps) {
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   function getComponentAlerts(componentName: string) {
     const criticalAlerts = filter(alerts, ["highestStatus", "CRITICAL"]);
@@ -782,7 +784,7 @@ function HDFSFederationSummary({
             </h3>
             <div
               className="custom-link text-uppercase fs-12 mt-2"
-              onClick={() => navigate("/main/hosts/component/DATANODE")}
+              onClick={() => goToHostsFilteredByComponent("DATANODE", 
"DataNode")}
             >
               DATANODES
             </div>
@@ -796,7 +798,7 @@ function HDFSFederationSummary({
             </h3>
             <div
               className="custom-link text-uppercase fs-12 mt-2"
-              onClick={() => navigate("/main/hosts/component/HDFS_ROUTER")}
+              onClick={() => goToHostsFilteredByComponent("HDFS_ROUTER", 
"Router")}
             >
               ROUTERS
             </div>
@@ -810,7 +812,7 @@ function HDFSFederationSummary({
             </h3>
             <div
               className="custom-link text-uppercase fs-12 mt-2"
-              onClick={() => navigate("/main/hosts/component/JOURNALNODE")}
+              onClick={() => goToHostsFilteredByComponent("JOURNALNODE", 
"JournalNode")}
             >
               JOURNALNODES
             </div>
@@ -824,7 +826,7 @@ function HDFSFederationSummary({
             </h3>
             <div
               className="custom-link text-uppercase fs-12 mt-2"
-              onClick={() => navigate("/main/hosts/component/NFS_GATEWAY")}
+              onClick={() => goToHostsFilteredByComponent("NFS_GATEWAY", "NFS 
Gateway")}
             >
               NFSGATEWAYS
             </div>
diff --git a/ambari-web/latest/src/screens/Services/ServiceComponents.tsx 
b/ambari-web/latest/src/screens/Services/ServiceComponents.tsx
index 52b13af188..ad232f0e04 100644
--- a/ambari-web/latest/src/screens/Services/ServiceComponents.tsx
+++ b/ambari-web/latest/src/screens/Services/ServiceComponents.tsx
@@ -38,6 +38,7 @@ import { pluralize } from "../../Utils/Utility";
 import modalManager from "../../store/ModalManager";
 import { AlertsModal } from "./ServiceAlerts";
 import { useNavigate } from "react-router-dom";
+import { useHostsFilterNavigation } from "../Hosts/hostsFilterNavigation";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import Tooltip from "../../components/Tooltip";
 import { getComponentAlerts } from "./alertUtils";
@@ -99,6 +100,7 @@ function HDFSSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["hdfs"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["hdfs"]) {
@@ -389,9 +391,7 @@ function HDFSSummary({ alerts }: { alerts: any }) {
                   <div
                     className="custom-link text-uppercase fs-12 mt-2"
                     onClick={() => {
-                      navigate(
-                        `/main/hosts/component/${slaveComponent.componentName}`
-                      );
+                      
goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                     }}
                   >
                     {pluralize(
@@ -493,6 +493,7 @@ function HBASESummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["hbase"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["hbase"]) {
@@ -653,9 +654,7 @@ function HBASESummary({ alerts }: { alerts: any }) {
                   <div
                     className="custom-link text-uppercase fs-12 mt-2"
                     onClick={() => {
-                      navigate(
-                        `/main/hosts/component/${slaveComponent.componentName}`
-                      );
+                      
goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                     }}
                   >
                     {pluralize(
@@ -727,6 +726,7 @@ function RANGERSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["ranger"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["ranger"]) {
@@ -957,9 +957,7 @@ function RANGERSummary({ alerts }: { alerts: any }) {
                   <div
                     className="custom-link text-uppercase fs-12 mt-2"
                     onClick={() => {
-                      navigate(
-                        `/main/hosts/component/${slaveComponent.componentName}`
-                      );
+                      
goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                     }}
                   >
                     {pluralize(
@@ -986,6 +984,7 @@ function ZOOKEEPERSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["zk"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["zk"]) {
@@ -1121,11 +1120,12 @@ function ZOOKEEPERSummary({ alerts }: { alerts: any }) {
                             <div
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 )
                               }
                             >
@@ -1154,6 +1154,7 @@ function KYUUBISummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["kyuubi"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["kyuubi"]) {
@@ -1292,11 +1293,12 @@ function KYUUBISummary({ alerts }: { alerts: any }) {
                             <div
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 )
                               }
                             >
@@ -1325,6 +1327,7 @@ function TRINOGATEWAYSummary({ alerts }: { alerts: any }) 
{
   const stringifiedModel = JSON.stringify(allServiceModels?.["trino_gateway"] 
|| {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["trino_gateway"]) {
@@ -1466,11 +1469,12 @@ function TRINOGATEWAYSummary({ alerts }: { alerts: any 
}) {
                             <div
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 )
                               }
                             >
@@ -1501,6 +1505,7 @@ function MAPREDUCE2Summary({ alerts }: { alerts: any }) {
   );
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["mapreduce2"]) {
@@ -1636,11 +1641,12 @@ function MAPREDUCE2Summary({ alerts }: { alerts: any }) 
{
                             <div
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 )
                               }
                             >
@@ -1668,7 +1674,7 @@ function TEZSummary() {
 
   const stringifiedModel = JSON.stringify(allServiceModels?.["tez"] || {});
 
-  const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["tez"]) {
@@ -1741,11 +1747,12 @@ function TEZSummary() {
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
                                 metricValue > 0 ?
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 ) : ""
                               }
                             >
@@ -1773,7 +1780,7 @@ function KERBEROSSummary() {
 
   const stringifiedModel = JSON.stringify(allServiceModels?.["kerberos"] || 
{});
 
-  const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["kerberos"]) {
@@ -1812,9 +1819,7 @@ function KERBEROSSummary() {
                 <div
                   className="custom-link text-uppercase fs-12 mt-2"
                   onClick={() => {
-                    navigate(
-                      `/main/hosts/component/${clientComponent.componentName}`
-                    );
+                    
goToHostsFilteredByComponent(clientComponent.componentName, 
clientComponent.displayName as string)
                   }}
                 >
                   {pluralize(
@@ -1841,6 +1846,7 @@ function SPARK3Summary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["spark3"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["spark3"]) {
@@ -1954,9 +1960,7 @@ function SPARK3Summary({ alerts }: { alerts: any }) {
                 <div
                   className="custom-link text-uppercase fs-12 mt-2"
                   onClick={() => {
-                    navigate(
-                      `/main/hosts/component/${slaveComponent.componentName}`
-                    );
+                    goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                   }}
                 >
                   {pluralize(
@@ -1999,12 +2003,13 @@ function SPARK3Summary({ alerts }: { alerts: any }) {
                           <div
                             className="custom-link text-uppercase fs-12"
                             onClick={() =>
-                              navigate(
-                                `/main/hosts/component/${component.display_name
-                                  .split(" ")
-                                  .join("_")
-                                  .slice(0, -1)}`
-                              )
+                              goToHostsFilteredByComponent(
+                                  component.display_name
+                                    .split(" ")
+                                    .join("_")
+                                    .slice(0, -1),
+                                  component.display_name
+                                )
                             }
                           >
                             {component.display_name}
@@ -2033,6 +2038,7 @@ function AMBARIMETRICSSummary({ alerts }: { alerts: any 
}) {
   );
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["ambari_metrics"]) {
@@ -2213,9 +2219,7 @@ function AMBARIMETRICSSummary({ alerts }: { alerts: any 
}) {
                 <div
                   className="custom-link text-uppercase fs-12 mt-2"
                   onClick={() => {
-                    navigate(
-                      `/main/hosts/component/${slaveComponent.componentName}`
-                    );
+                    goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                   }}
                 >
                   {pluralize(
@@ -2441,6 +2445,7 @@ function TRINOSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["trino"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["trino"]) {
@@ -2640,9 +2645,7 @@ function TRINOSummary({ alerts }: { alerts: any }) {
                 <div
                   className="custom-link text-uppercase fs-12 mt-2"
                   onClick={() => {
-                    navigate(
-                      `/main/hosts/component/${slaveComponent.componentName}`
-                    );
+                    goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                   }}
                 >
                   {slaveComponent.displayName}
@@ -2659,9 +2662,7 @@ function TRINOSummary({ alerts }: { alerts: any }) {
                 <div
                   className="custom-link text-uppercase fs-12 mt-2"
                   onClick={() => {
-                    navigate(
-                      `/main/hosts/component/${clientComponent.componentName}`
-                    );
+                    
goToHostsFilteredByComponent(clientComponent.componentName, 
clientComponent.displayName as string)
                   }}
                 >
                   {pluralize(
@@ -2688,6 +2689,7 @@ function SSMSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["ssm"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["ssm"]) {
@@ -2815,9 +2817,7 @@ function SSMSummary({ alerts }: { alerts: any }) {
                   <div
                     className="custom-link text-uppercase fs-12 mt-2"
                     onClick={() => {
-                      navigate(
-                        `/main/hosts/component/${slaveComponent.componentName}`
-                      );
+                      
goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                     }}
                   >
                     {pluralize(
@@ -2844,6 +2844,7 @@ function YARNSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["yarn"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["yarn"]) {
@@ -3127,9 +3128,7 @@ function YARNSummary({ alerts }: { alerts: any }) {
                     <div
                       className="custom-link text-uppercase fs-12 mt-2"
                       onClick={() => {
-                        navigate(
-                          
`/main/hosts/component/${slaveComponent.componentName}`
-                        );
+                        
goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                       }}
                     >
                       {pluralize(
@@ -3173,12 +3172,13 @@ function YARNSummary({ alerts }: { alerts: any }) {
                               <div
                                 className="custom-link text-uppercase fs-12"
                                 onClick={() =>
-                                  navigate(
-                                    
`/main/hosts/component/${component.display_name
-                                      .split(" ")
-                                      .join("_")
-                                      .slice(0, -1)}`
-                                  )
+                                  goToHostsFilteredByComponent(
+                                  component.display_name
+                                    .split(" ")
+                                    .join("_")
+                                    .slice(0, -1),
+                                  component.display_name
+                                )
                                 }
                               >
                                 {component.display_name}
@@ -3319,6 +3319,7 @@ function HIVESummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["hive"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["hive"]) {
@@ -3537,11 +3538,12 @@ function HIVESummary({ alerts }: { alerts: any }) {
                             <div
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 )
                               }
                             >
@@ -3569,7 +3571,7 @@ function SQOOPSummary() {
 
   const stringifiedModel = JSON.stringify(allServiceModels?.["sqoop"] || {});
 
-  const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["sqoop"]) {
@@ -3631,11 +3633,12 @@ function SQOOPSummary() {
                               className="custom-link text-uppercase fs-12"
                               onClick={() =>
                                 metricValue > 0 ? 
-                                navigate(
-                                  
`/main/hosts/component/${component.display_name
+                                goToHostsFilteredByComponent(
+                                  component.display_name
                                     .split(" ")
                                     .join("_")
-                                    .slice(0, -1)}`
+                                    .slice(0, -1),
+                                  component.display_name
                                 ) : ""
                               }
                             >
@@ -3664,6 +3667,7 @@ function PINOTSummary({ alerts }: { alerts: any }) {
   const stringifiedModel = JSON.stringify(allServiceModels?.["pinot"] || {});
 
   const navigate = useNavigate();
+  const { goToHostsFilteredByComponent } = useHostsFilterNavigation();
 
   useEffect(() => {
     if (allServiceModels["pinot"]) {
@@ -3797,9 +3801,7 @@ function PINOTSummary({ alerts }: { alerts: any }) {
                   <div
                     className="custom-link text-uppercase fs-12 mt-2"
                     onClick={() => {
-                      navigate(
-                        `/main/hosts/component/${slaveComponent.componentName}`
-                      );
+                      
goToHostsFilteredByComponent(slaveComponent.componentName, 
slaveComponent.displayName as string)
                     }}
                   >
                     {slaveComponent.displayName}


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

Reply via email to