Paginate policy store policies GraphQL field as a connection

What does this MR do and why?

Summary

The policies field in the GraphQL API has been upgraded from returning a plain list to returning a paginated connection, allowing clients to fetch large policy sets in manageable chunks rather than all at once.

Key changes:

  • Clients now navigate results using nodes and pageInfo (with hasNextPage and endCursor) instead of receiving a flat array.
  • Pagination is forward-only: use first to set page size and after with the previous endCursor to advance. The page size is capped at the policy store's maximum.
  • Passing ids together with pagination arguments (first or after) is explicitly rejected with a clear error, since the backend returns all ID matches at once and combining them would silently drop results.
  • Invalid or out-of-range cursors are handled gracefully — bad cursors return an error, and cursors pointing beyond the last available page are clamped to the service maximum.
  • Requesting first: 0 short-circuits immediately and returns an empty page without hitting the backend.
  • Tests and API documentation have been updated to reflect the new paginated shape and cover edge cases like cursor validation, page size capping, and the ids+pagination restriction.
  • The frontend now reads from the connection as well: the shared query document selects nodes and pageInfo and accepts first and after, and the list mapper and the single-policy read take their rows from nodes. This was originally split into a stacked merge request, but the graphql-verify job validates every checked-in query document against the merged schema, so the schema change and the query change have to land together.

References

Screenshots or screen recordings

Description UI
GraphQl Result Screenshot 2026-09-15 at 16.20.31.png

How to set up and validate locally

  1. In a Rails console, enable the experiment:
    • Feature.enable(:security_policies_v2)
    • ApplicationSetting.current.update!(policy_store_experiment_enabled: true)
    • Organizations::Organization.find(1).update!(policy_store_experiment_enabled: true)
    • You must own the organization.
  2. Create more than 20 policies with POST /api/v4/organizations/1/security/policy_store.
  3. In http://gdk.test:3000/-/graphql-explorer, run:
{
  organization(id: "gid://gitlab/Organizations::Organization/1") {
    policyStore {
      policies(first: 5) {
        nodes { id name }
        pageInfo { hasNextPage endCursor }
      }
    }
  }
}

Expect 5 nodes and hasNextPage: true.

  1. Repeat with after: "<endCursor>". Expect the next 5 nodes, and hasNextPage: false on the last page.
  2. Run policies(ids: [1], first: 1). Expect the error "ids cannot be combined with first or after".
  3. Open the policy store list page for the organization and confirm policies still render, then open one policy. Both read through the updated query document.
  4. Run yarn jest ee/spec/frontend/policy_store. Expected result: all suites pass.

MR acceptance checklist

Evaluated against the MR acceptance checklist.

🤖 Generated with Claude Code

Edited by Artur Fedorov

Merge request reports

Loading
Loading