From d75cf11cec6d5fbdee7f71394a893bd8162de097 Mon Sep 17 00:00:00 2001
From: Luke Heath
Date: Mon, 11 Apr 2022 15:21:34 -0500
Subject: [PATCH] Indicate that a policy's data is not yet accurate (#5031)
---
changes/issue-3300-policies-not-yet-accurate | 1 +
.../integration/all/app/policiesflow.spec.ts | 2 +
.../DataTable/TextCell/TextCell.tsx | 4 +-
frontend/interfaces/policy.ts | 2 +
.../ManagePoliciesPage/ManagePoliciesPage.tsx | 33 ----
.../PoliciesListWrapper.tsx | 11 +-
.../PoliciesTableConfig.tsx | 160 ++++++++++++++----
.../PoliciesListWrapper/_styles.scss | 4 +
8 files changed, 149 insertions(+), 68 deletions(-)
create mode 100644 changes/issue-3300-policies-not-yet-accurate
diff --git a/changes/issue-3300-policies-not-yet-accurate b/changes/issue-3300-policies-not-yet-accurate
new file mode 100644
index 0000000000..7678d32552
--- /dev/null
+++ b/changes/issue-3300-policies-not-yet-accurate
@@ -0,0 +1 @@
+* Indicate if a policy has not completed an initial run
\ No newline at end of file
diff --git a/cypress/integration/all/app/policiesflow.spec.ts b/cypress/integration/all/app/policiesflow.spec.ts
index 92e64a052b..6d4fa456fe 100644
--- a/cypress/integration/all/app/policiesflow.spec.ts
+++ b/cypress/integration/all/app/policiesflow.spec.ts
@@ -332,6 +332,8 @@ describe("Policies flow (seeded)", () => {
cy.visit("/policies/manage");
});
it("links to manage host page filtered by policy", () => {
+ // Move internal clock forward 2 hours so that policies report host results
+ cy.clock(Date.now() + 1000 * 60 * 120);
cy.getAttached(".failing_host_count__cell")
.first()
.within(() => {
diff --git a/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx b/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx
index c96dff4d23..073b39a2ed 100644
--- a/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx
+++ b/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx
@@ -11,7 +11,7 @@ const TextCell = ({
value,
formatter = (val) => val, // identity function if no formatter is provided
greyed,
- classes: className = "w250",
+ classes = "w250",
}: ITextCellProps): JSX.Element => {
let val = value;
@@ -20,7 +20,7 @@ const TextCell = ({
}
return (
-
+
{formatter(val)}
);
diff --git a/frontend/interfaces/policy.ts b/frontend/interfaces/policy.ts
index a43569d180..583c83ff74 100644
--- a/frontend/interfaces/policy.ts
+++ b/frontend/interfaces/policy.ts
@@ -37,6 +37,8 @@ export interface IPolicyStats extends IPolicy {
passing_host_count: number;
failing_host_count: number;
webhook: string;
+ has_run: boolean;
+ osquery_policy_ms: number;
}
// Used on the host details page and other places where the status of individual hosts are displayed
diff --git a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
index fd70f8e48e..52094f3a84 100644
--- a/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
@@ -7,7 +7,6 @@ import { AppContext } from "context/app";
import { PolicyContext } from "context/policy";
import { TableContext } from "context/table";
import { NotificationContext } from "context/notification";
-import { inMilliseconds, secondsToHms } from "fleet/helpers";
import { IPolicyStats, ILoadAllPoliciesResponse } from "interfaces/policy";
import { IWebhookFailingPolicies } from "interfaces/webhook";
import { IConfig } from "interfaces/config";
@@ -20,7 +19,6 @@ import usersAPI, { IGetMeResponse } from "services/entities/users";
import Button from "components/buttons/Button";
import RevealButton from "components/buttons/RevealButton";
-import InfoBanner from "components/InfoBanner/InfoBanner";
import Spinner from "components/Spinner";
import TeamsDropdown from "components/TeamsDropdown";
import TableDataError from "components/TableDataError";
@@ -43,16 +41,12 @@ interface IManagePoliciesPageProps {
const baseClass = "manage-policies-page";
-const DOCS_LINK =
- "https://fleetdm.com/docs/deploying/configuration#osquery-policy-update-interval";
-
const ManagePolicyPage = ({
router,
location,
}: IManagePoliciesPageProps): JSX.Element => {
const {
availableTeams,
- config,
isGlobalAdmin,
isGlobalMaintainer,
isOnGlobalTeam,
@@ -75,7 +69,6 @@ const ManagePolicyPage = ({
const {
setLastEditedQueryName,
setLastEditedQueryDescription,
- setLastEditedQueryBody,
setLastEditedQueryResolution,
setLastEditedQueryPlatform,
} = useContext(PolicyContext);
@@ -282,17 +275,8 @@ const ManagePolicyPage = ({
}`;
};
- const policyUpdateInterval =
- secondsToHms(
- inMilliseconds(config?.update_interval.osquery_policy || 0) / 1000
- ) || "osquery policy update interval";
-
const showTeamDescription = isPremiumTier && !!teamId;
- const showInfoBanner =
- (teamId && !teamPoliciesError && !!teamPolicies?.length) ||
- (!teamId && !globalPoliciesError && !!globalPolicies?.length);
-
const showInheritedPoliciesButton =
!!teamId &&
!isLoadingTeamPolicies &&
@@ -395,23 +379,6 @@ const ManagePolicyPage = ({
)}
- {!!policyUpdateInterval && showInfoBanner && (
-
-
- Your policies are checked every{" "}
- {policyUpdateInterval.trim()}.{" "}
- {isGlobalAdmin && (
-
- Check out the Fleet documentation on{" "}
-
- how to edit this frequency
-
- .
-
- )}
-
-
- )}
{!!teamId && teamPoliciesError &&
}
{!!teamId &&
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesListWrapper.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesListWrapper.tsx
index b70fd352e5..5201d23dc9 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesListWrapper.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/PoliciesListWrapper.tsx
@@ -1,4 +1,5 @@
-import React from "react";
+import React, { useContext } from "react";
+import { AppContext } from "context/app";
import { noop } from "lodash";
import paths from "router/paths";
@@ -42,6 +43,8 @@ const PoliciesListWrapper = ({
}: IPoliciesListWrapperProps): JSX.Element => {
const { MANAGE_HOSTS } = paths;
+ const { config } = useContext(AppContext);
+
const NoPolicies = () => {
return (
{
+ const seconds = ms / 1000;
+ if (seconds < 60) {
+ return `${seconds} seconds`;
+ }
+ if (seconds < 3600) {
+ const minutes = millisecondsToMinutes(ms);
+ return `${minutes} minute${minutes > 1 ? "s" : ""}`;
+ }
+ const hours = millisecondsToHours(ms);
+ return `${hours} hour${hours > 1 ? "s" : ""}`;
+};
+
+const getTooltip = (osqueryPolicyMs: number): JSX.Element => {
+ return (
+
+ Fleet is collecting policy results. Try again
+
+ in about {getPolicyRefreshTime(osqueryPolicyMs)} as the system catches up.
+
+ );
+};
+
// NOTE: cellProps come from react-table
// more info here https://react-table.tanstack.com/docs/api/useTable#cell-properties
const generateTableHeaders = (options: {
@@ -102,6 +126,7 @@ const generateTableHeaders = (options: {
),
},
@@ -115,19 +140,46 @@ const generateTableHeaders = (options: {
),
disableSortBy: true,
accessor: "passing_host_count",
- Cell: (cellProps: ICellProps): JSX.Element => (
-
- ),
+ Cell: (cellProps: ICellProps): JSX.Element => {
+ if (cellProps.row.original.has_run) {
+ return (
+
+ );
+ }
+ return (
+ <>
+
+ ---
+
+
+ {getTooltip(cellProps.row.original.osquery_policy_ms)}
+
+ >
+ );
+ },
},
{
title: "No",
@@ -139,19 +191,46 @@ const generateTableHeaders = (options: {
),
disableSortBy: true,
accessor: "failing_host_count",
- Cell: (cellProps: ICellProps): JSX.Element => (
-
- ),
+ Cell: (cellProps: ICellProps): JSX.Element => {
+ if (cellProps.row.original.has_run) {
+ return (
+
+ );
+ }
+ return (
+ <>
+
+ ---
+
+
+ {getTooltip(cellProps.row.original.osquery_policy_ms)}
+
+ >
+ );
+ },
},
];
@@ -200,17 +279,36 @@ const generateTableHeaders = (options: {
const generateDataSet = (
policiesList: IPolicyStats[] = [],
- currentAutomatedPolicies?: number[]
+ currentAutomatedPolicies?: number[],
+ osquery_policy?: number
): IPolicyStats[] => {
policiesList = policiesList.sort((a, b) =>
sortUtils.caseInsensitiveAsc(a.name, b.name)
);
+ let policiesLastRun: Date;
+ let osqueryPolicyMs: number;
- policiesList.forEach((policy) => {
- policy.webhook =
- currentAutomatedPolicies && currentAutomatedPolicies.includes(policy.id)
+ if (osquery_policy) {
+ osqueryPolicyMs = osquery_policy / 1000000;
+ // Convert from nanosecond to milliseconds
+ policiesLastRun = new Date(Date.now() - osqueryPolicyMs);
+ }
+
+ policiesList.forEach((policyItem) => {
+ policyItem.webhook =
+ currentAutomatedPolicies &&
+ currentAutomatedPolicies.includes(policyItem.id)
? "On"
: "Off";
+
+ // Define policy has_run based on updated_at compared againist last time policies ran as
+ // defined by osquery_policy.
+ policyItem.has_run = isAfter(
+ policiesLastRun,
+ new Date(policyItem.updated_at)
+ );
+ // Include osquery policy in item for reference in tooltip
+ policyItem.osquery_policy_ms = osqueryPolicyMs;
});
return policiesList;
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/_styles.scss
index af39273a27..ae50d0c608 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/_styles.scss
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesListWrapper/_styles.scss
@@ -33,6 +33,10 @@
}
}
+.has-not-run {
+ width: 20px;
+}
+
.no-policies {
display: flex;
flex-direction: column;