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:
co-authored by
Copilot
copilot-swe-agent[bot]
Cursor
nulmete
parent
15118e4797
commit
078fbc0f40
@@ -3970,7 +3970,7 @@ reports:
|
||||
|
||||
team, err := s.DS.TeamByName(ctx, teamName)
|
||||
require.NoError(t, err)
|
||||
pols, err := s.DS.ListMergedTeamPolicies(ctx, team.ID, fleet.ListOptions{}, "")
|
||||
pols, err := s.DS.ListMergedTeamPolicies(ctx, team.ID, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pols, 3)
|
||||
policyIDsByName := map[string]uint{}
|
||||
@@ -3987,7 +3987,7 @@ reports:
|
||||
"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", teamFile,
|
||||
}))
|
||||
|
||||
pols, err = s.DS.ListMergedTeamPolicies(ctx, team.ID, fleet.ListOptions{}, "")
|
||||
pols, err = s.DS.ListMergedTeamPolicies(ctx, team.ID, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, pols, "all policies should be removed after FMA installer is removed")
|
||||
|
||||
@@ -4860,7 +4860,7 @@ settings:
|
||||
installer, err := s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, titles[0].ID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
tmPols, err := s.DS.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{}, "")
|
||||
tmPols, err := s.DS.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tmPols, 1)
|
||||
require.Equal(t, "Install ruby", tmPols[0].Name)
|
||||
@@ -4880,7 +4880,7 @@ settings:
|
||||
installer, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &tm.ID, titles[0].ID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
tmPols, err = s.DS.ListMergedTeamPolicies(ctx, tm.ID, fleet.ListOptions{}, "")
|
||||
tmPols, err = s.DS.ListMergedTeamPolicies(ctx, tm.ID, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, tmPols, 1)
|
||||
require.Equal(t, "Install team ruby", tmPols[0].Name)
|
||||
@@ -4934,7 +4934,7 @@ labels:
|
||||
s.assertRealRunOutput(t, fleetctltest.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fullFile.Name()}))
|
||||
|
||||
// Verify policy, agent_options, controls, and reports were applied.
|
||||
policies, err := s.DS.ListGlobalPolicies(ctx, fleet.ListOptions{})
|
||||
policies, err := s.DS.ListGlobalPolicies(ctx, fleet.ListOptions{}, "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 1)
|
||||
require.Equal(t, "Test Global Policy", policies[0].Name)
|
||||
@@ -4977,7 +4977,7 @@ org_settings:
|
||||
s.assertRealRunOutput(t, fleetctltest.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", minimalFile.Name()}))
|
||||
|
||||
// Verify policies were cleared.
|
||||
policies, err = s.DS.ListGlobalPolicies(ctx, fleet.ListOptions{})
|
||||
policies, err = s.DS.ListGlobalPolicies(ctx, fleet.ListOptions{}, "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 0)
|
||||
|
||||
@@ -5082,7 +5082,7 @@ policies:
|
||||
s.assertRealRunOutput(t, fleetctltest.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", teamFile}))
|
||||
|
||||
// The global policy persisted both its include_any and exclude_all scopes.
|
||||
globalPolicies, err := s.DS.ListGlobalPolicies(ctx, fleet.ListOptions{})
|
||||
globalPolicies, err := s.DS.ListGlobalPolicies(ctx, fleet.ListOptions{}, "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, globalPolicies, 1)
|
||||
gp := globalPolicies[0]
|
||||
@@ -5097,7 +5097,7 @@ policies:
|
||||
// The team policy persisted both its include_all and exclude_any scopes.
|
||||
tm, err := s.DS.TeamByName(ctx, fleetName)
|
||||
require.NoError(t, err)
|
||||
teamPolicies, _, err := s.DS.ListTeamPolicies(ctx, tm.ID, fleet.ListOptions{}, fleet.ListOptions{}, "")
|
||||
teamPolicies, _, err := s.DS.ListTeamPolicies(ctx, tm.ID, fleet.ListOptions{}, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, teamPolicies, 1)
|
||||
tp := teamPolicies[0]
|
||||
@@ -5200,7 +5200,7 @@ labels:
|
||||
fl, err := s.DS.TeamByName(ctx, fleetName)
|
||||
require.NoError(t, err)
|
||||
|
||||
flPols, err := s.DS.ListMergedTeamPolicies(ctx, fl.ID, fleet.ListOptions{}, "")
|
||||
flPols, err := s.DS.ListMergedTeamPolicies(ctx, fl.ID, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, flPols, 1)
|
||||
require.Equal(t, "Test Fleet Policy", flPols[0].Name)
|
||||
@@ -5241,7 +5241,7 @@ name: %s
|
||||
}))
|
||||
|
||||
// Verify policies were cleared.
|
||||
flPols, err = s.DS.ListMergedTeamPolicies(ctx, fl.ID, fleet.ListOptions{}, "")
|
||||
flPols, err = s.DS.ListMergedTeamPolicies(ctx, fl.ID, fleet.ListOptions{}, "", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, flPols, 0)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user