Migrate policy store update call from REST to GraphQl
What does this MR do and why?
Summary
This change migrates the policy update operation from a legacy REST API call to a GraphQL mutation, bringing it in line with how policy creation already works.
The updatePolicyStorePolicy REST call is replaced with a new governPolicyUpdate GraphQL mutation; the now-unused REST client method is removed in the stacked follow-up !255616 (merged). To avoid duplicating the list of policy fields across multiple queries, those fields are extracted into a reusable GraphQL fragment shared by both the fetch query and the new update mutation.
Error handling is simplified as a result: previously, the code had to inspect HTTP response bodies in multiple places to extract validation messages from different fields (message or error). Now that all writes go through GraphQL mutations, a rejected update always surfaces its reason through a single PolicyStoreMutationError, making the error-reading logic much shorter and consistent.
A small utility function (toMutationVariables) is introduced to translate the snake_case parameter names used internally into the camelCase names the GraphQL mutation expects, with a safety check that catches any unmapped parameters early rather than silently dropping them.
Tests are updated throughout to reflect the new error type and to remove references to the old REST mock setup.
References
- Issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/617792 (confidential)
- Approved plan and progress: https://gitlab.com/gitlab-org/gitlab/-/issues/617792#note_3830226456
- Epic: https://gitlab.com/groups/gitlab-org/-/epics/22542
- Create migration: !252071 (merged)
- Delete migration: !252577 (merged)
- Decision on Int policy ids at the GraphQL boundary: policy store ids are plain
Int, not GlobalID, because policies are gem value objects (established in !250240 (merged) and !251612 (merged))
Screenshots or screen recordings
| Description | UI |
|---|---|
| Update mutation instead of REST call |
How to set up and validate locally
- Enable the policy store experiment for your organization in GDK, the same way you did for the create/delete migrations. The frontend is gated behind the same setting.
- Open
/-/security/policy_storein the GDK, open an existing policy, click Edit, change the name, and Save. Expect the success alert on the detail page and the new name shown. In the browser's Network tab, expect a POST to/api/graphqlwith operationNamegovernPolicyUpdateand no PATCH request. - On the detail page, click Disable, then Enable. Expect the status badge and the version number to change without a page reload, and the GraphQL variables to contain only
organizationId,policyId, andlifecycleState. - Edit a policy without touching the Scope step and Save. Expect the request variables to not include
policyScope. - Rename a policy to a name that already exists in the organization and Save. Expect the inline "A policy with this name already exists" error on the name field, not a page alert.
Verification
jest ee/spec/frontend/policy_store ee/spec/frontend/api_spec.js→Test Suites: 31 passed, 31 total / Tests: 539 passed, 539 total- All six
.graphqldocuments underpolicy_store/graphql/validate against a freshgitlab:graphql:schema:dumpwith thegraphqlpackage (local ESLint cannot see the Apollo schema dump). - Adversarial review (
sp-reviewer): pass with findings. Fixed in this diff: unmapped params now throw instead of being dropped (F1), the update mapping spec pins the full shape withtoEqual(F2), a restating comment was removed (F7). Kept for the record: deadgetPolicyStorePolicyREST sibling (F3, pre-existing),apiErrorMessagenaming (F4), and the toggle-after-delete behaviour change above (F5).
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.