Fleet UI: Pending install/uninstall decrease > refetch host data > finish host data polling > refetch host software data (#31186)

This commit is contained in:
RachelElysia 2025-07-24 10:16:57 -04:00 committed by GitHub
parent 23164a5478
commit 06428bd767
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 58 additions and 28 deletions

View file

@ -530,6 +530,8 @@ const DeviceUserPage = ({
router={router}
onShowInstallDetails={onShowInstallDetails}
onShowUninstallDetails={onShowUninstallDetails}
refetchHostDetails={refetchHostDetails}
isHostDetailsPolling={showRefetchSpinner}
/>
</TabPanel>
)}

View file

@ -1008,6 +1008,8 @@ const HostDetailsPage = ({
hostName={host.display_name}
hostMDMEnrolled={host.mdm.connected_to_fleet}
isHostOnline={host.status === "online"}
refetchHostDetails={refetchHostDetails}
isHostDetailsPolling={showRefetchSpinner}
/>
</TabPanel>
</>

View file

@ -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<Set<string>>(new Set()); // Track for polling
const pollingTimeoutIdRef = useRef<NodeJS.Timeout | null>(null);
const isAwaitingHostDetailsPolling = useRef(isHostDetailsPolling);
const queryKey = useMemo<IHostSoftwareQueryKey[]>(() => {
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 &&

View file

@ -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(
<SelfService
contactUrl={expectedUrl}
deviceToken="123-456"
isSoftwareEnabled
pathname="/test"
queryParams={{
page: 1,
query: "test",
order_key: "name",
order_direction: "asc",
per_page: 10,
vulnerable: true,
available_for_install: false,
min_cvss_score: undefined,
max_cvss_score: undefined,
exploit: false,
category_id: undefined,
self_service: false,
}}
router={createMockRouter()}
onShowInstallDetails={noop}
onShowUninstallDetails={noop}
/>
);
render(<SelfService {...TEST_PROPS} />);
// waiting for the device software data to render
await screen.findByText("test-software");

View file

@ -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<Set<string>>(new Set()); // Track for polling
const pollingTimeoutIdRef = useRef<NodeJS.Timeout | null>(null);
const isAwaitingHostDetailsPolling = useRef(isHostDetailsPolling);
const queryKey = useMemo<IDeviceSoftwareQueryKey[]>(() => {
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 &&