Skip to content

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

Open
allenhouchins wants to merge 21 commits into
mainfrom
claude/jovial-visvesvaraya-d4e1ba
Open

Add Targeted platforms column and platform filter to Policies page#44125
allenhouchins wants to merge 21 commits into
mainfrom
claude/jovial-visvesvaraya-d4e1ba

Conversation

@allenhouchins
Copy link
Copy Markdown
Member

@allenhouchins allenhouchins commented Apr 24, 2026

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:
    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

  • Changes file added for user-visible changes in changes/.

  • 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.

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

  • 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.

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.

Mirrors the Reports page pattern: a new non-sortable "Targeted
platforms" column (rendered via PlatformCell) and a platform filter
dropdown (All/macOS/Windows/Linux/ChromeOS) that syncs with the URL's
?platform= query param and resets pagination.

On the backend, ListGlobalPolicies, ListTeamPolicies,
ListMergedTeamPolicies, CountPolicies, CountMergedTeamPolicies, and the
corresponding service/request types now accept an optional platform
filter. Filtering is done in SQL with
(p.platforms = '' OR FIND_IN_SET(?, p.platforms)) so policies that
target all platforms (empty string) always match.
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 24, 2026

Codecov Report

❌ Patch coverage is 67.67677% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.73%. Comparing base (33ebf28) to head (27a822a).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
...policies/ManagePoliciesPage/ManagePoliciesPage.tsx 0.00% 13 Missing ⚠️
server/service/team_policies.go 60.00% 3 Missing and 3 partials ⚠️
...iesPage/components/PoliciesTable/PoliciesTable.tsx 58.33% 5 Missing ⚠️
server/fleet/policies.go 42.85% 4 Missing ⚠️
server/service/global_policies.go 63.63% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #44125      +/-   ##
==========================================
- Coverage   66.73%   66.73%   -0.01%     
==========================================
  Files        2731     2729       -2     
  Lines      218550   218712     +162     
  Branches    10808    10766      -42     
