Fleet UI: Update software empty states (#19744)

This commit is contained in:
RachelElysia
2024-06-17 09:57:03 -04:00
committed by GitHub
parent 58b982c01e
commit 85a5c6eecb
11 changed files with 67 additions and 64 deletions
+1
View File
@@ -0,0 +1 @@
- Clean up software empty states in the UI
@@ -1,12 +1,13 @@
import React, { useContext, useMemo } from "react";
import React, { useMemo } from "react";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import { Row } from "react-table";
import { InjectedRouter } from "react-router";
import PATHS from "router/paths";
import { InjectedRouter } from "react-router";
import { AppContext } from "context/app";
import { buildQueryStringFromParams } from "utilities/url";
import { ISoftwareResponse } from "interfaces/software";
import { ITableQueryData } from "components/TableContainer/TableContainer";
import TabsWrapper from "components/TabsWrapper";
import TableContainer from "components/TableContainer";
import TableDataError from "components/DataError";
@@ -20,12 +21,14 @@ interface ISoftwareCardProps {
isCollectingInventory: boolean;
isSoftwareFetching: boolean;
isSoftwareEnabled?: boolean;
software: any;
software?: ISoftwareResponse;
teamId?: number;
pageIndex: number;
navTabIndex: any;
onTabChange: any;
onQueryChange: any;
navTabIndex: number;
onTabChange: (index: number, last: number, event: Event) => boolean | void;
onQueryChange?:
| ((queryData: ITableQueryData) => void)
| ((queryData: ITableQueryData) => number);
router: InjectedRouter;
}
@@ -53,8 +56,6 @@ const Software = ({
teamId,
router,
}: ISoftwareCardProps): JSX.Element => {
const { noSandboxHosts } = useContext(AppContext);
const tableHeaders = useMemo(() => generateTableHeaders(teamId), [teamId]);
const handleRowSelect = (row: IRowProps) => {
@@ -6,7 +6,6 @@ import { Row } from "react-table";
import PATHS from "router/paths";
import { AppContext } from "context/app";
import { GITHUB_NEW_ISSUE_LINK } from "utilities/constants";
import CustomLink from "components/CustomLink";
@@ -55,8 +54,6 @@ const SoftwareOSTable = ({
isLoading,
resetPageIndex,
}: ISoftwareOSTableProps) => {
const { isSandboxMode, noSandboxHosts } = useContext(AppContext);
const determineQueryParamChange = useCallback(
(newTableQuery: ITableQueryData) => {
const changedEntry = Object.entries(newTableQuery).find(([key, val]) => {
@@ -168,7 +165,11 @@ const SoftwareOSTable = ({
isLoading={isLoading}
resultsTitle="items"
emptyComponent={() => (
<EmptySoftwareTable isSoftwareDisabled={!isSoftwareEnabled} />
<EmptySoftwareTable
tableName="operating systems"
isSoftwareDisabled={!isSoftwareEnabled}
isNotDetectingSoftware // non-searchable table renders not detecting by default
/>
)}
defaultSortHeader={orderKey}
defaultSortDirection={orderDirection}
@@ -188,7 +188,9 @@ const SoftwareTable = ({
// determines if a user be able to search in the table
const searchable =
isSoftwareEnabled &&
(!!tableData || query !== "" || softwareFilter === "vulnerableSoftware");
((tableData && tableData.length > 0) ||
query !== "" ||
softwareFilter !== "allSoftware");
const handleShowVersionsToggle = () => {
const queryParams: Record<string, string | number | undefined> = {
@@ -276,6 +278,10 @@ const SoftwareTable = ({
};
const renderCustomFilters = () => {
// Hide filters if no software is detected with no filters present
if (query === "" && !showVersions && softwareFilter === "allSoftware")
return <></>;
const options = showVersions
? SOFTWARE_VERSIONS_DROPDOWN_OPTIONS
: SOFTWARE_TITLES_DROPDOWN_OPTIONS;
@@ -327,8 +333,7 @@ const SoftwareTable = ({
<EmptySoftwareTable
softwareFilter={softwareFilter}
isSoftwareDisabled={!isSoftwareEnabled}
isCollectingSoftware={false} // TODO: update with new API
isSearching={query !== ""}
isNotDetectingSoftware={query === ""}
/>
)}
defaultSortHeader={orderKey}
@@ -93,9 +93,7 @@ describe("Software Vulnerabilities table", () => {
/>
);
expect(
screen.getByText("No software match the current search criteria")
).toBeInTheDocument();
expect(screen.getByText("No software detected")).toBeInTheDocument();
expect(screen.queryByText("Vulnerability")).toBeNull();
});
@@ -133,7 +131,7 @@ describe("Software Vulnerabilities table", () => {
);
expect(
screen.getByText("No software match the current search criteria")
screen.getByText("No items match the current search criteria")
).toBeInTheDocument();
expect(screen.queryByText("Vulnerability")).toBeNull();
});
@@ -61,9 +61,7 @@ const SoftwareVulnerabilitiesTable = ({
isLoading,
resetPageIndex,
}: ISoftwareVulnerabilitiesTableProps) => {
const { isPremiumTier, isSandboxMode, noSandboxHosts } = useContext(
AppContext
);
const { isPremiumTier } = useContext(AppContext);
const determineQueryParamChange = useCallback(
(newTableQuery: ITableQueryData) => {
@@ -140,7 +138,6 @@ const SoftwareVulnerabilitiesTable = ({
if (!data) return [];
return generateTableConfig(
isPremiumTier,
isSandboxMode,
router,
{
includeName: true,
@@ -255,7 +252,11 @@ const SoftwareVulnerabilitiesTable = ({
isLoading={isLoading}
resultsTitle={"items"}
emptyComponent={() => (
<EmptySoftwareTable isSoftwareDisabled={!isSoftwareEnabled} />
<EmptySoftwareTable
tableName="vulnerabilities"
isSoftwareDisabled={!isSoftwareEnabled}
isNotDetectingSoftware={query === ""}
/>
)}
defaultSortHeader={orderKey}
defaultSortDirection={orderDirection}
@@ -14,7 +14,6 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import LinkCell from "components/TableContainer/DataTable/LinkCell";
import TooltipWrapper from "components/TooltipWrapper";
import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip";
import { HumanTimeDiffWithDateTip } from "components/HumanTimeDiffWithDateTip";
interface ICellProps {
@@ -57,7 +56,6 @@ interface IVulnerabilitiesTableConfigOptions {
const generateTableHeaders = (
isPremiumTier?: boolean,
isSandboxMode?: boolean,
router?: InjectedRouter,
configOptions?: IVulnerabilitiesTableConfigOptions,
teamId?: number
@@ -124,7 +122,6 @@ const generateTableHeaders = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -157,7 +154,6 @@ const generateTableHeaders = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -191,7 +187,6 @@ const generateTableHeaders = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -225,7 +220,6 @@ const generateTableHeaders = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -10,32 +10,46 @@ import { ISoftwareDropdownFilterVal } from "pages/SoftwarePage/SoftwareTitles/So
export interface IEmptySoftwareTableProps {
softwareFilter?: ISoftwareDropdownFilterVal;
/** tableName is displayed in the search empty state */
tableName?: string;
isSoftwareDisabled?: boolean;
/** isNotDetectingSoftware renders empty states when no search string is present */
isNotDetectingSoftware?: boolean;
/** isCollectingSoftware is only used on the Dashboard page with a TODO to revisit */
isCollectingSoftware?: boolean;
isSearching?: boolean;
}
const generateTypeText = (softwareFilter?: ISoftwareDropdownFilterVal) => {
const generateTypeText = (
tableName: string,
softwareFilter?: ISoftwareDropdownFilterVal
) => {
if (softwareFilter === "installableSoftware") {
return "installable";
return "installable software";
}
return softwareFilter === "vulnerableSoftware" ? "vulnerable" : "";
if (softwareFilter === "vulnerableSoftware") {
return "vulnerable software";
}
return tableName;
};
const EmptySoftwareTable = ({
softwareFilter,
tableName = "software",
isSoftwareDisabled,
isNotDetectingSoftware,
isCollectingSoftware,
isSearching,
}: IEmptySoftwareTableProps): JSX.Element => {
const softwareTypeText = generateTypeText(softwareFilter);
const softwareTypeText = generateTypeText(tableName, softwareFilter);
const emptySoftware: IEmptyTableProps = {
header: `No ${softwareTypeText} software match the current search criteria`,
info:
"This report is updated every hour to protect the performance of your devices.",
header: "No items match the current search criteria",
info: `Expecting to see ${softwareTypeText}? Check back later.`,
};
if (isNotDetectingSoftware) {
emptySoftware.header = "No software detected";
}
if (isCollectingSoftware) {
emptySoftware.header = "No software detected";
emptySoftware.info =
@@ -56,11 +70,6 @@ const EmptySoftwareTable = ({
</>
);
}
if (softwareFilter === "vulnerableSoftware" && !isSearching) {
emptySoftware.header = "No vulnerable software detected";
emptySoftware.info =
"This report is updated every hour to protect the performance of your devices.";
}
return (
<EmptyTable
@@ -13,7 +13,6 @@ import { AppContext } from "context/app";
import { ISoftwareVulnerability } from "interfaces/software";
import { GITHUB_NEW_ISSUE_LINK } from "utilities/constants";
import { buildQueryStringFromParams } from "utilities/url";
import TableContainer from "components/TableContainer";
import EmptyTable from "components/EmptyTable";
import CustomLink from "components/CustomLink";
@@ -68,7 +67,7 @@ const SoftwareVulnerabilitiesTable = ({
router,
teamIdForApi,
}: ISoftwareVulnerabilitiesTableProps) => {
const { isPremiumTier, isSandboxMode } = useContext(AppContext);
const { isPremiumTier } = useContext(AppContext);
const classNames = classnames(baseClass, className);
@@ -88,14 +87,8 @@ const SoftwareVulnerabilitiesTable = ({
};
const tableHeaders = useMemo(
() =>
generateTableConfig(
Boolean(isPremiumTier),
Boolean(isSandboxMode),
router,
teamIdForApi
),
[isPremiumTier, isSandboxMode]
() => generateTableConfig(Boolean(isPremiumTier), router, teamIdForApi),
[isPremiumTier]
);
return (
<div className={classNames}>
@@ -10,7 +10,6 @@ import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCel
import TextCell from "components/TableContainer/DataTable/TextCell";
import TooltipWrapper from "components/TooltipWrapper";
import { HumanTimeDiffWithDateTip } from "components/HumanTimeDiffWithDateTip";
import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip";
import ProbabilityOfExploit from "components/ProbabilityOfExploit/ProbabilityOfExploit";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import LinkCell from "components/TableContainer/DataTable/LinkCell";
@@ -49,7 +48,6 @@ interface IDataColumn {
const generateTableConfig = (
isPremiumTier: boolean,
isSandboxMode: boolean,
router: InjectedRouter,
teamId?: number
): IDataColumn[] => {
@@ -108,7 +106,6 @@ const generateTableConfig = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -141,7 +138,6 @@ const generateTableConfig = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -175,7 +171,6 @@ const generateTableConfig = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -209,7 +204,6 @@ const generateTableConfig = (
value={titleWithTooltip}
isSortedDesc={headerProps.column.isSortedDesc}
/>
{isSandboxMode && <PremiumFeatureIconWithTooltip />}
</>
);
},
@@ -98,13 +98,19 @@ const HostSoftwareTable = ({
[determineQueryParamChange, pagePath, generateNewQueryParams, router]
);
const count = data?.count || data?.software.length || 0;
const isSoftwareNotDetected = count === 0 && searchQuery === "";
const memoizedSoftwareCount = useCallback(() => {
const count = data?.count || data?.software.length || 0;
if (isSoftwareNotDetected) {
return null;
}
return <TableCount name="items" count={count} />;
}, [data?.count, data?.software.length]);
const memoizedEmptyComponent = useCallback(() => {
return <EmptySoftwareTable isSearching={searchQuery !== ""} />;
return <EmptySoftwareTable isNotDetectingSoftware={searchQuery === ""} />;
}, [searchQuery]);
return (
@@ -125,7 +131,7 @@ const HostSoftwareTable = ({
emptyComponent={memoizedEmptyComponent}
showMarkAllPages={false}
isAllPagesSelected={false}
searchable
searchable={!isSoftwareNotDetected}
manualSortBy
/>
</div>