feat(ai_usage_events): add the event parameter to totalCount
What
Adds an optional, list-valued event parameter to totalCount on the AiUsageEvent analytics source, so one query can carry several event-filtered counts as aliased instances. It compiles to the totalCount(event: [String!]) argument that gitlab!257198 (merged) added to the AI usage events aggregation engine (merged 2026-09-24).
mode: analytics
query: type = AiUsageEvent and group = "my-group" and timestamp > -30d
dimensions: timestamp(weekly)
metrics: totalCount(event=["request_review_duo_code_review_on_mr_by_author", "request_review_duo_code_review_on_mr_by_non_author"]) as "requested", totalCount(event=["publish_duo_code_review_comments", "find_no_issues_duo_code_review_after_review"]) as "published", totalCount(event="publish_duo_code_review_comments") as "with comments", totalCount(event="find_no_issues_duo_code_review_after_review") as "without comments"compiles to
aggregated { nodes {
totalCount_event_request_ureview_uduo_ucode_ureview_uon_umr_uby_uauthor_0request_ureview_uduo_ucode_ureview_uon_umr_uby_unon_uauthor: totalCount(event: ["request_review_duo_code_review_on_mr_by_author", "request_review_duo_code_review_on_mr_by_non_author"])
...
} }and the transform lands each count under its alias. Scalar sugar and list form both work. A bare totalCount, totalCount() or totalCount(event=[]) compiles exactly as before.
How
The list-item vocabulary for field parameters had only Enum and Int. This adds a third kind, ListItem::Str:
- Items are unconstrained strings, not an enum. The engine owns the event registry, which grows with every release, and rejects an unknown name with its own
Invalid value(s) for parametererror. GLQL passes the value through verbatim, the same authority the existingeventfilter defers to. An unknown or wrong-case name compiles and the engine answers. - Items must be non-empty and drawn from ASCII letters, digits,
.,-,+,_,:. That is exactly the set the response-key sanitiser encodes injectively, so two distinct parameter values can never collide on one generated response key, and no"or\can reach the GraphQL string literal codegen emits. One property test pins both that the charset equals the sanitiser's non-lossy set and that the sanitiser is injective over it. - The
eventparameter onTotalCountinai_usage_events.rsmirrors thestatusparameter on DuoWorkflows from #215 (closed): optional, default[], up to 100 items. - Published in
schema.jsonas{ "kind": "List", "items": [{ "kind": "String" }] }; the conformance sweep samples the new item kind.
Tests
field_parameter.rs: the new kind and its const-time checks.analytics.rs: rstest tables for query-time validation (resolution, empty list, every rejection class).builder.rs: the published shape.display_field.rs: the charset-to-sanitiser coupling and injectivity property test.parser_tests.rs: pins that a zero-length""literal is reported as unterminated (pre-existing grammar behaviour, see below).ai_usage_events_tests.rs: 13 integration tests, including the four-alias code-review panel above end to end (compile, sort by alias carryingparameters: {event: [...]}, transform), slug escaping, the 100-item cap, duplicate-instance rules, and the parameter beside aneventfilter.
Full suite: 1853 tests, 0 failures. cargo clippy --all-targets -- -D warnings and cargo fmt --check clean. cargo run --bin generate-schema reproduces the committed schema byte for byte.
Not in this MR
- The docs table in gitlab-org/gitlab lands with the gem bump after release.
- A zero-length
""literal has never parsed anywhere in GLQL (the string grammar fails withUnterminated string); the empty-item guard is reached through a whitespace-only literal, since the parser trims quoted content. The grammar is out of scope; a parser test pins the current behaviour so a change there is noticed. tests/ai_usage_events_tests.rsgains a localcompile_jsonhelper mirroring the onetests/duo_workflows_tests.rsalready has. Promoting a shared JSON-returning helper intotest_helpers.rstouches the other suite's twenty call sites and is left for a follow-up.
CI note
graphql-validate fetches the schema dump from gitlab master, where the engine argument has been present since 2026-09-24.
Closes #226 (closed)