Fleet UI: Update software empty states (#19744)
This commit is contained in:
@@ -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}
|
||||
|
||||
+2
-4
@@ -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();
|
||||
});
|
||||
|
||||
+6
-5
@@ -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}
|
||||
|
||||
-6
@@ -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
|
||||
|
||||
+3
-10
@@ -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}>
|
||||
|
||||
-6
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user