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 parameter error. GLQL passes the value through verbatim, the same authority the existing event filter 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 event parameter on TotalCount in ai_usage_events.rs mirrors the status parameter on DuoWorkflows from #215 (closed): optional, default [], up to 100 items.
  • Published in schema.json as { "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 carrying parameters: {event: [...]}, transform), slug escaping, the 100-item cap, duplicate-instance rules, and the parameter beside an event filter.

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 with Unterminated 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.rs gains a local compile_json helper mirroring the one tests/duo_workflows_tests.rs already has. Promoting a shared JSON-returning helper into test_helpers.rs touches 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)

Edited by Chandra Saripaka

Merge request reports

Loading
Loading