fleet/frontend/pages/SoftwarePage/SoftwareVersionDetailsPage/SoftwareVersionDetailsPage.tsx
jacobshandling e982ca996a
UI – For iPad/iPhones: update refetch behavior, add Not supported to Host software vulnerabilites column (#21165)
## Addresses #21149 and #21148 

1. 21149: Updated refetch behavior – instead of looking for a not
"online" host status and short-circuiting the refetch cycle with an
error, keep trying until the 60s timeout limit:
- Couldn't refetch after 60s: ![Screenshot 2024-08-07 at 2 47
15 PM](https://github.com/user-attachments/assets/19467c43-1e19-43c3-8f89-a6b7e893ed75)
- Successful refetch: ![Screenshot 2024-08-07 at 2 47
35 PM](https://github.com/user-attachments/assets/1ab9b00f-8496-453c-b491-52c0e9aeb51c)

2. 21148: Vulnerabilities `Not supported` on iPad/iPhone host details:
![Screenshot 2024-08-07 at 2 55
44 PM](https://github.com/user-attachments/assets/5527bcdb-fb77-40d4-bbca-62264b7ea561)


- [x] Manual QA for all new/changed functionality

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2024-08-08 09:46:38 -07:00

193 lines
5.2 KiB
TypeScript

/** software/versions/:id */
import React, { useCallback, useContext } from "react";
import { useQuery } from "react-query";
import { useErrorHandler } from "react-error-boundary";
import { RouteComponentProps } from "react-router";
import { AxiosError } from "axios";
import useTeamIdParam from "hooks/useTeamIdParam";
import { AppContext } from "context/app";
import softwareAPI, {
ISoftwareVersionResponse,
IGetSoftwareVersionQueryKey,
} from "services/entities/software";
import hostsCountAPI, {
IHostsCountQueryKey,
IHostsCountResponse,
} from "services/entities/host_count";
import {
ISoftwareVersion,
formatSoftwareType,
isIpadOrIphoneSoftwareSource,
} from "interfaces/software";
import { ignoreAxiosError } from "interfaces/errors";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import Spinner from "components/Spinner";
import MainContent from "components/MainContent";
import TeamsHeader from "components/TeamsHeader";
import Card from "components/Card";
import SoftwareDetailsSummary from "../components/SoftwareDetailsSummary";
import SoftwareVulnerabilitiesTable from "../components/SoftwareVulnerabilitiesTable";
import DetailsNoHosts from "../components/DetailsNoHosts";
import { VulnsNotSupported } from "../components/SoftwareVulnerabilitiesTable/SoftwareVulnerabilitiesTable";
const baseClass = "software-version-details-page";
interface ISoftwareVersionDetailsRouteParams {
id: string;
team_id?: string;
}
type ISoftwareTitleDetailsPageProps = RouteComponentProps<
undefined,
ISoftwareVersionDetailsRouteParams
>;
const SoftwareVersionDetailsPage = ({
routeParams,
router,
location,
}: ISoftwareTitleDetailsPageProps) => {
const { isPremiumTier, isOnGlobalTeam } = useContext(AppContext);
const handlePageError = useErrorHandler();
const versionId = parseInt(routeParams.id, 10);
const {
currentTeamId,
teamIdForApi,
userTeams,
handleTeamChange,
} = useTeamIdParam({
location,
router,
includeAllTeams: true,
includeNoTeam: true,
});
const {
data: softwareVersion,
isLoading: isSoftwareVersionLoading,
isError: isSoftwareVersionError,
} = useQuery<
ISoftwareVersionResponse,
AxiosError,
ISoftwareVersion,
IGetSoftwareVersionQueryKey[]
>(
[{ scope: "softwareVersion", versionId, teamId: teamIdForApi }],
({ queryKey }) => softwareAPI.getSoftwareVersion(queryKey[0]),
{
...DEFAULT_USE_QUERY_OPTIONS,
retry: false,
select: (data) => data.software,
onError: (error) => {
if (!ignoreAxiosError(error, [403, 404])) {
handlePageError(error);
}
},
}
);
const { data: hostsCount } = useQuery<
IHostsCountResponse,
Error,
number,
IHostsCountQueryKey[]
>(
[{ scope: "hosts_count", softwareVersionId: versionId }],
({ queryKey }) => hostsCountAPI.load(queryKey[0]),
{
keepPreviousData: true,
staleTime: 10000, // stale time can be adjusted if fresher data is desired
select: (data) => data.count,
}
);
const onTeamChange = useCallback(
(teamId: number) => {
handleTeamChange(teamId);
},
[handleTeamChange]
);
const renderVulnTable = (swVersion: ISoftwareVersion) => {
if (isIpadOrIphoneSoftwareSource(swVersion.source)) {
const platformText = swVersion.source === "ios_apps" ? "iOS" : "iPadOS";
return <VulnsNotSupported platformText={platformText} />;
}
return (
<SoftwareVulnerabilitiesTable
data={swVersion.vulnerabilities ?? []}
itemName="software item"
isLoading={isSoftwareVersionLoading}
router={router}
teamIdForApi={teamIdForApi}
/>
);
};
const renderContent = () => {
if (isSoftwareVersionLoading) {
return <Spinner />;
}
if (!softwareVersion && !isSoftwareVersionError) {
return null;
}
return (
<>
{isPremiumTier && (
<TeamsHeader
isOnGlobalTeam={isOnGlobalTeam}
currentTeamId={currentTeamId}
userTeams={userTeams}
onTeamChange={onTeamChange}
/>
)}
{isSoftwareVersionError ? (
<DetailsNoHosts
header="Software not detected"
details="No hosts have this software installed."
/>
) : (
<>
<SoftwareDetailsSummary
title={`${softwareVersion.name}, ${softwareVersion.version}`}
type={formatSoftwareType(softwareVersion)}
hosts={hostsCount ?? 0}
queryParams={{
software_version_id: softwareVersion.id,
team_id: teamIdForApi,
}}
name={softwareVersion.name}
source={softwareVersion.source}
/>
<Card
borderRadiusSize="xxlarge"
includeShadow
className={`${baseClass}__vulnerabilities-section`}
>
<h2 className="section__header">Vulnerabilities</h2>
{renderVulnTable(softwareVersion)}
</Card>
</>
)}
</>
);
};
return (
<MainContent className={baseClass}>
<>{renderContent()}</>
</MainContent>
);
};
export default SoftwareVersionDetailsPage;