==========================================
+ Hits       145858   145963     +105     
- Misses      59487    59529      +42     
- Partials    13205    13220      +15     
Flag Coverage Δ
backend 68.56% <80.00%> (-0.02%) ⬇️
frontend 55.61% <37.93%> (+0.11%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Expand single-line anonymous assignments of ds.ListGlobalPoliciesFunc into multi-line function literals in gitops_test.go and testing_utils.go for readability and consistent formatting. No behavioral changes — the mocked functions still return nil.
Wrap the custom control and platform dropdown in a div with class `${baseClass}__filter-dropdowns` to provide a consistent horizontal layout. Add SCSS for the new class to display items in a row, center-align them, and apply gap spacing ($pad-medium). Affects PoliciesTable.tsx and _styles.scss.
Pass the filter-alt iconName to the platform filter dropdown in both PoliciesTable and QueriesTable to show a filter icon, and add a .__platform-dropdown style (width: 200px) in PoliciesTable SCSS to control the dropdown width for consistent layout.
@allenhouchins
Copy link
Copy Markdown
Member Author

@noahtalerman filter box width now matches the width on Reports and the icon has been updated to be consistent with the platform filter on the Hosts page. This is ready for review pending the checks passing.
Screenshot 2026-04-24 at 1 33 45 PM

@allenhouchins allenhouchins marked this pull request as ready for review April 24, 2026 18:48
Copilot AI review requested due to automatic review settings April 24, 2026 18:48
@allenhouchins allenhouchins requested review from a team, lukeheath and rachaelshaw as code owners April 24, 2026 18:48
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “Targeted platforms” column and a platform filter dropdown to the Policies page, backed by new platform query params and MySQL filtering so policies with empty platforms (all platforms) always match.

Changes:

  • Frontend: add non-sortable “Targeted platforms” column (PlatformCell) and a platform filter dropdown wired to URL query params.
  • Backend: thread optional platform through service/datastore APIs and apply platform filtering in policy list/count SQL.
  • Tests/docs: add MySQL integration coverage for platform filtering and document new REST query params.

Reviewed changes

Copilot reviewed 25 out of 27 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
server/service/testing_client.go Updates test teardown call site for new ListGlobalPolicies(..., platform) signature.
server/service/team_policies_test.go Updates service auth tests for new platform parameter in team policies list.
server/service/team_policies.go Threads platform through team policies list/count endpoints, service, and datastore calls.
server/service/global_policies_test.go Updates service auth tests for new platform parameter in global policies list.
server/service/global_policies.go Threads platform through global policies list/count endpoints, service, and datastore calls.
server/mock/service/service_mock.go Updates mock service function types and methods to include platform.
server/mock/datastore_mock.go Updates mock datastore function types and methods to include platform.
server/fleet/service.go Extends Fleet Service interface with platform args for policies list/count APIs.
server/fleet/datastore.go Extends Fleet Datastore interface with platform args for policies list/count APIs.
server/fleet/api_policies.go Adds optional platform query param to global/team policies list/count requests.
server/datastore/mysql/software_installers_test.go Updates MySQL tests for updated team policies list signature.
server/datastore/mysql/policies_test.go Adds testPoliciesPlatformFilter and updates many call sites for new signatures.
server/datastore/mysql/policies.go Implements platform filtering via platformFilterClause across list/count/team merge paths.
frontend/services/entities/team_policies.ts Adds optional platform param to team policies list/count API calls (normalizes "all").
frontend/services/entities/global_policies.ts Adds optional platform param to global policies list/count API calls (normalizes "all").
frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx Adds iconName to platform filter dropdown for queries table.
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss Adds layout styles for multiple filter dropdowns and platform dropdown sizing.
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx Adds “Targeted platforms” column rendered via PlatformCell.
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx Adds platform dropdown, URL update logic, and combines custom controls.
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx Adds tests for new column rendering and dropdown presence.
frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx Threads platform from URL into react-query keys and API calls; adjusts hide logic to consider platform.
docs/REST API/rest-api.md Documents platform query param for relevant policies list/count endpoints.
cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go Updates gitops integration tests for updated merged policies list signature.
cmd/fleetctl/fleetctl/testing_utils/testing_utils.go Updates fleetctl test mocks for updated policies list signatures.
cmd/fleetctl/fleetctl/testing_utils.go Updates empty gitops mocks for updated policies list signatures.
cmd/fleetctl/fleetctl/gitops_test.go Updates gitops unit tests for updated policies list signatures.
changes/policies-targeted-platforms-filter Adds release note entry for UI + API changes.
Comments suppressed due to low confidence (1)

server/datastore/mysql/policies.go:873

  • appendListOptionsToSQLSecure can add a cursor placeholder (?) and returns the corresponding args, but this code discards the returned args (query, _, err := ...). If InheritedAfter/cursor-based pagination is used, the generated SQL will include a ? without a matching argument and the query will fail. Capture the returned args and append them to args before executing the query.
	// We must normalize the name for full Unicode support (Unicode equivalence).
	match := norm.NFC.String(opts.MatchQuery)
	query, args = searchLike(query, args, match, policySearchColumns...)
	query, _, err := appendListOptionsToSQLSecure(query, &opts, policyAllowedOrderKeys)
	if err != nil {
		return nil, ctxerr.Wrap(ctx, err, "apply list options")
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 215 to 218
const page =
queryParams && queryParams.page ? parseInt(queryParams?.page, 10) : 0;
const targetedPlatformParam = queryParams?.platform;
const initialAutomationFilter = (() => {
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targetedPlatformParam is passed through to API calls and react-query keys without normalization/validation. This can cause inconsistent behavior:

  • platform=all is treated as “no filter” by the API clients (they omit it) and by the table UI, but it’s still truthy here for hide/isFiltered checks and creates a distinct cache key.
  • An invalid value (e.g. platform=macos) will be sent to the API (filtering results) while the dropdown falls back to “All platforms”, making the UI disagree with the data.
    Consider normalizing once here (e.g. if value is "all" or not isQueryablePlatform, set to undefined) and then use that normalized value for keys/conditions and props.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in commit 28d57581. targetedPlatformParam is now normalized at the point of derivation using isQueryablePlatform:

const targetedPlatformParam = isQueryablePlatform(queryParams?.platform)
  ? queryParams?.platform
  : undefined;

This means:

  • platform=allundefined (not in ["darwin","windows","linux","chrome"])
  • platform=macos (invalid) → undefined (also not in the list)
  • platform=darwin (valid) → "darwin" passes through

All downstream uses (API calls, react-query cache keys, hide/isFiltered conditions, and props to PoliciesTable) now consistently use the normalized value.

Comment thread changes/policies-targeted-platforms-filter Outdated
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a "Targeted platforms" column and a platform filter dropdown to the Policies UI. Introduces an optional platform query parameter to policy listing and counting endpoints and their request DTOs. The platform parameter is propagated through service methods, the Datastore interface, SQL query builders, mock implementations, and tests, enabling platform-scoped filtering for global, team, merged, and policy count results. Frontend React Query keys and policy-loading calls were updated to include the platform parameter.

Possibly related PRs

  • fleetdm/fleet (PR 33669) — Modifies frontend/services/entities/team_policies.ts and related policy query/count typings to add a platform query parameter and update policy query interfaces.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main changes: adding a Targeted platforms column and a platform filter dropdown to the Policies page.
Description check ✅ Passed The PR description is comprehensive and covers the implementation details, testing, and validation checklist items appropriately.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/jovial-visvesvaraya-d4e1ba

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx (1)

1021-1040: ⚠️ Potential issue | 🟡 Minor

Update stale comment to mention platform filter.

The sibling renderAutomationFilter hide-comment at line 1109 was updated to list the platform filter, but this comment still reads "no filters (search or automation dropdown)" even though !targetedPlatformParam is now part of the predicate. Worth a quick sync so the two match.

📝 Proposed edit
-    // Hide count if fetching count || there are errors OR there are no policy results with no filters (search or automation dropdown)
+    // Hide count if fetching count || there are errors OR there are no policy results with no filters (search, automation dropdown, or platform)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx` around
lines 1021 - 1040, Update the comment above renderPoliciesCountAndLastUpdated to
reflect that the hide condition now includes the platform filter; specifically,
change the wording "no filters (search or automation dropdown)" to mention the
platform filter as well (e.g., "no filters (search, automation dropdown, or
platform)"), so the comment stays consistent with the predicate that checks
searchQuery, automationFilter, and targetedPlatformParam alongside policyResults
and policiesErrors.
🧹 Nitpick comments (4)
frontend/services/entities/global_policies.ts (1)

28-29: Optional: tighten platform to a union type.

The JSDoc already enumerates the valid tokens ("darwin" | "windows" | "linux" | "chrome" plus "all" for the UI normalization). Promoting those to a shared union type (exported from here and reused by team_policies.ts and PoliciesTable) would catch typos at compile time and make refactors when platforms are added/removed safer. Not blocking.

♻️ Sketch
+export type PolicyTargetedPlatform = "darwin" | "windows" | "linux" | "chrome";
+export type PolicyPlatformFilter = PolicyTargetedPlatform | "all";
+
 export interface IGlobalPoliciesApiQueryParams {
   page?: number;
   perPage?: number;
   orderKey?: string;
   orderDirection?: "asc" | "desc";
   query?: string;
   automationType?: GlobalPoliciesAutomationType;
-  /** Targeted platform to filter policies by (e.g. "darwin"/"windows"/"linux"/"chrome"). */
-  platform?: string;
+  /** Targeted platform to filter policies by. "all" is normalized to no filter. */
+  platform?: PolicyPlatformFilter;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/services/entities/global_policies.ts` around lines 28 - 29, The
platform field is currently typed as string; tighten it to a shared exported
union type (e.g., export type Platform = "darwin" | "windows" | "linux" |
"chrome" | "all") and replace the inline platform?: string with platform?:
Platform in global_policies.ts; export that Platform type so team_policies.ts
and PoliciesTable can import and reuse it to catch typos and simplify future
platform changes.
server/datastore/mysql/policies_test.go (1)

474-509: Consider asserting team-only CountPolicies with platform filter.

This test validates team list + merged count, but it doesn’t explicitly lock down team-only CountPolicies(..., &team.ID, ..., platform) behavior. Adding those assertions would close a useful regression gap.

♻️ Suggested test additions
@@
 	// ListTeamPolicies with windows: the team has no darwin-only policy, so
 	// we expect team-all (empty platform) + cross-platform + win-linux inherited.
 	teamPols, inherited, err := ds.ListTeamPolicies(ctx, team.ID, fleet.ListOptions{OrderKey: "name"}, fleet.ListOptions{OrderKey: "name"}, "", "windows")
 	require.NoError(t, err)
 	require.Len(t, teamPols, 1)
 	assert.Equal(t, "team-all", teamPols[0].Name)
 	require.Len(t, inherited, 2)
 	inheritedNames := []string{inherited[0].Name, inherited[1].Name}
 	assert.ElementsMatch(t, []string{"cross-platform", "win-linux"}, inheritedNames)
+
+	// Team-only count should match teamPols filtering (no inherited globals).
+	teamCount, err := ds.CountPolicies(ctx, &team.ID, "", "", "windows")
+	require.NoError(t, err)
+	assert.Equal(t, 1, teamCount) // team-all only
+
+	teamCount, err = ds.CountPolicies(ctx, &team.ID, "", "", "darwin")
+	require.NoError(t, err)
+	assert.Equal(t, 2, teamCount) // team-all + team-darwin
 
 	// CountMergedTeamPolicies with platform filter
 	mergedCount, err := ds.CountMergedTeamPolicies(ctx, team.ID, "", "", "darwin")
 	require.NoError(t, err)
 	assert.Equal(t, 4, mergedCount)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/datastore/mysql/policies_test.go` around lines 474 - 509, Add explicit
assertions for team-scoped CountPolicies with platform filters: after creating
the team policies (NewTeamPolicy) and before/after the
ListTeamPolicies/CountMergedTeamPolicies checks, call CountPolicies for the team
(pass the team.ID as the team parameter) with platform="darwin" and assert it
returns 2 (team-darwin + team-all), and call CountPolicies with
platform="windows" and assert it returns 1 (team-all only); use the existing
ds.CountPolicies function name to locate the implementation and add the two
require/assert checks analogous to the existing CountMergedTeamPolicies and
ListTeamPolicies assertions.
frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx (1)

194-201: Minor: memoize combinedCustomControl for consistency.

renderPlatformDropdown and handlePlatformFilterDropdownChange are both useCallback-memoized, but combinedCustomControl is a new function on every render, which defeats those memos at the TableContainer boundary. Not a correctness issue — purely a consistency/perf nit.

♻️ Proposed refactor
-  const combinedCustomControl = () => {
-    return (
-      <div className={`${baseClass}__filter-dropdowns`}>
-        {customControl?.()}
-        {renderPlatformDropdown()}
-      </div>
-    );
-  };
+  const combinedCustomControl = useCallback(
+    () => (
+      <div className={`${baseClass}__filter-dropdowns`}>
+        {customControl?.()}
+        {renderPlatformDropdown()}
+      </div>
+    ),
+    [customControl, renderPlatformDropdown]
+  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx`
around lines 194 - 201, combinedCustomControl is recreated each render which
defeats callers relying on memoized callbacks; wrap it in useCallback to memoize
it. Update the function to const combinedCustomControl = useCallback(() => ( ...
), [customControl, renderPlatformDropdown, baseClass]) so it only changes when
its inputs change (include customControl, renderPlatformDropdown and baseClass
in the dependency array) and use that memoized callback where TableContainer
expects it.
server/fleet/datastore.go (1)

868-872: LGTM — consistent signature extension.

ListGlobalPolicies, CountPolicies, and CountMergedTeamPolicies now accept platform as the trailing parameter, matching the pattern previously used for automationType. The semantics (empty/"all" → no filter, otherwise FIND_IN_SET against p.platforms) are implemented safely via parameter binding in server/datastore/mysql/policies.go (see platformFilterClause), so there are no SQL-injection concerns introduced at this layer.

One optional nit: since callers pass a bare string where "" and "all" both mean "no filter" and valid tokens are a closed set (e.g. darwin, windows, linux, chrome), a short doc comment on these methods documenting the accepted values would make the contract explicit for future implementers/mocks. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/fleet/datastore.go` around lines 868 - 872, Add brief doc comments to
the affected datastore methods (ListGlobalPolicies, CountPolicies,
CountMergedTeamPolicies) describing the platform parameter contract: valid
tokens (e.g. "darwin", "windows", "linux", "chrome"), and that empty string or
"all" means no platform filter is applied; also note that other values are
matched against p.platforms via FIND_IN_SET semantics implemented in the MySQL
layer. Keep comments short and placed immediately above each method signature so
implementers and mocks can rely on the explicit parameter behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx`:
- Line 80: The component currently declares router?: InjectedRouter which allows
silent no-ops when router is missing; make the router prop required (change
router?: InjectedRouter to router: InjectedRouter) on the PoliciesTable
component and update all internal uses (replace router?.push(...) with
router.push(...), specifically in the filter-change handlers around the existing
router?.push calls at lines ~126-143) so the TypeScript compiler will force
callers to pass a router; then update any parent/factory that renders
PoliciesTable to provide the InjectedRouter (or wrap PoliciesTable with the
router HOC) so callers compile.

In `@server/service/team_policies.go`:
- Line 234: Add an HTTP integration test that exercises the platform query
binding end-to-end by calling the ListTeamPolicies and CountTeamPolicies
endpoints with ?platform=darwin; create policies with varying Platform values
using existing test helpers, invoke the handlers via the integration test server
(add the test in integration_core_test.go or integration_enterprise_test.go),
assert that responses include only darwin policies and that CountTeamPolicies
returns the matching count, and name the test something like
TestPoliciesPlatformFilterHTTP to mirror the datastore test; this verifies
ListTeamPoliciesRequest.Platform and CountTeamPoliciesRequest.Platform are bound
and forwarded correctly.

---

Outside diff comments:
In `@frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx`:
- Around line 1021-1040: Update the comment above
renderPoliciesCountAndLastUpdated to reflect that the hide condition now
includes the platform filter; specifically, change the wording "no filters
(search or automation dropdown)" to mention the platform filter as well (e.g.,
"no filters (search, automation dropdown, or platform)"), so the comment stays
consistent with the predicate that checks searchQuery, automationFilter, and
targetedPlatformParam alongside policyResults and policiesErrors.

---

Nitpick comments:
In
`@frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx`:
- Around line 194-201: combinedCustomControl is recreated each render which
defeats callers relying on memoized callbacks; wrap it in useCallback to memoize
it. Update the function to const combinedCustomControl = useCallback(() => ( ...
), [customControl, renderPlatformDropdown, baseClass]) so it only changes when
its inputs change (include customControl, renderPlatformDropdown and baseClass
in the dependency array) and use that memoized callback where TableContainer
expects it.

In `@frontend/services/entities/global_policies.ts`:
- Around line 28-29: The platform field is currently typed as string; tighten it
to a shared exported union type (e.g., export type Platform = "darwin" |
"windows" | "linux" | "chrome" | "all") and replace the inline platform?: string
with platform?: Platform in global_policies.ts; export that Platform type so
team_policies.ts and PoliciesTable can import and reuse it to catch typos and
simplify future platform changes.

In `@server/datastore/mysql/policies_test.go`:
- Around line 474-509: Add explicit assertions for team-scoped CountPolicies
with platform filters: after creating the team policies (NewTeamPolicy) and
before/after the ListTeamPolicies/CountMergedTeamPolicies checks, call
CountPolicies for the team (pass the team.ID as the team parameter) with
platform="darwin" and assert it returns 2 (team-darwin + team-all), and call
CountPolicies with platform="windows" and assert it returns 1 (team-all only);
use the existing ds.CountPolicies function name to locate the implementation and
add the two require/assert checks analogous to the existing
CountMergedTeamPolicies and ListTeamPolicies assertions.

In `@server/fleet/datastore.go`:
- Around line 868-872: Add brief doc comments to the affected datastore methods
(ListGlobalPolicies, CountPolicies, CountMergedTeamPolicies) describing the
platform parameter contract: valid tokens (e.g. "darwin", "windows", "linux",
"chrome"), and that empty string or "all" means no platform filter is applied;
also note that other values are matched against p.platforms via FIND_IN_SET
semantics implemented in the MySQL layer. Keep comments short and placed
immediately above each method signature so implementers and mocks can rely on
the explicit parameter behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d7e6b88a-8a44-4e7d-a0bc-1d6af92fbaa4

📥 Commits

Reviewing files that changed from the base of the PR and between cc5fa22 and a4c5ff4.

⛔ Files ignored due to path filters (1)
  • docs/REST API/rest-api.md is excluded by !**/*.md
📒 Files selected for processing (26)
  • changes/policies-targeted-platforms-filter
  • cmd/fleetctl/fleetctl/gitops_test.go
  • cmd/fleetctl/fleetctl/testing_utils.go
  • cmd/fleetctl/fleetctl/testing_utils/testing_utils.go
  • cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go
  • frontend/pages/policies/ManagePoliciesPage/ManagePoliciesPage.tsx
  • frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tests.tsx
  • frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx
  • frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTableConfig.tsx
  • frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss
  • frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTable.tsx
  • frontend/services/entities/global_policies.ts
  • frontend/services/entities/team_policies.ts
  • server/datastore/mysql/policies.go
  • server/datastore/mysql/policies_test.go
  • server/datastore/mysql/software_installers_test.go
  • server/fleet/api_policies.go
  • server/fleet/datastore.go
  • server/fleet/service.go
  • server/mock/datastore_mock.go
  • server/mock/service/service_mock.go
  • server/service/global_policies.go
  • server/service/global_policies_test.go
  • server/service/team_policies.go
  • server/service/team_policies_test.go
  • server/service/testing_client.go

}

tmPols, inheritedPols, err := svc.ListTeamPolicies(ctx, req.TeamID, req.Opts, inheritedListOptions, req.MergeInherited, req.AutomationType)
tmPols, inheritedPols, err := svc.ListTeamPolicies(ctx, req.TeamID, req.Opts, inheritedListOptions, req.MergeInherited, req.AutomationType, req.Platform)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for integration coverage of the new platform query param on the list/count endpoints
rg -nP -C3 '[?&]platform=' --type=go -g '!vendor/**'
rg -nP -C3 'testPoliciesPlatformFilter' --type=go

Repository: fleetdm/fleet

Length of output: 8629


🏁 Script executed:

# Check the full testPoliciesPlatformFilter test implementation
sed -n '400,500p' server/datastore/mysql/policies_test.go

Repository: fleetdm/fleet

Length of output: 3858


🏁 Script executed:

# Find the ListTeamPoliciesRequest and CountTeamPoliciesRequest struct definitions
rg -n 'type (ListTeamPoliciesRequest|CountTeamPoliciesRequest)' --type=go -A 10

Repository: fleetdm/fleet

Length of output: 1939


🏁 Script executed:

# Find the endpoint handlers for list/count team policies
rg -n 'listTeamPoliciesHandler|countTeamPoliciesHandler|ListTeamPolicies|CountTeamPolicies' --type=go -A 5 | head -100

Repository: fleetdm/fleet

Length of output: 8536


🏁 Script executed:

# Search for integration tests that call team policies endpoints with platform parameter
rg -n 'teams.*policies.*platform|platform.*teams.*policies' --type=go -A 5 -B 5

Repository: fleetdm/fleet

Length of output: 1249


🏁 Script executed:

# Check if there are any integration tests in integration_core_test.go that test platform filtering
rg -n 'TestTeamPolicies.*' --type=go server/service/integration_core_test.go

Repository: fleetdm/fleet

Length of output: 270


🏁 Script executed:

# Look for any test that constructs a URL with platform query param for team policies
rg -n '\/teams\/.*policies.*platform' --type=go

Repository: fleetdm/fleet

Length of output: 153


🏁 Script executed:

# Search for integration tests with platform parameter on team policies endpoints
rg -n 'teams.*policies.*\?platform' --type=go

Repository: fleetdm/fleet

Length of output: 39


🏁 Script executed:

# Look for any recent tests that might test platform filtering on team policies
rg -n 'ListTeamPolicies|CountTeamPolicies' --type=go server/service/integration_core_test.go -A 3

Repository: fleetdm/fleet

Length of output: 2065


🏁 Script executed:

# Check the test files in service directory for platform filtering tests on team endpoints
fd -e _test.go server/service | xargs grep -l 'platform.*team.*polic\|team.*polic.*platform' 2>/dev/null | head -5

Repository: fleetdm/fleet

Length of output: 419


🏁 Script executed:

# Look for all places where platform filtering is tested for team policies
rg -n 'testPoliciesPlatformFilter|ListMergedTeamPolicies|ListTeamPolicies.*platform' --type=go -B 2 -A 2

Repository: fleetdm/fleet

Length of output: 18642


🏁 Script executed:

# Check if there are integration tests that test platform parameter at HTTP level for any endpoint
rg -n 'DoJSON.*platform.*=|DoRaw.*platform.*=' --type=go server/service/integration_core_test.go | head -20

Repository: fleetdm/fleet

Length of output: 39


Request DTO binding is correct, but add HTTP integration test for platform parameter.

The query:"platform,optional" tags are properly set on both ListTeamPoliciesRequest.Platform (server/fleet/api_policies.go:192) and CountTeamPoliciesRequest.Platform (server/fleet/api_policies.go:212), and both handlers correctly read and pass req.Platform to the service layer. However, there is no HTTP integration test that exercises the ?platform= query parameter on these endpoints. While testPoliciesPlatformFilter in server/datastore/mysql/policies_test.go provides datastore-level coverage of platform filtering for team policies (lines 489–495), add an HTTP-level integration test in integration_core_test.go or integration_enterprise_test.go that calls these endpoints with ?platform=darwin to verify end-to-end binding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/service/team_policies.go` at line 234, Add an HTTP integration test
that exercises the platform query binding end-to-end by calling the
ListTeamPolicies and CountTeamPolicies endpoints with ?platform=darwin; create
policies with varying Platform values using existing test helpers, invoke the
handlers via the integration test server (add the test in
integration_core_test.go or integration_enterprise_test.go), assert that
responses include only darwin policies and that CountTeamPolicies returns the
matching count, and name the test something like TestPoliciesPlatformFilterHTTP
to mirror the datastore test; this verifies ListTeamPoliciesRequest.Platform and
CountTeamPoliciesRequest.Platform are bound and forwarded correctly.

@noahtalerman
Copy link
Copy Markdown
Member

filter box width now matches the width on Reports and the icon has been updated to be consistent with the platform filter on the Hosts page. This is ready for review pending the checks passing.

Sweet! FYI @sharon-fdm @rachaelshaw re this quick win: #23737

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
nulmete
nulmete previously approved these changes May 5, 2026
Copy link
Copy Markdown
Member

@nulmete nulmete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Test failure seems unrelated.

@allenhouchins
Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

…araya-d4e1ba

# Conflicts:
#	frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/PoliciesTable.tsx
#	server/fleet/service.go

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 12, 2026

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in commit 9b267030. Two files had conflicts:

  1. PoliciesTable.tsx: Combined disableTableHeader={!searchable} (added in main) with our combinedCustomControl and selectedDropdownFilter={platform} props.
  2. server/fleet/service.go: Kept GetTeamPolicyByID (the correct function name from main) while preserving our platform string addition to CountTeamPolicies.

Go build and TypeScript both pass cleanly.

Adjust test call sites to match updated datastore API: ListGlobalPolicies and ListTeamPolicies gained additional string parameters. Pass empty string(s) in tests so they compile and keep existing assertions. Changes applied to server/datastore/mysql/policies_test.go and server/service/integration_enterprise_test.go.
@allenhouchins allenhouchins marked this pull request as draft May 12, 2026 16:01
ListTeamPoliciesFunc/ListMergedTeamPoliciesFunc mocks and
svc.ListTeamPolicies calls were missing the new platform string
parameter, causing golangci-lint typecheck failures.

Co-authored-by: Cursor <cursoragent@cursor.com>
@allenhouchins
Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

…araya-d4e1ba

Co-authored-by: allenhouchins <32207388+allenhouchins@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 13, 2026

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in commit 2a331dce. This merge with the latest main was clean — no conflicts.

@allenhouchins allenhouchins marked this pull request as ready for review May 13, 2026 18:11
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@allenhouchins allenhouchins requested a review from nulmete May 13, 2026 18:11
Copy link
Copy Markdown
Member

@nulmete nulmete left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall 👍 I've tested locally and found no issues.

Just two stylistic comments to address:

  1. Remove the entire .table-container__search-input css block in frontend/pages/policies/ManagePoliciesPage/components/PoliciesTable/_styles.scss L57-68.
  2. Use t.Context() in server/datastore/mysql/policies_test.go L404.

Comment thread server/datastore/mysql/policies_test.go Outdated

func testPoliciesPlatformFilter(t *testing.T, ds *Datastore) {
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
ctx := context.Background()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use t.Context() (it's test-scoped and ensures proper cleanup - context.Background() does not)

there are other similar occurrences in this file but it's from an older pattern, modifying this one is enough 👍

width: 100%;

.table-container__search-input {
margin-left: 0;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think margin-left: 0 is not needed.

order: -2;

.policies-table__filter-dropdowns {
flex: 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

.form-field--dropdown,
.policies-table__platform-dropdown {
flex: 1;
width: auto;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe width: auto is not needed as well.


&__header-left {
order: 2;
display: flex;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: display: flex is already declared as part of the TableContainer styles (see @frontend/components/TableContainer/_styles.scss#L120)

Comment on lines +64 to +66
& .search-field__input-wrapper {
width: auto;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is not affecting any element. I did a quick check and it doesn't seem we have any elements with this class:

Image

Therefore I'd remove this style block, and I'll put an item on the FE sync agenda to revisit this particular style, since it's also present on other files

Image

Comment on lines +60 to +62
.input-with-icon {
width: 100%;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed as well, width: 100% is also part of the TableContainer styles.

Image

Comment on lines +53 to +68
.table-container__search {
order: 1;
width: 100%;

.table-container__search-input {
margin-left: 0;

.input-with-icon {
width: 100%;
}

& .search-field__input-wrapper {
width: auto;
}
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this whole block is not needed.

Suggested change
.table-container__search {
order: 1;
width: 100%;
.table-container__search-input {
margin-left: 0;
.input-with-icon {
width: 100%;
}
& .search-field__input-wrapper {
width: auto;
}
}
}

Remove unused/duplicated CSS rules in the policies table component (.table-container__search and some flex/width overrides) and simplify filter dropdown styling. In tests, replace context.Background() with t.Context() in testPoliciesPlatformFilter so the test uses the test's context (enabling cancellation/timeouts).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add platform filter to Policies page, API, and fleetctl

7 participants