feat: anchor fixed-day buckets with an optional origin parameter
What does this MR do and why?
Every analytics date dimension takes an optional origin beside granularity: the instant the engine counts fixed-day buckets from.
mode: analytics
query: type = mergerequest and group = "gitlab-org" and mergedAt > -60d
dimensions: merged(granularity="30d", origin="2026-07-16T00:00:00Z")
metrics: totalCount
sort: merged ascWithout an origin, 30d buckets are counted from the Unix epoch and rarely line up with a query's date range. Anchoring them is what makes "the last 30 days vs. the 30 before" a two-row comparison, the building block the DAP Impact Dashboard needs (#187 (closed), second half; the Xd granularity itself is !528 (merged)).
originvalue: an ISO 8601 date or date-time. A date can be written bare like the dates in filters (origin=2026-07-16); a date-time needs quotes.2026-07-16,"2026-07-16T00:00:00Z"and"2026-07-16T02:00:00+02:00"are one instant and compile to one canonical"2026-07-16T00:00:00Z", so they share a GraphQL selection and a response-key slug. Relative spellings such as-60dortoday()are not accepted; #230 tracks them.- Rule:
originneeds a fixed-day granularity. The definition declares that as a prerequisite ongranularity, the analyzer checks prerequisites generically, and the schema document publishes it underrequires.created(granularity=weekly, origin="2026-07-16"), orcreated(origin=...)with a calendar default, is a compile error naming both parameters and the value rather than the engine's message. - Optional parameters: a parameter can now be declared optional, meaning it is simply not sent when the query leaves it out. That is a third state next to "has a default" and "required". A bare
createdstill compiles tocreatedAt(granularity: "weekly")alone. The schema document publishes such a parameter with"optional": trueand nodefault, and describes the newTimestampkind undervalue_kinds. - Positional shorthand: one positional argument binds to the first parameter, so
created(weekly)andcreated(30d)keep working now thatcreateddeclares two parameters. Two positional arguments on such a field keep the existing "use named syntax" error, since there is no positional order fororigin. - Sorting on an anchored dimension carries both parameters into
orderBy. The slug escape scheme gains:(as_c) so an aliased anchored bucket keeps a lossless response key. - Shared declaration: every date dimension on every source declares the same
granularity+originpair through one shared definition.
Backend status
origin needs GitLab 19.5 (gitlab!254135 (merged) and gitlab!254973 (merged)). Sorting on an anchored bucket needs the orderBy parameter coercion fix in gitlab!253703 (merged) (19.4).
On gitlab.com today, origin on a nullable date column returns HTTP 500 unless the query also filters on that column: pipelines started/finished, merge requests merged, agent platform sessions created. With a filter such as started > -60d the same query succeeds. Non-null columns (merge requests created, code suggestions timestamp, Duo workflows created, contributions created) work with or without a filter. Tracked in gitlab#629599 (closed); nothing on the GLQL side changes when it is fixed.
User docs in gitlab-org/gitlab follow with the package bump.
How to set up and validate locally
cargo test
DUMP_GRAPHQL=1 cargo test && npm run test:graphqlExample Usage
cd glql_rb
bundle install
bundle exec rake compileDuo workflow sessions in 28-day buckets anchored at a release date
mode: analytics
query: type = duoworkflow and group = "gitlab-org" and created > -56d
dimensions: created(granularity="28d", origin="2026-07-16")
metrics: totalCount, usersCount
sort: created descTest via ruby extension
cd glql_rb # after the build prerequisite above
bundle exec ruby -Ilib - <<'RUBY'
require "gitlab_query_language"
require "json"
require "net/http"
query = "type = duoworkflow and group = \"gitlab-org\" and created > -56d"
context = { mode: "analytics", dimensions: "created(granularity=\"28d\", origin=\"2026-07-16\")", metrics: "totalCount, usersCount", sort: "created desc" }
compiled = Glql.compile(query, context)
puts "== Generated GraphQL =="
puts compiled["output"]
uri = URI("https://gitlab.com/api/graphql")
http = Net::HTTP.new(uri.host, uri.port); http.use_ssl = true
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")
req["PRIVATE-TOKEN"] = ENV.fetch("GITLAB_TOKEN")
req.body = { query: compiled["output"], variables: { limit: 4 } }.to_json
response = JSON.parse(http.request(req).body)
transformed = Glql.transform(response["data"], { mode: context[:mode], fields: compiled["fields"] })
puts "\n== Transformed rows =="
puts JSON.pretty_generate(transformed["data"])
RUBYquery GLQL($before: String, $after: String, $limit: Int) {
group(fullPath: "gitlab-org") {
analytics {
duoWorkflows(createdAtFrom: "2026-07-24 23:59") {
aggregated(before: $before, after: $after, first: $limit, orderBy: [{direction: DESC, identifier: "createdAt", parameters: {granularity: "28d", origin: "2026-07-16T00:00:00Z"}}]) {
count
pageInfo { startCursor endCursor hasNextPage hasPreviousPage }
nodes {
dimensions {
createdAt(granularity: "28d", origin: "2026-07-16T00:00:00Z")
}
totalCount
usersCount
}
}
}
}
}
}{
"count": 3,
"nodes": [
{ "usersCount": 854, "totalCount": 37580, "created": "2026-09-10" },
{ "usersCount": 1126, "totalCount": 113722, "created": "2026-08-13" },
{ "usersCount": 1018, "totalCount": 77627, "created": "2026-07-16" }
]
}The buckets start on the origin and every 28 days after it.
Merge requests created per 30-day window, aliased
mode: analytics
query: type = mergerequest and group = "gitlab-org" and created > -60d
dimensions: created(granularity=30d, origin="2026-07-16T00:00:00Z") as "Period"
metrics: totalCount
sort: created ascTest via ruby extension
Same snippet as above with this query and context. Generated selection and rows:
mergeRequests(createdAtFrom: "2026-07-20 23:59") {
aggregated(before: $before, after: $after, first: $limit, orderBy: [{direction: ASC, identifier: "createdAt", parameters: {granularity: "30d", origin: "2026-07-16T00:00:00Z"}}]) {
nodes {
dimensions {
createdAt_granularity_30d_origin_2026_m07_m16T00_c00_c00Z: createdAt(granularity: "30d", origin: "2026-07-16T00:00:00Z")
}
totalCount
}
}
}{
"count": 3,
"nodes": [
{ "totalCount": 10531, "Period": "2026-07-16" },
{ "totalCount": 14603, "Period": "2026-08-15" },
{ "totalCount": 3160, "Period": "2026-09-14" }
]
}Compile errors
dimensions: created(granularity=weekly, origin="2026-07-16")`created(origin=...)` needs `granularity` to be a fixed-day granularity such as `30d`, not a calendar bucket (`daily`, `weekly`, `monthly`); `weekly` is not.dimensions: created(origin=2026-07-16)`created(origin=...)` needs `granularity` to be a fixed-day granularity such as `30d`, not a calendar bucket (`daily`, `weekly`, `monthly`); the default `weekly` is not.dimensions: created(granularity=30d, origin=-60d)`-60d` is not a valid value for `created(origin=...)`. Expected an ISO 8601 date or date-time, for example 2026-07-16 or 2026-07-16T00:00:00Zdimensions: created(30d, "2026-07-16")`created` accepts multiple parameters; use named syntax: `created(granularity=..., origin=...)`Related issues
Related to #187 (closed) Related to gitlab#629599 (closed)