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
- Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/629551
- Follows the pattern established by https://gitlab.com/gitlab-org/gitlab/-/issues/617064, which added the created/merged/closed measurements
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.rbExpected 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_v1feature flag (it isdefault_enabled: false). - The group needs the
ai_analyticslicensed feature (Premium/Ultimate), otherwise the request returns "The following sources are not accessible". - The
duo_workflows_workflows_enrichedClickHouse table needs rows. Siphon does not replicate this table locally, so rows must be inserted by hand.
Specs added cover:
open_mr_count.sumalongside created/merged/closed in a single request- all five aggregates for
open_mr_countin 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 monthlycreatedAtdate bucket, in both the authorized case and the case where the user lacks theread_agent_artifactscustom ability
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.