Fleet UI: Host details page > policies improvements (#19483)

This commit is contained in:
RachelElysia 2024-06-11 14:27:43 -04:00 committed by GitHub
parent 05eb338561
commit 99f431f8d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 88 additions and 29 deletions

View file

@ -0,0 +1 @@
- Host policy table can be sortable by response and View all host link preserves the team

View file

@ -471,6 +471,7 @@ const DeviceUserPage = ({
deviceUser
togglePolicyDetailsModal={togglePolicyDetailsModal}
hostPlatform={host?.platform || ""}
router={router}
/>
</TabPanel>
)}

View file

@ -903,6 +903,8 @@ const HostDetailsPage = ({
isLoading={isLoadingHost}
togglePolicyDetailsModal={togglePolicyDetailsModal}
hostPlatform={host.platform}
router={router}
currentTeamId={currentTeam?.id}
/>
</TabPanel>
</Tabs>

View file

@ -1,7 +1,11 @@
import React from "react";
import React, { useCallback } from "react";
import { InjectedRouter } from "react-router";
import { Row } from "react-table";
import { noop } from "lodash";
import { IHostPolicy } from "interfaces/policy";
import { SUPPORT_LINK } from "utilities/constants";
import { PolicyResponse, SUPPORT_LINK } from "utilities/constants";
import { createHostsByPolicyPath } from "utilities/helpers";
import TableContainer from "components/TableContainer";
import EmptyTable from "components/EmptyTable";
import Card from "components/Card";
@ -21,6 +25,15 @@ interface IPoliciesProps {
deviceUser?: boolean;
togglePolicyDetailsModal: (policy: IHostPolicy) => void;
hostPlatform: string;
router: InjectedRouter;
currentTeamId?: number;
}
interface IHostPoliciesRowProps extends Row {
original: {
id: number;
response: "pass" | "fail";
};
}
const Policies = ({
@ -29,8 +42,13 @@ const Policies = ({
deviceUser,
togglePolicyDetailsModal,
hostPlatform,
router,
currentTeamId,
}: IPoliciesProps): JSX.Element => {
const tableHeaders = generatePolicyTableHeaders(togglePolicyDetailsModal);
const tableHeaders = generatePolicyTableHeaders(
togglePolicyDetailsModal,
currentTeamId
);
if (deviceUser) {
// Remove view all hosts link
tableHeaders.pop();
@ -38,6 +56,23 @@ const Policies = ({
const failingResponses: IHostPolicy[] =
policies.filter((policy: IHostPolicy) => policy.response === "fail") || [];
const onClickRow = useCallback(
(row: IHostPoliciesRowProps) => {
const { id: policyId, response: policyResponse } = row.original;
const viewAllHostPath = createHostsByPolicyPath(
policyId,
policyResponse === "pass"
? PolicyResponse.PASSING
: PolicyResponse.FAILING,
currentTeamId
);
router.push(viewAllHostPath);
},
[router]
);
const renderHostPolicies = () => {
if (hostPlatform === "ios" || hostPlatform === "ipados") {
return (
@ -83,14 +118,16 @@ const Policies = ({
columnConfigs={tableHeaders}
data={generatePolicyDataSet(policies)}
isLoading={isLoading}
manualSortBy
resultsTitle="policy items"
defaultSortHeader="response"
defaultSortDirection="asc"
resultsTitle="policies"
emptyComponent={() => <></>}
showMarkAllPages={false}
isAllPagesSelected={false}
disablePagination
disableCount
disableMultiRowSelect
disableMultiRowSelect={!deviceUser} // Removes hover/click state if deviceUser
isClientSidePagination
onClickRow={deviceUser ? noop : onClickRow}
/>
</>
);

View file

@ -1,8 +1,11 @@
import React from "react";
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
import Button from "components/buttons/Button";
import { IHostPolicy } from "interfaces/policy";
import { PolicyResponse, DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
import StatusIndicatorWithIcon from "components/StatusIndicatorWithIcon";
import Button from "components/buttons/Button";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import { IndicatorStatus } from "components/StatusIndicatorWithIcon/StatusIndicatorWithIcon";
@ -42,7 +45,8 @@ interface IDataColumn {
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
const generatePolicyTableHeaders = (
togglePolicyDetails: (policy: IHostPolicy, teamId?: number) => void
togglePolicyDetails: (policy: IHostPolicy, teamId?: number) => void,
currentTeamId?: number
): IDataColumn[] => {
const STATUS_CELL_VALUES: Record<PolicyStatus, IStatusCellValue> = {
pass: {
@ -65,12 +69,17 @@ const generatePolicyTableHeaders = (
disableSortBy: true,
Cell: (cellProps) => {
const { name } = cellProps.row.original;
const onClickPolicyName = (e: React.MouseEvent) => {
// Allows for button to be clickable in a clickable row
e.stopPropagation();
togglePolicyDetails(cellProps.row.original);
};
return (
<Button
className="policy-info"
onClick={() => {
togglePolicyDetails(cellProps.row.original);
}}
onClick={onClickPolicyName}
variant="text-icon"
>
<span className={`policy-info-text`}>{name}</span>
@ -80,9 +89,15 @@ const generatePolicyTableHeaders = (
},
{
title: "Status",
Header: "Status",
Header: (cellProps) => (
<HeaderCell
value={cellProps.column.title}
isSortedDesc={cellProps.column.isSortedDesc}
/>
),
disableSortBy: false,
sortType: "caseInsensitive",
accessor: "response",
disableSortBy: true,
Cell: (cellProps) => {
if (cellProps.row.original.response === "") {
return <>{DEFAULT_EMPTY_CELL_VALUE}</>;
@ -114,6 +129,7 @@ const generatePolicyTableHeaders = (
cellProps.row.original.response === "pass"
? PolicyResponse.PASSING
: PolicyResponse.FAILING,
team_id: currentTeamId,
}}
className="policy-link"
/>

View file

@ -17,7 +17,7 @@ import { IPolicyStats } from "interfaces/policy";
import PATHS from "router/paths";
import sortUtils from "utilities/sort";
import { PolicyResponse } from "utilities/constants";
import { buildQueryStringFromParams } from "utilities/url";
import { createHostsByPolicyPath } from "utilities/helpers";
import InheritedBadge from "components/InheritedBadge";
import { getConditionalSelectHeaderCheckboxProps } from "components/TableContainer/utilities/config_utils";
import PassingColumnHeader from "../PassingColumnHeader";
@ -60,18 +60,6 @@ interface IDataColumn {
sortType?: string;
}
const createHostsByPolicyPath = (
policyId: number,
policyResponse: PolicyResponse,
teamId?: number | null
) => {
return `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
policy_id: policyId,
policy_response: policyResponse,
team_id: teamId,
})}`;
};
const getPolicyRefreshTime = (ms: number): string => {
const seconds = ms / 1000;
if (seconds < 60) {

View file

@ -19,7 +19,6 @@ import {
intlFormat,
intervalToDuration,
isAfter,
isBefore,
addDays,
} from "date-fns";
import yaml from "js-yaml";
@ -41,6 +40,7 @@ import {
import { ITeam } from "interfaces/team";
import { UserRole } from "interfaces/user";
import PATHS from "router/paths";
import stringUtils from "utilities/strings";
import sortUtils from "utilities/sort";
import { checkTable } from "utilities/sql_tools";
@ -54,6 +54,7 @@ import {
INITIAL_FLEET_DATE,
PLATFORM_LABEL_DISPLAY_TYPES,
isPlatformLabelNameFromAPI,
PolicyResponse,
} from "utilities/constants";
import { ISchedulableQueryStats } from "interfaces/schedulable_query";
import { IDropdownOption } from "interfaces/dropdownOption";
@ -91,6 +92,18 @@ export const addGravatarUrlToResource = (resource: any): any => {
};
};
export const createHostsByPolicyPath = (
policyId: number,
policyResponse: PolicyResponse,
teamId?: number | null
) => {
return `${PATHS.MANAGE_HOSTS}?${buildQueryStringFromParams({
policy_id: policyId,
policy_response: policyResponse,
team_id: teamId,
})}`;
};
const labelSlug = (label: ILabel): string => {
const { id, name } = label;
@ -965,6 +978,7 @@ export function getCustomDropdownOptions(
export default {
addGravatarUrlToResource,
createHostsByPolicyPath,
formatConfigDataForServer,
formatLabelResponse,
formatFloatAsPercentage,