Add Targeted platforms column and platform filter to Policies page (#44125)

- @noahtalerman: For the following quick win:
  - https://github.com/fleetdm/fleet/issues/23737

## Summary

Adds a "Targeted platforms" column and a platform filter dropdown to the
Policies page (`/policies/manage`), matching the pattern already used on
the Reports page (`/queries/manage`, `ManageQueriesPage`).

### Frontend
- New non-sortable **Targeted platforms** column rendered via
`PlatformCell`, sourced from each policy's comma-separated `platform`
field.
- New platform filter dropdown (All / macOS / Windows / Linux /
ChromeOS) wired as a `customControl` on the Policies table, alongside
the existing automation filter. Selecting a value pushes a new URL (not
a replace), resets `page` to 0, and updates the `platform` query param.
- `ManagePoliciesPage` reads `location.query.platform` and threads it
through to both `globalPoliciesAPI.loadAll` / `teamPoliciesAPI.loadAll`
and the react-query keys, plus the count endpoints. The
automation-filter and count "hide" conditions now include the platform
filter so they remain visible when only a platform filter is active.
- `frontend/services/entities/global_policies.ts` and `team_policies.ts`
accept an optional `platform` param (with `"all"` normalized to
`undefined`).
- Added tests for the new column and dropdown in
`PoliciesTable.tests.tsx`.

### Backend
- Added `Platform string ` + `` `query:"platform,optional"` `` to
`ListGlobalPoliciesRequest`, `CountGlobalPoliciesRequest`,
`ListTeamPoliciesRequest`, `CountTeamPoliciesRequest`.
- Extended datastore and service signatures (`ListGlobalPolicies`,
`ListTeamPolicies`, `ListMergedTeamPolicies`, `CountPolicies`,
`CountMergedTeamPolicies`, `ListGlobalPolicies`/`ListTeamPolicies` on
the service) to accept a `platform string` arg. Mocks and all call sites
updated.
- Platform filtering in SQL uses a new helper `platformFilterClause`:
  ```sql
  AND (p.platforms = '' OR FIND_IN_SET(?, p.platforms))
  ```
so policies targeting "all platforms" (empty `platforms` field) always
match regardless of the selected filter. `FIND_IN_SET` uses a bound
parameter (no injection risk).
- Added a new MySQL integration test `testPoliciesPlatformFilter`
covering empty-platform (match-all), per-platform filter, and
team/merged paths.

### Docs
- REST API docs for `GET /api/v1/fleet/global/policies`, `GET
/api/v1/fleet/fleets/:id/policies`, and the corresponding `/count`
endpoints now document the `platform` query param.
- Added `changes/policies-targeted-platforms-filter`.

## Behavior

- `platform=all` (or missing) returns all policies.
- Selecting a specific platform returns policies whose `platforms`
column is empty OR contains the selected token.
- The dropdown only renders when the table is searchable (results exist
OR any filter is active).
- Changing the filter pushes a new URL and resets the page.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually

Local verification:
- `go build ./...` — clean
- `go vet ./server/... ./cmd/... ./ee/...` — clean
- `make lint-go-incremental` — 0 issues
- Go service-level policy tests pass. MySQL integration tests compile
but could not be run locally (no Docker); CI will exercise the new
`testPoliciesPlatformFilter` test.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a "Targeted platforms" column with platform icons and an "All
platforms" option.
* Added a platform filter dropdown to scope policy lists; counts,
last-updated, and controls adapt when a platform filter is active.
Backend now honors an optional platform query parameter so filtering
returns matching policies.

* **Tests**
* Added and updated unit and integration tests covering the new column,
filter UI, and platform-filtered policy listings.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: nulmete <nicoulmete1@gmail.com>
This commit is contained in:
Allen Houchins
2026-07-10 13:32:16 -05:00
committed by GitHub
co-authored by Copilot copilot-swe-agent[bot] Cursor nulmete
parent 15118e4797
commit 078fbc0f40
29 changed files with 745 additions and 245 deletions
+4
View File
@@ -9227,6 +9227,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
| order_key | string | query | What to order results by. Allowed fields are `id`, `name`, `team_id`, `created_at`, `updated_at`, `failing_host_count`, and `passing_host_count`. |
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `"asc"` and `"desc"`. Default is `"asc"`. |
| after | string | query | The value to get results after. This needs `order_key` defined, as that's the column that would be used. |
| platform | string | query | Filters policies by targeted platform. Accepts `"darwin"`, `"windows"`, `"linux"`, or `"chrome"`. Policies that target all platforms (empty `platform` field) are always included. |
#### Example
@@ -9302,6 +9303,7 @@ _Available in Fleet Premium_
| order_direction | string | query | **Requires `order_key`**. The direction of the order given the order key. Options include `"asc"` and `"desc"`. Default is `"asc"`. |
| after | string | query | The value to get results after. This needs `order_key` defined, as that's the column that would be used. |
| automation_type | string | query | Filters by automation type. Supported values are "software", "scripts", "calendar", "conditional_access", and "other". |
| platform | string | query | Filters policies by targeted platform. Accepts `"darwin"`, `"windows"`, `"linux"`, or `"chrome"`. Policies that target all platforms (empty `platform` field) are always included. |
#### Example (default usage)
@@ -9500,6 +9502,7 @@ _Available in Fleet Premium_
| Name | Type | In | Description |
| ------------------ | ------- | ---- | ------------------------------------------------------------------------------------------------------------- |
| query | string | query | Search query keywords. Searchable fields include `name`. |
| platform | string | query | Filters policies by targeted platform. Accepts `"darwin"`, `"windows"`, `"linux"`, or `"chrome"`. Policies that target all platforms (empty `platform` field) are always included. |
#### Example
@@ -9530,6 +9533,7 @@ _Available in Fleet Premium_
| query | string | query | Search query keywords. Searchable fields include `name`. |
| merge_inherited | boolean | query | If `true`, will include inherited ("All fleets") policies in the count. |
| automation_type | string | query | Filters by automation type. Supported values are "software", "scripts", "calendar", "conditional_access", and "other". |
| platform | string | query | Filters policies by targeted platform. Accepts `"darwin"`, `"windows"`, `"linux"`, or `"chrome"`. Policies that target all platforms (empty `platform` field) are always included. |
#### Example