From c0ac242e11a5b5aef8575db85cdec48ef7c5d149 Mon Sep 17 00:00:00 2001 From: jacobshandling <61553566+jacobshandling@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:24:09 -0400 Subject: [PATCH] UI - Host filters bug (#22132) ## #22088 - Hosts page filters were using the same type that had been updated with additional, more granular options, but the filters for that page still only support the aggregate options "install", "pending" or "failed". Updated hosts page to use new type `SoftwareAggregateStatus` to maintain functionality. - Note that this does _not_ fix this related but distinct _released_ bug: https://github.com/fleetdm/fleet/issues/22136 - [x] Manual QA for all new/changed functionality --------- Co-authored-by: Jacob Shandling --- frontend/interfaces/software.ts | 21 +++++++++++++++++++ .../SoftwareTitleDetailsPage/helpers.ts | 9 ++------ .../hosts/ManageHostsPage/ManageHostsPage.tsx | 16 ++++++++------ .../HostsFilterBlock/HostsFilterBlock.tsx | 6 +++--- frontend/services/entities/hosts.ts | 8 +++---- frontend/utilities/url/index.ts | 5 +++-- 6 files changed, 43 insertions(+), 22 deletions(-) diff --git a/frontend/interfaces/software.ts b/frontend/interfaces/software.ts index 2818accc7ce6..a3e633a8bb6b 100644 --- a/frontend/interfaces/software.ts +++ b/frontend/interfaces/software.ts @@ -221,6 +221,19 @@ export const isValidSoftwareInstallStatus = ( ): s is SoftwareInstallStatus => !!s && SOFTWARE_INSTALL_STATUSES.includes(s as SoftwareInstallStatus); +export const SOFTWARE_AGGREGATE_STATUSES = [ + "installed", + "pending", + "failed", +] as const; + +export type SoftwareAggregateStatus = typeof SOFTWARE_AGGREGATE_STATUSES[number]; + +export const isValidSoftwareAggregateStatus = ( + s: string | undefined | null +): s is SoftwareAggregateStatus => + !!s && SOFTWARE_AGGREGATE_STATUSES.includes(s as SoftwareAggregateStatus); + export const isSoftwareUninstallStatus = ( s: string | undefined | null ): s is SoftwareUninstallStatus => @@ -328,6 +341,14 @@ export const getInstallStatusPredicate = (status: string | undefined) => { ); }; +export const aggregateInstallStatusCounts = ( + packageStatuses: ISoftwarePackage["status"] +) => ({ + installed: packageStatuses.installed, + pending: packageStatuses.pending_install + packageStatuses.pending_uninstall, + failed: packageStatuses.failed_install + packageStatuses.failed_uninstall, +}); + export const INSTALL_STATUS_ICONS: Record< SoftwareInstallStatus | "pending" | "failed", IconNames diff --git a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts index 986497d160f8..a762152aa728 100644 --- a/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts +++ b/frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/helpers.ts @@ -1,16 +1,11 @@ import { IAppStoreApp, - ISoftwarePackage, ISoftwareTitleDetails, isSoftwarePackage, + aggregateInstallStatusCounts, } from "interfaces/software"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; -const mergePackageStatuses = (packageStatuses: ISoftwarePackage["status"]) => ({ - installed: packageStatuses.installed, - pending: packageStatuses.pending_install + packageStatuses.pending_uninstall, - failed: packageStatuses.failed_install + packageStatuses.failed_uninstall, -}); /** * Generates the data needed to render the package card. */ @@ -31,7 +26,7 @@ export const getPackageCardInfo = (softwareTitle: ISoftwareTitleDetails) => { : packageData.latest_version) || DEFAULT_EMPTY_CELL_VALUE, uploadedAt: isSoftwarePackage(packageData) ? packageData.uploaded_at : "", status: isSoftwarePackage(packageData) - ? mergePackageStatuses(packageData.status) + ? aggregateInstallStatusCounts(packageData.status) : packageData.status, isSelfService: packageData.self_service, }; diff --git a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx index 71f2386c587d..8131541166ca 100644 --- a/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx +++ b/frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx @@ -51,8 +51,8 @@ import { ILabel } from "interfaces/label"; import { IOperatingSystemVersion } from "interfaces/operating_system"; import { IPolicy, IStoredPolicyResponse } from "interfaces/policy"; import { - isValidSoftwareInstallStatus, - SoftwareInstallStatus, + isValidSoftwareAggregateStatus, + SoftwareAggregateStatus, } from "interfaces/software"; import { ITeam } from "interfaces/team"; import { IEmptyTableProps } from "interfaces/empty_table"; @@ -170,6 +170,7 @@ const ManageHostsPage = ({ includeNoTeam: true, overrideParamsOnTeamChange: { // remove the software status filter when selecting all teams + // TODO - update if supporting 'No teams' for this filter [HOSTS_QUERY_PARAMS.SOFTWARE_STATUS]: (newTeamId?: number) => !newTeamId, }, }); @@ -242,10 +243,12 @@ const ManageHostsPage = ({ queryParams?.software_title_id !== undefined ? parseInt(queryParams.software_title_id, 10) : undefined; - const softwareStatus = isValidSoftwareInstallStatus( + const softwareStatus = isValidSoftwareAggregateStatus( queryParams?.[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS] ) - ? (queryParams[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS] as SoftwareInstallStatus) + ? (queryParams[ + HOSTS_QUERY_PARAMS.SOFTWARE_STATUS + ] as SoftwareAggregateStatus) : undefined; const status = isAcceptableStatus(queryParams?.status) ? queryParams?.status @@ -736,7 +739,7 @@ const ManageHostsPage = ({ }; const handleSoftwareInstallStatausChange = ( - newStatus: SoftwareInstallStatus + newStatus: SoftwareAggregateStatus ) => { handleResetPageIndex(); @@ -846,7 +849,8 @@ const ManageHostsPage = ({ } else if (softwareTitleId) { newQueryParams.software_title_id = softwareTitleId; if (softwareStatus && teamIdForApi && teamIdForApi > 0) { - // software_status is only valid when software_title_id is present and a team is selected + // software_status is only valid when software_title_id is present and a team (other than + // 'No team') is selected newQueryParams[HOSTS_QUERY_PARAMS.SOFTWARE_STATUS] = softwareStatus; } } else if (mdmId) { diff --git a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx index 5f6e626b44c0..cfcd400726ab 100644 --- a/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx +++ b/frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx @@ -15,7 +15,7 @@ import { } from "interfaces/mdm"; import { IMunkiIssuesAggregate } from "interfaces/macadmins"; import { IPolicy } from "interfaces/policy"; -import { SoftwareInstallStatus } from "interfaces/software"; +import { SoftwareAggregateStatus } from "interfaces/software"; import { HOSTS_QUERY_PARAMS, @@ -72,7 +72,7 @@ interface IHostsFilterBlockProps { osSettingsStatus?: MdmProfileStatus; diskEncryptionStatus?: DiskEncryptionStatus; bootstrapPackageStatus?: BootstrapPackageStatus; - softwareStatus?: SoftwareInstallStatus; + softwareStatus?: SoftwareAggregateStatus; }; selectedLabel?: ILabel; isOnlyObserver?: boolean; @@ -88,7 +88,7 @@ interface IHostsFilterBlockProps { newMacSettingsStatus: MacSettingsStatusQueryParam ) => void; onChangeSoftwareInstallStatusFilter: ( - newStatus: SoftwareInstallStatus + newStatus: SoftwareAggregateStatus ) => void; onClickEditLabel: (evt: React.MouseEvent) => void; onClickDeleteLabel: () => void; diff --git a/frontend/services/entities/hosts.ts b/frontend/services/entities/hosts.ts index eca209aab4da..5a5c625f0cc9 100644 --- a/frontend/services/entities/hosts.ts +++ b/frontend/services/entities/hosts.ts @@ -12,7 +12,7 @@ import { import { IHostSoftware, ISoftware, - SoftwareInstallStatus, + SoftwareAggregateStatus, } from "interfaces/software"; import { DiskEncryptionStatus, @@ -72,7 +72,7 @@ export interface ILoadHostsOptions { softwareId?: number; softwareTitleId?: number; softwareVersionId?: number; - softwareStatus?: SoftwareInstallStatus; + softwareStatus?: SoftwareAggregateStatus; status?: HostStatus; mdmId?: number; mdmEnrollmentStatus?: string; @@ -103,7 +103,7 @@ export interface IExportHostsOptions { softwareId?: number; softwareTitleId?: number; softwareVersionId?: number; - softwareStatus?: SoftwareInstallStatus; + softwareStatus?: SoftwareAggregateStatus; status?: HostStatus; mdmId?: number; munkiIssueId?: number; @@ -133,7 +133,7 @@ export interface IActionByFilter { softwareId?: number | null; softwareTitleId?: number | null; softwareVersionId?: number | null; - softwareStatus?: SoftwareInstallStatus; + softwareStatus?: SoftwareAggregateStatus; osName?: string; osVersion?: string; osVersionId?: number | null; diff --git a/frontend/utilities/url/index.ts b/frontend/utilities/url/index.ts index a8557f4ab8c8..07c754a22779 100644 --- a/frontend/utilities/url/index.ts +++ b/frontend/utilities/url/index.ts @@ -9,7 +9,7 @@ import { HOSTS_QUERY_PARAMS, MacSettingsStatusQueryParam, } from "services/entities/hosts"; -import { isValidSoftwareInstallStatus } from "interfaces/software"; +import { isValidSoftwareAggregateStatus } from "interfaces/software"; export type QueryValues = string | number | boolean | undefined | null; export type QueryParams = Record; @@ -119,8 +119,9 @@ export const reconcileSoftwareParams = ({ | "softwareStatus" >) => { if ( - isValidSoftwareInstallStatus(softwareStatus) && + isValidSoftwareAggregateStatus(softwareStatus) && softwareTitleId && + // TODO - update if supporting 'No team' for software status filter teamId && teamId > 0 ) {