Add minimum coverage threshold (2/6): Service logic & REST API

What does this MR do and why?

This is MR 2 of 6 in a stacked series that adds a minimum coverage threshold to the Coverage-Check approval rule.

MR Branch Base Content
1 6284-coverage-threshold-1-db-model-ff master DB migration & model validation (!226363 (merged))
2 (this MR) 6284-coverage-threshold-2-service-rest 6284-coverage-threshold-1-db-model-ff Service logic, REST API & feature flag
3 6284-coverage-threshold-3-graphql 6284-coverage-threshold-2-service-rest GraphQL mutations & fields
4 6284-coverage-threshold-4-blockage-reason 6284-coverage-threshold-3-graphql blockage_reason feature & GraphQL exposure
5 6284-coverage-threshold-5-frontend 6284-coverage-threshold-4-blockage-reason Frontend: Settings UI + MR widget feedback
6 6284-coverage-threshold-6-docs master Documentation (standalone, no dependency)

Splitting was discussed and agreed upon with @kherman1 and @hfyngvason in !224343 (closed).

This MR includes:

  • coverage_minimum_threshold feature flag (gitlab_com_derisk) definition, introduced here together with its first consumer in the critical mergeability path (recommended by @hfyngvason; relocated from MR 1 per @SamWord's review)
  • Absolute threshold check in Ci::SyncReportsToApprovalRulesService#require_coverage_approval?, gated behind the feature flag. When enabled and a threshold is set, approval is required if pipeline coverage falls below the threshold, regardless of whether coverage decreased from the base pipeline.
  • Helper method coverage_minimum_threshold_for to read the threshold from the MR's approval rules
  • REST API coverage_minimum_threshold parameter on project approval rule create and update endpoints
  • coverage_minimum_threshold exposure in the approval rule API entity (only for code_coverage rules)
  • Standardized API parameter descriptions per reviewer feedback from @idurham
  • Generated OpenAPI v2/v3 documentation updates

Files to review in this MR

Note: This is a stacked MR. The diff shows cumulative changes against master. Please focus your review on the files listed below, which are the ones introduced in this MR.

  • ee/config/feature_flags/gitlab_com_derisk/coverage_minimum_threshold.yml - Feature flag definition
  • ee/app/services/ci/sync_reports_to_approval_rules_service.rb - Threshold check + helper method
  • ee/lib/api/entities/approval_rule.rb - Entity exposure
  • ee/lib/api/helpers/project_approval_rules_helpers.rb - API parameters (create + update)
  • ee/spec/services/ci/sync_reports_to_approval_rules_service_spec.rb - Service tests
  • ee/spec/requests/api/project_approval_rules_spec.rb - API integration tests
  • doc/api/openapi/openapi_v2.yaml - Generated OpenAPI v2
  • doc/api/openapi/openapi_v3.yaml - Generated OpenAPI v3

Related to #6284 Extracted from !224343 (closed)

How to set up and validate locally

Prerequisites

1. Enable the feature flag

Feature.enable(:coverage_minimum_threshold)

2. Test the REST API

Replace <TOKEN> with your personal access token and <PROJECT_ID> with a project ID where you are a maintainer.

Create a coverage rule with threshold:

curl -s -X POST "http://gdk.local:3000/api/v4/projects/<PROJECT_ID>/approval_rules" \
  -H "PRIVATE-TOKEN: <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Coverage-Check", "approvals_required": 1, "report_type": "code_coverage", "coverage_minimum_threshold": 80.0}'

Expected response includes:

{
  "id": 123,
  "name": "Coverage-Check",
  "report_type": "code_coverage",
  "coverage_minimum_threshold": 80.0
}

Update the threshold:

curl -s -X PUT "http://gdk.local:3000/api/v4/projects/<PROJECT_ID>/approval_rules/<RULE_ID>" \
  -H "PRIVATE-TOKEN: <TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"coverage_minimum_threshold": 90.0}'

Expected: coverage_minimum_threshold is now 90.0 in the response.

Delete the rule (cleanup):

curl -s -X DELETE "http://gdk.local:3000/api/v4/projects/<PROJECT_ID>/approval_rules/<RULE_ID>" \
  -H "PRIVATE-TOKEN: <TOKEN>"

3. Verify service logic in Rails console

# The service checks threshold in require_coverage_approval?
# When coverage_minimum_threshold is set and pipeline coverage is below it,
# the approval rule will NOT have its approvals_required lowered to 0,
# meaning approval stays required.

# Verify the method exists and is gated:
service = Ci::SyncReportsToApprovalRulesService.new(Ci::Pipeline.last)
service.respond_to?(:execute, true)
# => true

4. Run the specs

bundle exec rspec ee/spec/services/ci/sync_reports_to_approval_rules_service_spec.rb \
  -e 'coverage_minimum_threshold'
# Expected: 11 examples, 0 failures

bundle exec rspec ee/spec/requests/api/project_approval_rules_spec.rb \
  -e 'coverage_minimum_threshold'
# Expected: 2 examples, 0 failures

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

  • The change is backwards compatible. When coverage_minimum_threshold is nil, the existing relative coverage check is unchanged.
  • Threshold check is gated behind coverage_minimum_threshold feature flag to protect the critical mergeability path.
  • Feature flag definition is introduced together with its first consumer to comply with the broken-master guideline.
  • No N+1 queries introduced. coverage_minimum_threshold_for uses pick (single value query).
  • API changes are additive (new optional parameter), no breaking changes.
  • API parameter descriptions standardized per reviewer feedback.

Related to #6284

Edited by Norman Debald

Merge request reports

Loading