mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Fleet UI: Pending install/uninstall decrease > refetch host data > finish host data polling > refetch host software data (#31186)
This commit is contained in:
parent
23164a5478
commit
06428bd767
5 changed files with 58 additions and 28 deletions
|
|
@ -530,6 +530,8 @@ const DeviceUserPage = ({
|
|||
router={router}
|
||||
onShowInstallDetails={onShowInstallDetails}
|
||||
onShowUninstallDetails={onShowUninstallDetails}
|
||||
refetchHostDetails={refetchHostDetails}
|
||||
isHostDetailsPolling={showRefetchSpinner}
|
||||
/>
|
||||
</TabPanel>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
Loading…
Reference in a new issue