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]