feat(ai_usage_events): add analytics analyzer for the duoUsageEvents engine

What does this MR do and why?

Adds GLQL analytics-mode support for the AiUsageEvent source, backed by the duoUsageEvents aggregation engine mounted under analytics in the monolith.

Supported surface:

GLQL Kind GraphQL
feature dimension feature
event dimension event
user dimension user { id, username, name, avatarUrl, webUrl }
timestamp dimension timestamp(granularity:) — daily/weekly/monthly, default weekly
totalCount metric totalCount
usersCount metric usersCount
featuresCount metric featuresCount
returningUsersCount metric returningUsersCount
previousPeriodUsersCount metric previousPeriodUsersCount
feature, event filter feature: [String!], event: [String!]
user filter userId: [String!] (numeric IDs converted to GIDs)
timestamp filter timestampFrom/To

Implementation notes:

  • timestamp supports the full daily/weekly/monthly granularity set with a weekly default. CodeSuggestions is monthly-only, but this engine accepts all three.
  • The user filter only accepts numeric IDs (converted to gid://gitlab/User/N), same as CodeSuggestions. Usernames can't work until the engine grows a user_username filter, tracked in gitlab#599750.
  • feature filter values pass through without client-side validation (same as language/ideName for CodeSuggestions), so an unknown identifier returns empty results rather than an error. The live identifiers today include agent_platform, mcp, duo_workflow, code_review, code_suggestions, chat — note it's chat, not duo_chat.
  • The engine also has a featuresCountFrom/To metric-range filter (a HAVING-style filter on aggregated rows). The engine requires the features_count metric to be selected whenever it's used, and the compiler has no filter-requires-metric validation yet, so that's coming in a follow-up MR stacked on this branch. There's a test pinning the current behaviour: featuresCount in a query filter returns a clear error.

Example Usage

Build the Ruby extension once:

cd glql_rb
bundle install
bundle exec rake compile

AI feature usage by week (gitlab-org, last 90 days)

mode: analytics
query: type = AiUsageEvent and group = "gitlab-org" and timestamp >= -90d
dimensions: feature, timestamp(weekly)
metrics: totalCount, usersCount
sort: totalCount desc
Test via ruby extension
GLQL_QUERY='type = AiUsageEvent and group = "gitlab-org" and timestamp >= -90d'

echo "q = Glql.compile('${GLQL_QUERY}', { mode: 'analytics', dimensions: 'feature, timestamp(weekly)', metrics: 'totalCount, usersCount', sort: 'totalCount desc' })[\"output\"]; puts q; File.write('/tmp/glql_query.json', { query: q, variables: { limit: 5 } }.to_json); exit" | bundle exec rake console && curl -s -X POST https://gitlab.com/api/graphql -H "Content-Type: application/json" -H "Authorization: Bearer $GITLAB_TOKEN" -d @/tmp/glql_query.json | python3 -m json.tool

Compiled query:

query GLQL($before: String, $after: String, $limit: Int) {
  group(fullPath: "gitlab-org") {
    analytics {
      duoUsageEvents(timestampFrom: "2026-04-30 00:00") {
        aggregated(before: $before, after: $after, first: $limit, orderBy: [{direction: DESC, identifier: "totalCount"}]) {
          count
          nodes {
            dimensions { feature timestamp(granularity: "weekly") }
            totalCount
            usersCount
          }
        }
      }
    }
  }
}

Response (count: 113, trimmed to 5 nodes):

{ "data": { "group": { "analytics": { "duoUsageEvents": { "aggregated": {
  "count": 113,
  "nodes": [
    { "dimensions": { "feature": "agent_platform", "timestamp": "2026-06-22" }, "totalCount": 84114, "usersCount": 837 },
    { "dimensions": { "feature": "agent_platform", "timestamp": "2026-07-06" }, "totalCount": 82818, "usersCount": 786 },
    { "dimensions": { "feature": "agent_platform", "timestamp": "2026-06-15" }, "totalCount": 81316, "usersCount": 831 },
    { "dimensions": { "feature": "agent_platform", "timestamp": "2026-07-13" }, "totalCount": 81174, "usersCount": 778 },
    { "dimensions": { "feature": "agent_platform", "timestamp": "2026-05-18" }, "totalCount": 75784, "usersCount": 810 }
  ] } } } } } }

Top Duo Chat users (gitlab-org, last 30 days)

mode: analytics
query: type = AiUsageEvent and group = "gitlab-org" and feature = "chat" and timestamp >= -30d
dimensions: user
metrics: totalCount
sort: totalCount desc
Test via ruby extension
GLQL_QUERY='type = AiUsageEvent and group = "gitlab-org" and feature = "chat" and timestamp >= -30d'

echo "q = Glql.compile('${GLQL_QUERY}', { mode: 'analytics', dimensions: 'user', metrics: 'totalCount', sort: 'totalCount desc' })[\"output\"]; puts q; File.write('/tmp/glql_query.json', { query: q, variables: { limit: 5 } }.to_json); exit" | bundle exec rake console && curl -s -X POST https://gitlab.com/api/graphql -H "Content-Type: application/json" -H "Authorization: Bearer $GITLAB_TOKEN" -d @/tmp/glql_query.json | python3 -m json.tool

Compiled query:

query GLQL($before: String, $after: String, $limit: Int) {
  group(fullPath: "gitlab-org") {
    analytics {
      duoUsageEvents(feature: ["chat"], timestampFrom: "2026-06-29 00:00") {
        aggregated(before: $before, after: $after, first: $limit, orderBy: [{direction: DESC, identifier: "totalCount"}]) {
          count
          nodes {
            dimensions { user { id avatarUrl username name webUrl } }
            totalCount
          }
        }
      }
    }
  }
}

Transformed rows (count: 83, trimmed to 5):

[
  { "totalCount": 276, "user": { "id": "gid://gitlab/User/480097", "username": "alexives", "name": "Alex Ives", "webUrl": "https://gitlab.com/alexives" } },
  { "totalCount": 176, "user": { "id": "gid://gitlab/User/29580426", "username": "official.harshith1", "name": "Harshith Sudar", "webUrl": "https://gitlab.com/official.harshith1" } },
  { "totalCount": 116, "user": { "id": "gid://gitlab/User/28008286", "username": "gitlab-qa-user-for-webide", "name": "WebIDE Test User (DevEx Team's Test Account)", "webUrl": "https://gitlab.com/gitlab-qa-user-for-webide" } },
  { "totalCount": 54, "user": { "id": "gid://gitlab/User/32334524", "username": "JoannaNiemczyk", "name": "Joanna Niemczyk", "webUrl": "https://gitlab.com/JoannaNiemczyk" } },
  { "totalCount": 52, "user": { "id": "gid://gitlab/User/20978192", "username": "Saahmed", "name": "Safwan Ahmed", "webUrl": "https://gitlab.com/Saahmed" } }
]

Related engine implementation: gitlab#589610 (closed), gitlab#592278 (closed)

Closes #94 (closed)

Edited by Robert Hunt

Merge request reports

Loading
Loading