Add openMrCount measurement to DuoWorkflows analytics
What does this MR do and why?
Adds the openMrCount measurement to the DuoWorkflows analytics source, the fourth object beside createdMrCount, mergedMrCount and closedMrCount, with its five stat leaves: openMrCountMin / Max / Mean / Sum / Quantile.
The DAP Impact v1 Work tab panel "Duo MRs by status" shows created, merged, closed and still-open MRs per Duo session. GLQL has exposed the first three since !514 (merged) (#206 (closed)). "Still open" was created - merged - closed on the client, which GLQL cannot express and which is wrong for anything but sums — you cannot subtract per-flow distributions (min, max, mean, quantiles).
The engine derives it by filtering the created merge request ids down to those absent from both outcome lists, rather than by subtracting counts, so it cannot go negative when the lists are briefly out of sync. It reads from the same reconciliation that feeds mergedMrCount and closedMrCount and shares their lag: a merge request merged in the last few hours can still be counted as open.
This follows exactly the pattern !514 (merged) used for the three existing MR-count measurements:
- One
Measurementconst insrc/analyzer/sources/duo_workflows.rs, returned frommeasurements() - Five
Fieldvariants insrc/types/field.rs, with their parse and display arms field_parameters():OpenMrCountQuantiletakes the sharedquantile_param(0.5)src/schema/schema.jsonregenerated; codegen and transform tests intests/duo_workflows_tests.rs
All metrics and sort fields come for free — valid_metrics() and valid_sort_fields() both chain measurements().fields(), and the sort identifier openMrCount.sum is derived by the shared Measurement helper.
Not to be confused with the Project field openMergeRequestsCount, which counts a project's open merge requests rather than one flow's. No parse or display collision; the key sets are disjoint.
Engine dependency: resolved
This needed gitlab!256408 (merged) (gitlab#629551), which adds the open_mr_count measurement to the DuoWorkflows aggregation engine. That merged on 2026-09-23 (merge commit fe63ec09), so this is no longer blocked.
openMrCount is now on the master GraphQL schema, and the contract matches what GLQL emits:
openMrCount: DuoWorkflowsAggregationResponseOpenMrCountMetrics
type DuoWorkflowsAggregationResponseOpenMrCountMetrics {
max: Int
mean: Float
min: Int
quantile(quantile: Float): Float
sum: Float
}graphql-validate passes: all 1694 compiled queries validate against the live master schema.
Example Usage
The full created → open/merged/closed funnel in one query, which is what the Work tab panel needs:
mode: analytics
query: type = DuoWorkflow and group = "gitlab-org" and created > -6M
dimensions: created(monthly)
metrics: createdMrCountSum, mergedMrCountSum, closedMrCountSum, openMrCountSumGenerated GraphQL
Taken from the compiled output of test_mr_count_sums_render_and_sort_side_by_side (DUMP_GRAPHQL=1 cargo test --test duo_workflows_tests). Each measurement renders its own object with its own aliased leaf, and the transform lifts them back to flat row keys:
query GLQL($before: String, $after: String, $limit: Int) {
project(fullPath: "gitlab-org/gitlab") {
analytics {
duoWorkflows {
aggregated(before: $before, after: $after, first: $limit) {
count
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
nodes {
dimensions {
createdAt(granularity: "monthly")
}
createdMrCount {
createdMrCountSum: sum
}
mergedMrCount {
mergedMrCountSum: sum
}
closedMrCount {
closedMrCountSum: sum
}
openMrCount {
openMrCountSum: sum
}
}
}
}
}
}
}Sorting on a stat resolves to the dotted engine identifier:
aggregated(before: $before, after: $after, first: $limit, orderBy: [{direction: DESC, identifier: "openMrCount.sum"}])The end-to-end ruby-extension snippet is omitted because the engine change merged to master only today and is not necessarily deployed to gitlab.com yet. Running the query against gitlab.com also needs the dap_impact_v1 feature flag and the ai_analytics licensed feature, so it is not reproducible from a plain shell in any case.
How to set up and validate locally
# 1. lint and format
cargo fmt -- --check
cargo clippy --all-targets -- -D warnings
# 2. the three guards this change actually relies on
cargo test --lib measurement # test_registered_measurements_follow_the_naming_rule
cargo test --test derivable_apis_tests # canonical_field_names_parse_back_to_themselves
cargo test --test duo_workflows_tests
# 3. the schema document is generated; confirm it is in sync
cargo run --bin generate-schema
npx prettier --check src/schema/schema.json
# 4. full suite
cargo testAll green locally: 1817 tests pass, 0 failures. Expected schema.json diff is three additive hunks in the duoWorkflows source: five entries appended to sort_fields, five to metrics, and one openMrCountQuantile object in parameterized_fields.
Reviewer notes
archlinux:rubyandruby-headfail, and are unrelated to this change. Both areallow_failure: true, so the pipeline is green overall ("passed with warnings").archlinux:rubyfails compiling themagnuscrate (unresolved import rb_sys::rb_data_type_struct__bindgen_ty_1) and carries a standingTODO: Replace with stable distro job for reliable buildspointing at #111;ruby-headfails onNameError: uninitialized constant Pathname::SEPARATOR_PATfrom Ruby master's stdlib. This MR touches no Ruby and no FFI surface.- Adding the five
Fieldvariants produces no compile errors — everymatchoverFieldhas a catch-all arm. The guards above are the only thing that catches a half-finished change, so they are worth running rather than trusting the build. - The new
MeasurementCaseusesdimension: "group"on purpose. The three existing cases usecreated,flowTypeandstatus, all scalar shapes;groupis the object-valued parameterised dimension, the one shape no measurement was yet paired with. FieldderivesOrdfrom declaration order, so inserting the variants mid-enum shifts later discriminants. Nothing depends on the numeric values (serde uses variant names), but it is a reason to read the schema diff rather than skim it.- The transform fixture uses
7.5foropenMrCountSumagainst31.5created,18.0merged and6.0closed, so the row stays arithmetically coherent with what the measurement means.
After merge
This needs a glql release and a version bump in gitlab-org/gitlab before the Work tab panel can use it. The monolith is already current at 0.39.0 (bumped in gitlab@200ed590), so the bump after this merges only has to carry the next release.
Closes #220 (closed)