fleet/frontend/pages/SoftwarePage/SoftwareOS/SoftwareOSTable/SoftwareOSTable.tsx
Gabriel Hernandez 4c99ebebaf
UI updates to software page to support added software feature. (#18731)
relates to #18328

make updates to the software titles page to support new add software
feature. this includes.

**Change of page description**


![image](https://github.com/fleetdm/fleet/assets/1153709/e90a2149-54c4-41f0-a1ec-12ebc4619d6c)

**new install status column and change order of `Type` and `verison`
columns**


![image](https://github.com/fleetdm/fleet/assets/1153709/662841fd-2f9e-489c-adc3-fbf1442228b2)

**adding new dropdown filter option and conditionally showing it for
titles and versions tables**


![image](https://github.com/fleetdm/fleet/assets/1153709/8e81680e-d733-4d63-94b6-b4441cb708e3)

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://fleetdm.com/docs/contributing/committing-changes#changes-files)
for more information.
- [x] Manual QA for all new/changed functionality
2024-05-06 13:49:49 +01:00

205 lines
5.6 KiB
TypeScript

/** software/os OS tab > Table */
import React, { useCallback, useContext, useMemo } from "react";
import { InjectedRouter } from "react-router";
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";
import TableContainer from "components/TableContainer";
import LastUpdatedText from "components/LastUpdatedText";
import { ITableQueryData } from "components/TableContainer/TableContainer";
import EmptySoftwareTable from "pages/SoftwarePage/components/EmptySoftwareTable";
import { IOSVersionsResponse } from "services/entities/operating_systems";
import generateTableConfig from "pages/DashboardPage/cards/OperatingSystems/OperatingSystemsTableConfig";
import { buildQueryStringFromParams } from "utilities/url";
import { getNextLocationPath } from "utilities/helpers";
const baseClass = "software-os-table";
interface IRowProps extends Row {
original: {
os_version_id?: string;
};
}
interface ISoftwareOSTableProps {
router: InjectedRouter;
isSoftwareEnabled: boolean;
data?: IOSVersionsResponse;
perPage: number;
orderDirection: "asc" | "desc";
orderKey: string;
currentPage: number;
teamId?: number;
isLoading: boolean;
}
const SoftwareOSTable = ({
router,
isSoftwareEnabled,
data,
perPage,
orderDirection,
orderKey,
currentPage,
teamId,
isLoading,
}: ISoftwareOSTableProps) => {
const { isSandboxMode, noSandboxHosts } = useContext(AppContext);
const determineQueryParamChange = useCallback(
(newTableQuery: ITableQueryData) => {
const changedEntry = Object.entries(newTableQuery).find(([key, val]) => {
switch (key) {
case "sortDirection":
return val !== orderDirection;
case "sortHeader":
return val !== orderKey;
case "pageIndex":
return val !== currentPage;
default:
return false;
}
});
return changedEntry?.[0] ?? "";
},
[currentPage, orderDirection, orderKey]
);
const generateNewQueryParams = useCallback(
(newTableQuery: ITableQueryData, changedParam: string) => {
return {
team_id: teamId,
order_direction: newTableQuery.sortDirection,
order_key: newTableQuery.sortHeader,
page: changedParam === "pageIndex" ? newTableQuery.pageIndex : 0,
};
},
[teamId]
);
const onQueryChange = useCallback(
(newTableQuery: ITableQueryData) => {
// we want to determine which query param has changed in order to
// reset the page index to 0 if any other param has changed.
const changedParam = determineQueryParamChange(newTableQuery);
// if nothing has changed, don't update the route. this can happen when
// this handler is called on the inital render.
if (changedParam === "") return;
const newRoute = getNextLocationPath({
pathPrefix: PATHS.SOFTWARE_OS,
routeTemplate: "",
queryParams: generateNewQueryParams(newTableQuery, changedParam),
});
router.replace(newRoute);
},
[determineQueryParamChange, generateNewQueryParams, router]
);
const softwareTableHeaders = useMemo(() => {
if (!data) return [];
return generateTableConfig(teamId, router, {
includeName: true,
includeVulnerabilities: true,
includeIcon: true,
});
}, [data, router, teamId]);
const handleRowSelect = (row: IRowProps) => {
const hostsBySoftwareParams = {
os_version_id: row.original.os_version_id,
team_id: teamId,
};
const path = `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams(
hostsBySoftwareParams
)}`;
router.push(path);
};
const getItemsCountText = () => {
const count = data?.count;
if (!data?.os_versions || !count) return "";
return count === 1 ? `${count} item` : `${count} items`;
};
const getLastUpdatedText = () => {
if (!data?.os_versions || !data?.counts_updated_at) return "";
return (
<LastUpdatedText
lastUpdatedAt={data.counts_updated_at}
whatToRetrieve="software"
/>
);
};
const renderSoftwareCount = () => {
const itemText = getItemsCountText();
const lastUpdatedText = getLastUpdatedText();
if (!itemText) return null;
return (
<div className={`${baseClass}__count`}>
<span>{itemText}</span>
{lastUpdatedText}
</div>
);
};
const renderTableFooter = () => {
return (
<div>
Seeing unexpected software or vulnerabilities?{" "}
<CustomLink
url={GITHUB_NEW_ISSUE_LINK}
text="File an issue on GitHub"
newTab
/>
</div>
);
};
return (
<div className={baseClass}>
<TableContainer
columnConfigs={softwareTableHeaders}
data={data?.os_versions ?? []}
isLoading={isLoading}
resultsTitle="items"
emptyComponent={() => (
<EmptySoftwareTable isSoftwareDisabled={!isSoftwareEnabled} />
)}
defaultSortHeader={orderKey}
defaultSortDirection={orderDirection}
defaultPageIndex={currentPage}
manualSortBy
pageSize={perPage}
showMarkAllPages={false}
isAllPagesSelected={false}
disableNextPage={!data?.meta.has_next_results}
searchable={false}
onQueryChange={onQueryChange}
stackControls
renderCount={renderSoftwareCount}
renderFooter={renderTableFooter}
disableMultiRowSelect
onSelectSingleRow={handleRowSelect}
/>
</div>
);
};
export default SoftwareOSTable;