mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
Fix unreleased UI bugs in device details software tab (#19178)
This commit is contained in:
parent
c94e83055f
commit
07c992c178
6 changed files with 36 additions and 25 deletions
|
|
@ -33,7 +33,7 @@ const mockLocation = {
|
|||
};
|
||||
|
||||
describe("Device User Page", () => {
|
||||
it("renders the software empty message if the device has no software", async () => {
|
||||
it("hides the software tab if the device has no software", async () => {
|
||||
const render = createCustomRenderer({
|
||||
withBackendMock: true,
|
||||
});
|
||||
|
|
@ -50,7 +50,7 @@ describe("Device User Page", () => {
|
|||
// waiting for the device data to render
|
||||
await screen.findByText("About");
|
||||
|
||||
await user.click(screen.getByRole("tab", { name: "Software" }));
|
||||
expect(screen.queryByText(/Software/)).not.toBeInTheDocument();
|
||||
|
||||
// TODO: Fix this to the new copy
|
||||
// expect(screen.getByText("No software detected")).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -333,6 +333,14 @@ const DeviceUserPage = ({
|
|||
const findSelectedTab = (pathname: string) =>
|
||||
findIndex(tabPaths, (x) => x.startsWith(pathname.split("?")[0]));
|
||||
|
||||
// TODO: This is a temporary fix that conditionally shows the new software tab depending on
|
||||
// whether software items returned in the device details response (legacy endpoint).
|
||||
// If the tab is selected, we call the new host software endpoint and display those results.
|
||||
// Software in the legacy response is only being used as a proxy for `iseSoftwareEnabled`.
|
||||
// Ideally we should be checking the config for whether software is enabled to show/hide the tab,
|
||||
// but it isn't available via device token authenticated API. And we need better specified empty states.
|
||||
const isSoftwareEnabled = !!host?.software.length;
|
||||
|
||||
return (
|
||||
<div className="core-wrapper">
|
||||
{!host || isLoadingHost ? (
|
||||
|
|
@ -386,7 +394,7 @@ const DeviceUserPage = ({
|
|||
>
|
||||
<TabList>
|
||||
<Tab>Details</Tab>
|
||||
<Tab>Software</Tab>
|
||||
{isSoftwareEnabled && <Tab>Software</Tab>}
|
||||
{isPremiumTier && (
|
||||
<Tab>
|
||||
<div>
|
||||
|
|
@ -405,17 +413,20 @@ const DeviceUserPage = ({
|
|||
munki={deviceMacAdminsData?.munki}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<SoftwareCard
|
||||
id={deviceAuthToken}
|
||||
isFleetdHost={!!host.orbit_version}
|
||||
router={router}
|
||||
pathname={location.pathname}
|
||||
queryParams={parseHostSoftwareQueryParams(location.query)}
|
||||
isMyDevicePage
|
||||
teamId={host.team_id || 0}
|
||||
/>
|
||||
</TabPanel>
|
||||
{isSoftwareEnabled && (
|
||||
<TabPanel>
|
||||
<SoftwareCard
|
||||
id={deviceAuthToken}
|
||||
isFleetdHost={!!host.orbit_version}
|
||||
router={router}
|
||||
pathname={location.pathname}
|
||||
queryParams={parseHostSoftwareQueryParams(location.query)}
|
||||
isMyDevicePage
|
||||
teamId={host.team_id || 0}
|
||||
isSoftwareEnabled={isSoftwareEnabled}
|
||||
/>
|
||||
</TabPanel>
|
||||
)}
|
||||
{isPremiumTier && (
|
||||
<TabPanel>
|
||||
<PoliciesCard
|
||||
|
|
|
|||
|
|
@ -26,8 +26,6 @@ const formatSoftwareType = (source: string) => {
|
|||
return DICT[source] || "Unknown";
|
||||
};
|
||||
|
||||
// interface ISoftwareTableHeadersProps {}
|
||||
|
||||
export const generateSoftwareTableData = (
|
||||
software: IHostSoftware[]
|
||||
): IHostSoftware[] => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const baseClass = "host-software-table";
|
|||
|
||||
interface IHostSoftwareTableProps {
|
||||
tableConfig: any; // TODO: type
|
||||
data: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
|
||||
data?: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
|
||||
isLoading: boolean;
|
||||
router: InjectedRouter;
|
||||
sortHeader: string;
|
||||
|
|
@ -26,7 +26,7 @@ interface IHostSoftwareTableProps {
|
|||
pagePath: string;
|
||||
}
|
||||
|
||||
const SoftwareCount = (count: number) => {
|
||||
const SoftwareCount = ({ count }: { count: number }) => {
|
||||
return (
|
||||
<div className={`${baseClass}__count`}>
|
||||
<span>
|
||||
|
|
@ -107,8 +107,9 @@ const HostSoftwareTable = ({
|
|||
);
|
||||
|
||||
const memoizedSoftwareCount = useCallback(() => {
|
||||
return SoftwareCount(data.count || data.software.length || 0);
|
||||
}, [data.count, data.software.length]);
|
||||
const count = data?.count || data?.software.length || 0;
|
||||
return <SoftwareCount count={count} />;
|
||||
}, [data?.count, data?.software.length]);
|
||||
|
||||
const memoizedEmptyComponent = useCallback(() => {
|
||||
return <EmptySoftwareTable isSearching={searchQuery !== ""} />;
|
||||
|
|
@ -120,12 +121,13 @@ const HostSoftwareTable = ({
|
|||
renderCount={memoizedSoftwareCount}
|
||||
resultsTitle="software items"
|
||||
columnConfigs={tableConfig}
|
||||
data={data.software}
|
||||
data={data?.software || []}
|
||||
isLoading={isLoading}
|
||||
defaultSortHeader={sortHeader}
|
||||
defaultSortDirection={sortDirection}
|
||||
defaultSearchQuery={searchQuery}
|
||||
defaultPageIndex={page}
|
||||
disableNextPage={data?.meta.has_next_results === false}
|
||||
pageSize={DEFAULT_PAGE_SIZE}
|
||||
inputPlaceHolder="Search by name"
|
||||
onQueryChange={onQueryChange}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ const SoftwareCard = ({
|
|||
},
|
||||
{
|
||||
...DEFAULT_USE_QUERY_OPTIONS,
|
||||
enabled: isSoftwareEnabled && !isMyDevicePage,
|
||||
enabled: isSoftwareEnabled && !isMyDevicePage, // if disabled, we'll always show a generic "No software detected" message
|
||||
keepPreviousData: true,
|
||||
staleTime: 7000,
|
||||
}
|
||||
|
|
@ -250,8 +250,8 @@ const SoftwareCard = ({
|
|||
<Spinner />
|
||||
) : (
|
||||
<>
|
||||
{(isError || !data) && <DataError />}
|
||||
{!isError && data && (
|
||||
{isError && <DataError />}
|
||||
{!isError && (
|
||||
<HostSoftwareTable
|
||||
isLoading={
|
||||
isMyDevicePage ? deviceSoftwareFetching : hostSoftwareFetching
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export default {
|
|||
// Device endpoints
|
||||
DEVICE_USER_DETAILS: `/${API_VERSION}/fleet/device`,
|
||||
DEVICE_SOFTWARE: (token: string) =>
|
||||
`/${API_VERSION}/fleet/devices/${token}/software`,
|
||||
`/${API_VERSION}/fleet/device/${token}/software`,
|
||||
DEVICE_USER_RESET_ENCRYPTION_KEY: (token: string): string => {
|
||||
return `/${API_VERSION}/fleet/device/${token}/rotate_encryption_key`;
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue