From 06428bd7674de96f52cbeab22d31091dbbd132c7 Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:16:57 -0400 Subject: [PATCH] Fleet UI: Pending install/uninstall decrease > refetch host data > finish host data polling > refetch host software data (#31186) --- .../details/DeviceUserPage/DeviceUserPage.tsx | 2 ++ .../HostDetailsPage/HostDetailsPage.tsx | 2 ++ .../HostSoftwareLibrary.tsx | 23 ++++++++++++++ .../SelfService/SelfService.tests.tsx | 30 ++----------------- .../Software/SelfService/SelfService.tsx | 29 +++++++++++++++++- 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx index 8c306c86cd..65ac3902e5 100644 --- a/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx +++ b/frontend/pages/hosts/details/DeviceUserPage/DeviceUserPage.tsx @@ -530,6 +530,8 @@ const DeviceUserPage = ({ router={router} onShowInstallDetails={onShowInstallDetails} onShowUninstallDetails={onShowUninstallDetails} + refetchHostDetails={refetchHostDetails} + isHostDetailsPolling={showRefetchSpinner} /> )} diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index aecb003d12..1247fefb89 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -1008,6 +1008,8 @@ const HostDetailsPage = ({ hostName={host.display_name} hostMDMEnrolled={host.mdm.connected_to_fleet} isHostOnline={host.status === "online"} + refetchHostDetails={refetchHostDetails} + isHostDetailsPolling={showRefetchSpinner} /> diff --git a/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibrary.tsx b/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibrary.tsx index c139618fac..dbaaf28412 100644 --- a/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibrary.tsx +++ b/frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibrary.tsx @@ -57,6 +57,8 @@ interface IHostInstallersProps { hostScriptsEnabled?: boolean; hostMDMEnrolled?: boolean; isHostOnline?: boolean; + refetchHostDetails: () => void; + isHostDetailsPolling: boolean; } const DEFAULT_SEARCH_QUERY = ""; @@ -107,6 +109,8 @@ const HostSoftwareLibrary = ({ isSoftwareEnabled = false, hostMDMEnrolled, isHostOnline = false, + refetchHostDetails, + isHostDetailsPolling, }: IHostInstallersProps) => { const { renderFlash } = useContext(NotificationContext); const { @@ -127,6 +131,7 @@ const HostSoftwareLibrary = ({ const pendingSoftwareSetRef = useRef>(new Set()); // Track for polling const pollingTimeoutIdRef = useRef(null); + const isAwaitingHostDetailsPolling = useRef(isHostDetailsPolling); const queryKey = useMemo(() => { return [ @@ -143,6 +148,7 @@ const HostSoftwareLibrary = ({ isLoading: hostSoftwareLibraryLoading, isError: hostSoftwareLibraryError, isFetching: hostSoftwareLibraryFetching, + refetch: refetchHostSoftwareLibrary, } = useQuery< IGetHostSoftwareResponse, AxiosError, @@ -157,6 +163,17 @@ const HostSoftwareLibrary = ({ }, }); + // After host details polling (in parent) finishes, refetch software data. + // Ensures self service data reflects updates to installed_versions from the latest host details. + useEffect(() => { + // Detect completion of the host details polling (in parent) + // Once host details polling completes, refetch software data to retreive updated installed_versions keyed from host details data + if (isAwaitingHostDetailsPolling.current && !isHostDetailsPolling) { + refetchHostSoftwareLibrary(); + } + isAwaitingHostDetailsPolling.current = isHostDetailsPolling; + }, [isHostDetailsPolling, refetchHostSoftwareLibrary]); + // Poll for pending installs/uninstalls const { refetch: refetchForPendingInstallsOrUninstalls } = useQuery< IGetHostSoftwareResponse, @@ -178,6 +195,12 @@ const HostSoftwareLibrary = ({ .map((software) => String(software.id)) ); + // Refresh host details if the number of pending installs or uninstalls has decreased + // To update the software library information of the newly installed/uninstalled software + if (newPendingSet.size < pendingSoftwareSetRef.current.size) { + refetchHostDetails(); + } + // Compare new set with the previous set const setsAreEqual = newPendingSet.size === pendingSoftwareSetRef.current.size && diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx index 316612bd1a..f03673384f 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx @@ -31,6 +31,8 @@ const TEST_PROPS: ISoftwareSelfServiceProps = { router: createMockRouter(), onShowInstallDetails: noop, onShowUninstallDetails: noop, + refetchHostDetails: noop, + isHostDetailsPolling: false, }; describe("SelfService", () => { @@ -86,33 +88,7 @@ describe("SelfService", () => { const render = createCustomRenderer({ withBackendMock: true }); - const expectedUrl = "http://example.com"; - - render( - - ); + render(); // waiting for the device software data to render await screen.findByText("test-software"); diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx index 9b5efa24ff..65040d7410 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tsx @@ -68,6 +68,8 @@ export interface ISoftwareSelfServiceProps { router: InjectedRouter; onShowInstallDetails: (uuid?: InstallOrCommandUuid) => void; onShowUninstallDetails: (details?: ISoftwareUninstallDetails) => void; + refetchHostDetails: () => void; + isHostDetailsPolling: boolean; } const SoftwareSelfService = ({ @@ -79,6 +81,8 @@ const SoftwareSelfService = ({ router, onShowInstallDetails, onShowUninstallDetails, + refetchHostDetails, + isHostDetailsPolling, }: ISoftwareSelfServiceProps) => { const { renderFlash } = useContext(NotificationContext); @@ -98,6 +102,7 @@ const SoftwareSelfService = ({ const pendingSoftwareSetRef = useRef>(new Set()); // Track for polling const pollingTimeoutIdRef = useRef(null); + const isAwaitingHostDetailsPolling = useRef(isHostDetailsPolling); const queryKey = useMemo(() => { return [ @@ -112,7 +117,12 @@ const SoftwareSelfService = ({ }, [deviceToken, queryParams.page, queryParams.query]); // Fetch self-service software (regular API call) - const { isLoading, isError, isFetching } = useQuery< + const { + isLoading, + isError, + isFetching, + refetch: refetchSelfServiceData, + } = useQuery< IGetDeviceSoftwareResponse, AxiosError, IGetDeviceSoftwareResponse, @@ -126,6 +136,17 @@ const SoftwareSelfService = ({ }, }); + // After host details polling (in parent) finishes, refetch software data. + // Ensures self service data reflects updates to installed_versions from the latest host details. + useEffect(() => { + // Detect completion of the host details polling (in parent) + // Once host details polling completes, refetch software data to retreive updated installed_versions keyed from host details data + if (isAwaitingHostDetailsPolling.current && !isHostDetailsPolling) { + refetchSelfServiceData(); + } + isAwaitingHostDetailsPolling.current = isHostDetailsPolling; + }, [isHostDetailsPolling, refetchSelfServiceData]); + // Poll for pending installs/uninstalls const { refetch: refetchForPendingInstallsOrUninstalls } = useQuery< IGetDeviceSoftwareResponse, @@ -147,6 +168,12 @@ const SoftwareSelfService = ({ .map((software) => String(software.id)) ); + // Refresh host details if the number of pending installs or uninstalls has decreased + // To update the software library information + if (newPendingSet.size < pendingSoftwareSetRef.current.size) { + refetchHostDetails(); + } + // Compare new set with the previous set const setsAreEqual = newPendingSet.size === pendingSoftwareSetRef.current.size &&