Add open MR count measurement to DuoWorkflows engine

What does this MR do and why?

Adds a new measurement, open_mr_count, to the DuoWorkflows aggregation engine (Analytics::AggregationEngines::DuoWorkflows), alongside the existing created_mr_count, merged_mr_count and closed_mr_count measurements added in https://gitlab.com/gitlab-org/gitlab/-/issues/617064.

It counts merge requests created by an agentic flow that are still open (neither merged nor closed). It is computed at query time from existing columns - no migration, no new column, no new feature flag. It inherits the existing dap_impact_v1 feature flag gate on the whole engine. The aggregation framework exposes it in GraphQL automatically as openMrCount on DuoWorkflowsAggregationResponse, with a generated type DuoWorkflowsAggregationResponseOpenMrCountMetrics offering sum, min, max, mean and quantile(quantile: Float). No resolver or GraphQL type code was written, and no authorize: option is needed, matching the sibling MR count measurements (unlike credits_used, which has authorize: :read_agent_artifacts).

This is part of the DAP Impact dashboard work. Engineering leaders evaluating Duo Agent Platform want the full lifecycle of merge requests that agentic flows create. Today the data source exposes created, merged and closed counts but not "still open". A consumer has to fetch three numbers and subtract them client-side, which is error-prone, and cannot be done at all for per-flow distributions (min, max, mean, quantiles) - only for sums. This lets the dashboard show a complete created -> open/merged/closed funnel in a single query, and lets leaders spot backlogs of agent-created MRs that nobody is reviewing.

Why filter instead of subtract

The SQL is:

length(arrayFilter(mr_id -> NOT has(later_merged_merge_request_ids, mr_id) AND NOT has(later_closed_merge_request_ids, mr_id), created_merge_request_ids))

It filters the created MR ids rather than computing length(created) - length(merged) - length(closed). later_merged_merge_request_ids and later_closed_merge_request_ids are written by duo_workflows_workflows_enriched_mr_outcomes_mv, which refreshes every 12 hours, while created_merge_request_ids is recomputed by duo_workflows_workflows_enriched_delta_mv every 15 minutes and carries the old later_* values forward unchanged. If a created-MR link is deleted between refreshes, later_* can briefly hold an id no longer in created_merge_request_ids, and subtraction goes negative. The arrayFilter predicate is the same one the outcomes view itself uses to find unresolved MRs, so it can never drop below zero. The arrays hold a handful of ids per flow, so the cost difference is negligible.

Demonstration against ClickHouse, on rows where an outcome id is no longer present in the created list:

created merged closed by subtraction this MR
[21] [21,99] [] -1 0
[31,32] [31] [99] 0 1
[41,42,43] [41,42] [43] 0 0

Row 2 shows subtraction doesn't just go negative, it also undercounts: MR 32 is genuinely still open, but the stale id masks it.

Inherited staleness (not new, out of scope here)

An MR merged in the last 12 hours still counts as open until the outcomes view refreshes. That view only re-checks flows whose row changed in the last 60 days, so older untouched flows keep their last known state. This applies equally to merged_mr_count and closed_mr_count today. Making outcome tracking fresher, or extending the 60-day repair window, is deliberately out of scope for this MR.

References

Screenshots or screen recordings

Not applicable - this is a backend GraphQL measurement with no UI.

For reference, the closest equivalent is the GraphQL response shape:

query {
  group(fullPath: "gitlab-org") {
    analytics {
      duoWorkflows {
        aggregated {
          nodes {
            createdMrCount { sum }
            mergedMrCount { sum }
            closedMrCount { sum }
            openMrCount { sum min max mean quantile(quantile: 0.5) }
          }
        }
      }
    }
  }
}

How to set up and validate locally

Run the specs:

bundle exec rspec ee/spec/models/analytics/aggregation_engines/duo_workflows_spec.rb -e 'merge request count measurements'
bundle exec rspec ee/spec/requests/api/graphql/analytics/duo_workflows_spec.rb

Expected results: the first gives 4 examples, 0 failures; the second gives 59 examples, 0 failures.

To exercise it through the GraphQL API in GDK:

  • Enable the dap_impact_v1 feature flag (it is default_enabled: false).
  • The group needs the ai_analytics licensed feature (Premium/Ultimate), otherwise the request returns "The following sources are not accessible".
  • The duo_workflows_workflows_enriched ClickHouse table needs rows. Siphon does not replicate this table locally, so rows must be inserted by hand.

Specs added cover:

  • open_mr_count.sum alongside created/merged/closed in a single request
  • all five aggregates for open_mr_count in one request: min, max, mean, quantile, sum
  • a group-by case grouping by workflow_definition
  • openMrCount { sum } added to the GraphQL request spec, covering the monthly createdAt date bucket, in both the authorized case and the case where the user lacks the read_agent_artifacts custom ability

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Jatin Parihar

Merge request reports

Loading
Loading