diff --git a/frontend/components/InheritedBadge/InheritedBadge.tsx b/frontend/components/InheritedBadge/InheritedBadge.tsx
index 7394063f45..0a098317c1 100644
--- a/frontend/components/InheritedBadge/InheritedBadge.tsx
+++ b/frontend/components/InheritedBadge/InheritedBadge.tsx
@@ -15,7 +15,7 @@ const InheritedBadge = ({
}: IInheritedBadgeProps) => {
const tooltipId = uniqueId();
return (
-
+
(!isFetchingGlobalCount &&
renderPoliciesCount(globalPoliciesCount)) ||
diff --git a/frontend/pages/policies/ManagePoliciesPage/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/_styles.scss
index caae6f8f4d..ca0978671d 100644
--- a/frontend/pages/policies/ManagePoliciesPage/_styles.scss
+++ b/frontend/pages/policies/ManagePoliciesPage/_styles.scss
@@ -193,30 +193,31 @@
}
}
- .critical-badge,
- .inherited-badge {
- display: inline-flex;
- }
-
- .inherited-badge {
- display: flex;
- padding: 4px;
- justify-content: center;
- align-items: center;
- gap: 4px;
- font-weight: $bold;
- font-size: $xxx-small;
- color: $core-fleet-black;
- border-radius: 4px;
- background: $ui-vibrant-blue-10;
- }
-
.policy-name-text {
text-overflow: ellipsis;
overflow: hidden;
}
}
}
+
+ .critical-badge,
+ .policy-has-not-run {
+ .critical-badge-icon {
+ display: inline-flex;
+ }
+
+ @include tooltip5-arrow-styles;
+
+ .react-tooltip {
+ @include tooltip-text;
+ font-style: normal;
+ text-align: center;
+ }
+ }
+
+ .inherited-badge {
+ overflow: initial;
+ }
}
}
}
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx
index 14ff10e161..f100e4761b 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx
@@ -1,23 +1,31 @@
import React from "react";
-import { render, screen } from "@testing-library/react";
+import { screen, waitFor } from "@testing-library/react";
import { noop } from "lodash";
+import { createCustomRenderer } from "test/test-utils";
+import createMockUser from "__mocks__/userMock";
import createMockPolicy from "__mocks__/policyMock";
import PoliciesTable from "./PoliciesTable";
describe("Policies table", () => {
- const testCriticalPolicy = createMockPolicy({ critical: true });
+ it("Renders the page-wide empty state when no policies are present", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
- it("Renders a tooltip including 'Premium feature' copy for a critical policy in Sandbox mode", () => {
render(
{}}
currentTeam={{ id: -1, name: "All teams" }}
isPremiumTier
- isSandboxMode
searchQuery=""
page={0}
onQueryChange={noop}
@@ -25,18 +33,52 @@ describe("Policies table", () => {
/>
);
- expect(
- screen.getByText("This policy has been marked as critical.", {
- exact: false,
- })
- ).toBeInTheDocument();
- expect(
- screen.getByText("This is a premium feature.", { exact: false })
- ).toBeInTheDocument();
+ expect(screen.getByText("You don't have any policies")).toBeInTheDocument();
+ expect(screen.queryByText("Name")).toBeNull();
});
- it("Renders a tooltip excluding 'Premium feature' copy for a critical policy not in Sandbox mode", () => {
+ it("Renders the empty search state when search query exists for server side search with no results", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
render(
+ {}}
+ currentTeam={{ id: -1, name: "All teams" }}
+ isPremiumTier
+ searchQuery="shouldn't match anything"
+ page={0}
+ onQueryChange={noop}
+ renderPoliciesCount={() => null}
+ />
+ );
+
+ expect(screen.getByText("No matching policies")).toBeInTheDocument();
+ expect(screen.queryByText("Name")).toBeNull();
+ });
+
+ it("Renders a critical badge and tooltip for a critical policy", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testCriticalPolicy = createMockPolicy({ critical: true });
+
+ const { user } = render(
{
onDeletePolicyClick={() => {}}
currentTeam={{ id: -1, name: "All teams" }}
isPremiumTier
- isSandboxMode={false}
searchQuery=""
page={0}
onQueryChange={noop}
@@ -52,13 +93,141 @@ describe("Policies table", () => {
/>
);
- expect(
- screen.getByText("This policy has been marked as critical.", {
- exact: false,
- })
- ).toBeInTheDocument();
- expect(
- screen.queryByText("This is a premium feature.", { exact: false })
- ).toBeNull();
+ await waitFor(() => {
+ waitFor(() => {
+ user.hover(screen.getByTestId("policy-icon"));
+ });
+
+ expect(
+ screen.getByText("This policy has been marked as critical.")
+ ).toBeInTheDocument();
+ });
+ });
+
+ it("Renders an inherited badge and tooltip for inherited policy on a team's policies page", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testInheritedPolicy = createMockPolicy({ team_id: null });
+
+ const { user } = render(
+ {}}
+ currentTeam={{ id: 2, name: "Team 2" }}
+ isPremiumTier
+ searchQuery=""
+ page={0}
+ onQueryChange={noop}
+ renderPoliciesCount={() => null}
+ />
+ );
+
+ await waitFor(() => {
+ waitFor(() => {
+ user.hover(screen.getByText("Inherited"));
+ });
+
+ expect(
+ screen.getByText("This policy runs on all hosts.")
+ ).toBeInTheDocument();
+ });
+ });
+
+ it("Does not render an inherited badge and tooltip for global policy on the All teams's policies page", () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testGlobalPolicy = createMockPolicy({ team_id: null });
+
+ render(
+ {}}
+ currentTeam={{ id: -1, name: "All teams" }}
+ isPremiumTier
+ searchQuery=""
+ page={0}
+ onQueryChange={noop}
+ renderPoliciesCount={() => null}
+ />
+ );
+
+ expect(screen.queryByText("Inherited")).not.toBeInTheDocument();
+ });
+
+ it("Renders the correct number of checkboxes for team policies and not inherited policies on a team's policies page and can check select all box", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testInheritedPolicies = [
+ createMockPolicy({ team_id: null, name: "Inherited policy 1" }),
+ createMockPolicy({ id: 2, team_id: null, name: "Inherited policy 2" }),
+ createMockPolicy({ id: 3, team_id: null, name: "Inherited policy 3" }),
+ ];
+
+ const testTeamPolicies = [
+ createMockPolicy({ id: 4, team_id: 2, name: "Team policy 1" }),
+ createMockPolicy({ id: 5, team_id: 2, name: "Team policy 2" }),
+ ];
+
+ const { container, user } = render(
+ {}}
+ currentTeam={{ id: 2, name: "Team 2" }}
+ isPremiumTier
+ searchQuery=""
+ page={0}
+ onQueryChange={noop}
+ renderPoliciesCount={() => null}
+ canAddOrDeletePolicy
+ hasPoliciesToDelete
+ />
+ );
+
+ const numberOfCheckboxes = container.querySelectorAll(
+ "input[type='checkbox']"
+ ).length;
+
+ expect(numberOfCheckboxes).toBe(
+ testTeamPolicies.length + 1 // +1 for Select all checkbox
+ );
+
+ const checkbox = container.querySelectorAll(
+ "input[type='checkbox']"
+ )[0] as HTMLInputElement;
+
+ await waitFor(() => {
+ waitFor(() => {
+ user.click(checkbox);
+ });
+
+ expect(checkbox.checked).toBe(true);
+ });
});
});
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx
index 36c027256a..9047242c8b 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx
@@ -26,7 +26,6 @@ interface IPoliciesTableProps {
currentTeam: ITeamSummary | undefined;
currentAutomatedPolicies?: number[];
isPremiumTier?: boolean;
- isSandboxMode?: boolean;
renderPoliciesCount: () => JSX.Element | null;
onQueryChange: (newTableQuery: ITableQueryData) => void;
searchQuery: string;
@@ -45,7 +44,6 @@ const PoliciesTable = ({
currentTeam,
currentAutomatedPolicies,
isPremiumTier,
- isSandboxMode,
onQueryChange,
renderPoliciesCount,
searchQuery,
@@ -104,8 +102,7 @@ const PoliciesTable = ({
hasPermissionAndPoliciesToDelete,
},
policiesList,
- isPremiumTier,
- isSandboxMode
+ isPremiumTier
)}
data={generateDataSet(
policiesList,
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
index dff531ab77..3d395c64c2 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
@@ -7,7 +7,7 @@ import {
millisecondsToHours,
millisecondsToMinutes,
} from "date-fns";
-import ReactTooltip from "react-tooltip";
+import { Tooltip as ReactTooltip5 } from "react-tooltip-5";
// @ts-ignore
import Checkbox from "components/forms/fields/Checkbox";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
@@ -18,7 +18,7 @@ import PATHS from "router/paths";
import sortUtils from "utilities/sort";
import { PolicyResponse } from "utilities/constants";
import { buildQueryStringFromParams } from "utilities/url";
-import { COLORS } from "styles/var/colors";
+import InheritedBadge from "components/InheritedBadge";
import { getConditionalSelectHeaderCheckboxProps } from "components/TableContainer/utilities/config_utils";
import PassingColumnHeader from "../PassingColumnHeader";
@@ -87,11 +87,11 @@ const getPolicyRefreshTime = (ms: number): string => {
const getTooltip = (osqueryPolicyMs: number): JSX.Element => {
return (
-
+ <>
Fleet is collecting policy results. Try again
in about {getPolicyRefreshTime(osqueryPolicyMs)} as the system catches up.
-
+ >
);
};
@@ -104,8 +104,7 @@ const generateTableHeaders = (
tableType?: string;
},
policiesList: IPolicyStats[] = [],
- isPremiumTier?: boolean,
- isSandboxMode?: boolean
+ isPremiumTier?: boolean
): IDataColumn[] => {
const { selectedTeamId, hasPermissionAndPoliciesToDelete } = options;
const viewingTeamPolicies = selectedTeamId !== -1;
@@ -143,11 +142,10 @@ const generateTableHeaders = (
<>
{cellProps.cell.value}
{isPremiumTier && cellProps.row.original.critical && (
- <>
+
-
This policy has been marked as critical.
- {isSandboxMode && (
- <>
-
- This is a premium feature.
- >
- )}
-
- >
+
+
)}
{viewingTeamPolicies && !cellProps.row.original.team_id && (
- <>
-
- Inherited
-
-
- This policy runs on all hosts.
-
- >
+
)}
>
}
@@ -228,24 +203,24 @@ const generateTableHeaders = (
);
}
return (
- <>
+
---
-
{getTooltip(cellProps.row.original.next_update_ms)}
-
- >
+
+
);
},
},
@@ -279,33 +254,30 @@ const generateTableHeaders = (
);
}
return (
- <>
+
---
-
{getTooltip(cellProps.row.original.next_update_ms)}
-
- >
+
+
);
},
sortType: "caseInsensitive",
},
];
- console.log(
- "hasPermissionAndPoliciesToDelete",
- hasPermissionAndPoliciesToDelete
- );
+
if (hasPermissionAndPoliciesToDelete) {
tableHeaders.unshift({
id: "selection",
diff --git a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss
index 9913e4d229..1d8354c466 100644
--- a/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss
+++ b/frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss
@@ -26,10 +26,6 @@
}
}
-.has-not-run {
- width: 20px;
-}
-
.no-team-policy {
border: 1px solid #e2e4ea;
box-sizing: border-box;
diff --git a/frontend/pages/queries/ManageQueriesPage/_styles.scss b/frontend/pages/queries/ManageQueriesPage/_styles.scss
index 8ec52b7d29..3f7cd25150 100644
--- a/frontend/pages/queries/ManageQueriesPage/_styles.scss
+++ b/frontend/pages/queries/ManageQueriesPage/_styles.scss
@@ -149,8 +149,17 @@
.inherited-badge {
overflow: initial;
}
+ .observer-can-run-badge {
+ @include tooltip5-arrow-styles;
+
+ .react-tooltip {
+ @include tooltip-text;
+ font-style: normal;
+ text-align: center;
+ }
+ }
}
- .query-icon {
+ .observer-can-run-query-icon {
display: block;
}
@@ -158,9 +167,6 @@
display: flex;
gap: $pad-xsmall;
}
- .observer-can-run-tooltip {
- font-weight: $regular;
- }
}
@media (max-width: $break-md) {
diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tests.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tests.tsx
index 3d7e560bf7..3269ebcc1e 100644
--- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tests.tsx
+++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tests.tsx
@@ -1,9 +1,10 @@
import React from "react";
-import { screen } from "@testing-library/react";
+import { screen, waitFor } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";
import createMockUser from "__mocks__/userMock";
+import createMockQuery from "__mocks__/queryMock";
import { ISchedulableQuery } from "interfaces/schedulable_query";
import QueriesTable, { IQueriesTableProps } from "./QueriesTable";
@@ -207,4 +208,160 @@ describe("QueriesTable", () => {
});
});
});
+
+ it("Renders an observer can run badge and tooltip for a observer can run query", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testObserverCanRunQuery = [
+ createMockQuery({
+ observer_can_run: true,
+ }),
+ ];
+ const testQueries = testObserverCanRunQuery.map(enhanceQuery);
+
+ const { user } = render(
+
+ );
+
+ await waitFor(() => {
+ waitFor(() => {
+ user.hover(screen.getByTestId("query-icon"));
+ });
+
+ expect(
+ screen.getByText("Observers can run this query.")
+ ).toBeInTheDocument();
+ });
+ });
+
+ it("Renders an inherited badge and tooltip for inherited query on a team's queries page", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testInheritedQuery = [createMockQuery()];
+
+ const testQueries = testInheritedQuery.map(enhanceQuery);
+
+ const { user } = render(
+
+ );
+
+ await waitFor(() => {
+ waitFor(() => {
+ user.hover(screen.getByText("Inherited"));
+ });
+
+ expect(
+ screen.getByText("This query runs on all hosts.")
+ ).toBeInTheDocument();
+ });
+ });
+
+ it("Does not render an inherited badge and tooltip for global query on the All team's queries page", () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const testGlobalQuery = [createMockQuery()];
+ const testQueries = testGlobalQuery.map(enhanceQuery);
+
+ render(
+
+ );
+
+ expect(screen.queryByText("Inherited")).not.toBeInTheDocument();
+ });
+
+ it("Renders the correct number of checkboxes for team queries and not inherited queries on a team's queries page and can check select all box", async () => {
+ const render = createCustomRenderer({
+ context: {
+ app: {
+ isGlobalAdmin: true,
+ currentUser: createMockUser(),
+ },
+ },
+ });
+
+ const { container, user } = render(
+
+ );
+
+ const numberOfCheckboxes = container.querySelectorAll(
+ "input[type='checkbox']"
+ ).length;
+
+ expect(numberOfCheckboxes).toBe(
+ testTeamQueries.length + 1 // +1 for Select all checkbox
+ );
+
+ const checkbox = container.querySelectorAll(
+ "input[type='checkbox']"
+ )[0] as HTMLInputElement;
+
+ await waitFor(() => {
+ waitFor(() => {
+ user.click(checkbox);
+ });
+
+ expect(checkbox.checked).toBe(true);
+ });
+ });
});
diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx
index 0b03dad9b3..32b712ef82 100644
--- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx
+++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx
@@ -26,7 +26,7 @@ import TextCell from "components/TableContainer/DataTable/TextCell";
import PerformanceImpactCell from "components/TableContainer/DataTable/PerformanceImpactCell";
import TooltipWrapper from "components/TooltipWrapper";
import InheritedBadge from "components/InheritedBadge";
-import { COLORS } from "styles/var/colors";
+import { Tooltip as ReactTooltip5 } from "react-tooltip-5";
import QueryAutomationsStatusIndicator from "../QueryAutomationsStatusIndicator";
interface IQueryRow {
@@ -136,25 +136,50 @@ const generateTableHeaders = ({
<>
{cellProps.cell.value}
{!isOnlyObserver && cellProps.row.original.observer_can_run && (
- <>
+
-
+
-
Observers can run this query.
-
- >
+
+
+
+ // <>
+ //
+ //
+ //
+ //
+ // Observers can run this query.
+ //
+ // >
)}
{viewingTeamScope &&
// inherited