Loading
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:
timestampsupports the full daily/weekly/monthly granularity set with a weekly default. CodeSuggestions is monthly-only, but this engine accepts all three.- The
userfilter only accepts numeric IDs (converted togid://gitlab/User/N), same as CodeSuggestions. Usernames can't work until the engine grows auser_usernamefilter, tracked in gitlab#599750. featurefilter values pass through without client-side validation (same aslanguage/ideNamefor CodeSuggestions), so an unknown identifier returns empty results rather than an error. The live identifiers today includeagent_platform,mcp,duo_workflow,code_review,code_suggestions,chat— note it'schat, notduo_chat.- The engine also has a
featuresCountFrom/Tometric-range filter (a HAVING-style filter on aggregated rows). The engine requires thefeatures_countmetric 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:featuresCountin a query filter returns a clear error.
Example Usage
Build the Ruby extension once:
cd glql_rb
bundle install
bundle exec rake compileAI 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 descTest 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.toolCompiled 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 descTest 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.toolCompiled 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