Fix unreleased UI bugs in device details software tab (#19178)

This commit is contained in:
Sarah Gillespie 2024-05-21 11:49:37 -05:00 committed by GitHub
parent c94e83055f
commit 07c992c178
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 36 additions and 25 deletions

View file

@ -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();

View file

@ -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

View file

@ -26,8 +26,6 @@ const formatSoftwareType = (source: string) => {
return DICT[source] || "Unknown";
};
// interface ISoftwareTableHeadersProps {}
export const generateSoftwareTableData = (
software: IHostSoftware[]
): IHostSoftware[] => {

View file

@ -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}

View file

@ -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

View file

@ -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`;
},