From de94299b65fa3edff725c06d5c101622050ee297 Mon Sep 17 00:00:00 2001 From: Jacob Shandling <61553566+jacobshandling@users.noreply.github.com> Date: Tue, 16 Apr 2024 09:00:23 -0700 Subject: [PATCH] =?UTF-8?q?UI=20=E2=80=93=20Show=20percentages=20of=20pass?= =?UTF-8?q?ing=20and=20failing=20hosts=20when=20a=20live=20policy=20run=20?= =?UTF-8?q?completes=20(#18257)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Addresses #16500 ![Screenshot 2024-04-12 at 4 11 22 PM](https://github.com/fleetdm/fleet/assets/61553566/8f1cf17c-7378-4246-8f17-6f8fe3321b54) - [x] Changes file added for user-visible changes in `changes/` - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- changes/16500-policy-pass-fail-percentage | 1 + frontend/interfaces/host.ts | 2 +- .../PolicyErrorsTable.tsx} | 29 ++++----- .../PolicyErrorsTableConfig.tsx} | 0 .../_styles.scss | 2 +- .../components/PolicyErrorsTable/index.ts | 1 + .../PolicyQueriesErrorsTable/index.ts | 1 - .../components/PolicyQueriesTable/index.ts | 1 - .../PolicyResults.tsx} | 65 ++++++++++++------- .../_styles.scss | 6 +- .../components/PolicyResults/helpers.tsx | 19 ++++++ .../components/PolicyResults/index.ts | 1 + .../PolicyResultsTable.tsx} | 37 +++++------ .../PolicyResultsTableConfig.tsx} | 6 +- .../_styles.scss | 2 +- .../components/PolicyResultsTable/index.ts | 1 + .../components/QueryResults/index.ts | 1 - .../policies/PolicyPage/screens/RunQuery.tsx | 4 +- 18 files changed, 108 insertions(+), 71 deletions(-) create mode 100644 changes/16500-policy-pass-fail-percentage rename frontend/pages/policies/PolicyPage/components/{PolicyQueriesErrorsTable/PolicyQueriesErrorsTable.tsx => PolicyErrorsTable/PolicyErrorsTable.tsx} (69%) rename frontend/pages/policies/PolicyPage/components/{PolicyQueriesErrorsTable/PolicyQueriesErrorsTableConfig.tsx => PolicyErrorsTable/PolicyErrorsTableConfig.tsx} (100%) rename frontend/pages/policies/PolicyPage/components/{PolicyQueriesErrorsTable => PolicyErrorsTable}/_styles.scss (98%) create mode 100644 frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/index.ts delete mode 100644 frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/index.ts delete mode 100644 frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/index.ts rename frontend/pages/policies/PolicyPage/components/{QueryResults/QueryResults.tsx => PolicyResults/PolicyResults.tsx} (76%) rename frontend/pages/policies/PolicyPage/components/{QueryResults => PolicyResults}/_styles.scss (96%) create mode 100644 frontend/pages/policies/PolicyPage/components/PolicyResults/helpers.tsx create mode 100644 frontend/pages/policies/PolicyPage/components/PolicyResults/index.ts rename frontend/pages/policies/PolicyPage/components/{PolicyQueriesTable/PolicyQueriesTable.tsx => PolicyResultsTable/PolicyResultsTable.tsx} (59%) rename frontend/pages/policies/PolicyPage/components/{PolicyQueriesTable/PolicyQueriesTableConfig.tsx => PolicyResultsTable/PolicyResultsTableConfig.tsx} (94%) rename frontend/pages/policies/PolicyPage/components/{PolicyQueriesTable => PolicyResultsTable}/_styles.scss (98%) create mode 100644 frontend/pages/policies/PolicyPage/components/PolicyResultsTable/index.ts delete mode 100644 frontend/pages/policies/PolicyPage/components/QueryResults/index.ts diff --git a/changes/16500-policy-pass-fail-percentage b/changes/16500-policy-pass-fail-percentage new file mode 100644 index 0000000000..bc93d8227f --- /dev/null +++ b/changes/16500-policy-pass-fail-percentage @@ -0,0 +1 @@ +* When a live policy run finishes, display the percentages of passing and failing hosts to the user. diff --git a/frontend/interfaces/host.ts b/frontend/interfaces/host.ts index f0d1ae2b3d..29afed9537 100644 --- a/frontend/interfaces/host.ts +++ b/frontend/interfaces/host.ts @@ -206,7 +206,7 @@ export interface IPackStats { type: string; } -export interface IHostPolicyQuery { +export interface IPolicyHostResponse { id: number; display_name: string; query_results?: unknown[]; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/PolicyQueriesErrorsTable.tsx b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTable.tsx similarity index 69% rename from frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/PolicyQueriesErrorsTable.tsx rename to frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTable.tsx index 3b1fb9e1be..7948d73c66 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/PolicyQueriesErrorsTable.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTable.tsx @@ -6,32 +6,25 @@ import { ICampaignError } from "interfaces/campaign"; import { generateTableHeaders, generateDataSet, -} from "./PolicyQueriesErrorsTableConfig"; +} from "./PolicyErrorsTableConfig"; -const baseClass = "policies-queries-table"; -const noPolicyQueries = "no-policy-queries"; +// TODO - this class is duplicated and styles are overlapping with PolicyResultsTable. Differentiate +// them clearly and encapsulate common styles. +const baseClass = "policy-results-table"; -interface IPoliciesTableProps { +interface IPolicyErrorsTableProps { errorsList: ICampaignError[]; isLoading: boolean; resultsTitle?: string; canAddOrDeletePolicy?: boolean; } -const PoliciesTable = ({ +const PolicyErrorsTable = ({ errorsList, isLoading, resultsTitle, canAddOrDeletePolicy, -}: IPoliciesTableProps): JSX.Element => { - const NoPolicyQueries = () => { - return ( -
-

No hosts are online.

-
- ); - }; - +}: IPolicyErrorsTableProps): JSX.Element => { return (
( +
+

No hosts are online.

+
+ )} onQueryChange={noop} disableCount /> @@ -63,4 +60,4 @@ const PoliciesTable = ({ ); }; -export default PoliciesTable; +export default PolicyErrorsTable; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/PolicyQueriesErrorsTableConfig.tsx b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTableConfig.tsx similarity index 100% rename from frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/PolicyQueriesErrorsTableConfig.tsx rename to frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/PolicyErrorsTableConfig.tsx diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/_styles.scss b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/_styles.scss similarity index 98% rename from frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/_styles.scss rename to frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/_styles.scss index 24618c22f9..44aaf3d222 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/_styles.scss +++ b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/_styles.scss @@ -1,4 +1,4 @@ -.policies-queries-table { +.policy-results-table { border-collapse: collapse; &__wrapper { diff --git a/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/index.ts b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/index.ts new file mode 100644 index 0000000000..1167b6f858 --- /dev/null +++ b/frontend/pages/policies/PolicyPage/components/PolicyErrorsTable/index.ts @@ -0,0 +1 @@ +export { default } from "./PolicyErrorsTable"; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/index.ts b/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/index.ts deleted file mode 100644 index 9b51be9010..0000000000 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesErrorsTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./PolicyQueriesErrorsTable"; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/index.ts b/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/index.ts deleted file mode 100644 index 87b478a29f..0000000000 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./PolicyQueriesTable"; diff --git a/frontend/pages/policies/PolicyPage/components/QueryResults/QueryResults.tsx b/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx similarity index 76% rename from frontend/pages/policies/PolicyPage/components/QueryResults/QueryResults.tsx rename to frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx index d31de5d0d8..b78ffbb171 100644 --- a/frontend/pages/policies/PolicyPage/components/QueryResults/QueryResults.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyResults/PolicyResults.tsx @@ -18,14 +18,16 @@ import Icon from "components/Icon/Icon"; import TabsWrapper from "components/TabsWrapper"; import InfoBanner from "components/InfoBanner"; import ShowQueryModal from "components/modals/ShowQueryModal"; +import TooltipWrapper from "components/TooltipWrapper"; -import QueryResultsHeading from "components/queries/queryResults/QueryResultsHeading"; +import ResultsHeading from "components/queries/queryResults/QueryResultsHeading"; import AwaitingResults from "components/queries/queryResults/AwaitingResults"; -import PolicyQueryTable from "../PolicyQueriesTable/PolicyQueriesTable"; -import PolicyQueriesErrorsTable from "../PolicyQueriesErrorsTable/PolicyQueriesErrorsTable"; +import PolicyResultsTable from "../PolicyResultsTable/PolicyResultsTable"; +import PolicyQueriesErrorsTable from "../PolicyErrorsTable/PolicyErrorsTable"; +import { getYesNoCounts } from "./helpers"; -interface IQueryResultsProps { +interface IPolicyResultsProps { campaign: ICampaign; isQueryFinished: boolean; policyName?: string; @@ -43,7 +45,7 @@ const NAV_TITLES = { ERRORS: "Errors", }; -const QueryResults = ({ +const PolicyResults = ({ campaign, isQueryFinished, policyName, @@ -52,10 +54,10 @@ const QueryResults = ({ setSelectedTargets, goToQueryEditor, targetsTotalCount, -}: IQueryResultsProps): JSX.Element => { +}: IPolicyResultsProps): JSX.Element => { const { lastEditedQueryBody } = useContext(PolicyContext); - const { hosts: hostsOnline, hosts_count: hostsCount, errors } = + const { hosts: hostResponses, hosts_count: hostsCount, errors } = campaign || {}; const totalRowsCount = get(campaign, ["hosts_count", "successful"], 0); @@ -63,11 +65,11 @@ const QueryResults = ({ const [navTabIndex, setNavTabIndex] = useState(0); const [showQueryModal, setShowQueryModal] = useState(false); - const onExportQueryResults = (evt: React.MouseEvent) => { + const onExportResults = (evt: React.MouseEvent) => { evt.preventDefault(); - if (hostsOnline) { - const hostsExport = hostsOnline.map((host) => { + if (hostResponses) { + const hostsExport = hostResponses.map((host) => { return { host: host.display_name, status: @@ -121,9 +123,7 @@ const QueryResults = ({
@@ -211,7 +232,7 @@ const QueryResults = ({ return (
- - {renderTable()} + {renderResultsTable()} {renderErrorsTable()} @@ -246,4 +267,4 @@ const QueryResults = ({ ); }; -export default QueryResults; +export default PolicyResults; diff --git a/frontend/pages/policies/PolicyPage/components/QueryResults/_styles.scss b/frontend/pages/policies/PolicyPage/components/PolicyResults/_styles.scss similarity index 96% rename from frontend/pages/policies/PolicyPage/components/QueryResults/_styles.scss rename to frontend/pages/policies/PolicyPage/components/PolicyResults/_styles.scss index 446a4c8c16..045ce4b631 100644 --- a/frontend/pages/policies/PolicyPage/components/QueryResults/_styles.scss +++ b/frontend/pages/policies/PolicyPage/components/PolicyResults/_styles.scss @@ -1,5 +1,4 @@ .query-results { - .info-banner { margin: 2rem auto 1.25rem; } @@ -46,9 +45,12 @@ margin-top: $pad-xlarge; } + &__results-meta > * { + font-size: $x-small; + } + &__results-count, &__error-count { - font-size: $x-small; font-weight: $bold; } } diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/helpers.tsx b/frontend/pages/policies/PolicyPage/components/PolicyResults/helpers.tsx new file mode 100644 index 0000000000..eff6cc0593 --- /dev/null +++ b/frontend/pages/policies/PolicyPage/components/PolicyResults/helpers.tsx @@ -0,0 +1,19 @@ +import { IPolicyHostResponse } from "interfaces/host"; + +export const getYesNoCounts = (hostResponses: IPolicyHostResponse[]) => { + const yesNoCounts = hostResponses.reduce( + (acc, hostResponse) => { + if (hostResponse.query_results?.length) { + acc.yes += 1; + } else { + acc.no += 1; + } + return acc; + }, + { yes: 0, no: 0 } + ); + + return yesNoCounts; +}; + +export default { getYesNoCounts }; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResults/index.ts b/frontend/pages/policies/PolicyPage/components/PolicyResults/index.ts new file mode 100644 index 0000000000..b2801dc41d --- /dev/null +++ b/frontend/pages/policies/PolicyPage/components/PolicyResults/index.ts @@ -0,0 +1 @@ +export { default } from "./PolicyResults"; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/PolicyQueriesTable.tsx b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTable.tsx similarity index 59% rename from frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/PolicyQueriesTable.tsx rename to frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTable.tsx index 25c1b516bc..da095f36aa 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/PolicyQueriesTable.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTable.tsx @@ -1,37 +1,30 @@ import React from "react"; import { noop } from "lodash"; -import { IHostPolicyQuery } from "interfaces/host"; +import { IPolicyHostResponse } from "interfaces/host"; import TableContainer from "components/TableContainer"; import { generateTableHeaders, generateDataSet, -} from "./PolicyQueriesTableConfig"; +} from "./PolicyResultsTableConfig"; -const baseClass = "policies-queries-table"; -const noPolicyQueries = "no-policy-queries"; +// TODO - this class is duplicated and styles are overlapping with PolicyErrorsTable. Differentiate +// them clearly and encapsulate common styles. +const baseClass = "policy-results-table"; -interface IPoliciesTableProps { - policyHostsList: IHostPolicyQuery[]; +interface IPolicyResultsTableProps { + hostResponses: IPolicyHostResponse[]; isLoading: boolean; resultsTitle?: string; canAddOrDeletePolicy?: boolean; } -const PoliciesTable = ({ - policyHostsList, +const PolicyResultsTable = ({ + hostResponses, isLoading, resultsTitle, canAddOrDeletePolicy, -}: IPoliciesTableProps): JSX.Element => { - const NoPolicyQueries = () => { - return ( -
-

No hosts are online.

-
- ); - }; - +}: IPolicyResultsTableProps): JSX.Element => { return (
( +
+

No hosts are online.

+
+ )} onQueryChange={noop} disableCount /> @@ -62,4 +59,4 @@ const PoliciesTable = ({ ); }; -export default PoliciesTable; +export default PolicyResultsTable; diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/PolicyQueriesTableConfig.tsx b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTableConfig.tsx similarity index 94% rename from frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/PolicyQueriesTableConfig.tsx rename to frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTableConfig.tsx index 2ae2ad5bde..d4b7529c36 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/PolicyQueriesTableConfig.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/PolicyResultsTableConfig.tsx @@ -10,7 +10,7 @@ import Icon from "components/Icon/Icon"; import TextCell from "components/TableContainer/DataTable/TextCell/TextCell"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; -import { IHostPolicyQuery } from "interfaces/host"; +import { IPolicyHostResponse } from "interfaces/host"; import sortUtils from "utilities/sort"; interface IHeaderProps { @@ -22,7 +22,7 @@ interface ICellProps { value: string; }; row: { - original: IHostPolicyQuery; + original: IPolicyHostResponse; }; } @@ -87,7 +87,7 @@ const generateTableHeaders = (): IDataColumn[] => { }; const generateDataSet = memoize( - (policyHostsList: IHostPolicyQuery[] = []): IHostPolicyQuery[] => { + (policyHostsList: IPolicyHostResponse[] = []): IPolicyHostResponse[] => { policyHostsList = policyHostsList.sort((a, b) => sortUtils.caseInsensitiveAsc(a.display_name, b.display_name) ); diff --git a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/_styles.scss b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/_styles.scss similarity index 98% rename from frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/_styles.scss rename to frontend/pages/policies/PolicyPage/components/PolicyResultsTable/_styles.scss index 65fa20a8a5..3e069e8365 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyQueriesTable/_styles.scss +++ b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/_styles.scss @@ -1,4 +1,4 @@ -.policies-queries-table { +.policy-results-table { border-collapse: collapse; &__wrapper { diff --git a/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/index.ts b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/index.ts new file mode 100644 index 0000000000..0c36de0471 --- /dev/null +++ b/frontend/pages/policies/PolicyPage/components/PolicyResultsTable/index.ts @@ -0,0 +1 @@ +export { default } from "./PolicyResultsTable"; diff --git a/frontend/pages/policies/PolicyPage/components/QueryResults/index.ts b/frontend/pages/policies/PolicyPage/components/QueryResults/index.ts deleted file mode 100644 index 4ff72f5be8..0000000000 --- a/frontend/pages/policies/PolicyPage/components/QueryResults/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./QueryResults"; diff --git a/frontend/pages/policies/PolicyPage/screens/RunQuery.tsx b/frontend/pages/policies/PolicyPage/screens/RunQuery.tsx index 2c085532e7..a2b08feef7 100644 --- a/frontend/pages/policies/PolicyPage/screens/RunQuery.tsx +++ b/frontend/pages/policies/PolicyPage/screens/RunQuery.tsx @@ -15,7 +15,7 @@ import { ICampaign, ICampaignState } from "interfaces/campaign"; import { IPolicy } from "interfaces/policy"; import { ITarget } from "interfaces/target"; -import QueryResults from "../components/QueryResults"; +import PolicyResults from "../components/PolicyResults"; interface IRunQueryProps { storedPolicy: IPolicy | undefined; @@ -198,7 +198,7 @@ const RunQuery = ({ const { campaign } = campaignState; return ( -