Render GLQL date buckets as date-only labels in tables and lists
What does this MR do and why?
Resolves glql#200 (closed)
In analytics mode, date bucket dimensions like created(monthly) rendered as relative time ("1 month ago") in table and list views, because no presenter matched them. This MR adds a DateBucketPresenter that formats bucketed date strings properly, and routes fields with a granularity parameter to it instead of falling through to the relative-time presenter.
Changes
- New
DateBucketPresenter(app/assets/javascripts/glql/components/presenters/date_bucket.vue) formats bucketed date strings for table and list views. - New shared util
app/assets/javascripts/glql/utils/date_bucket.jsholdsbucketDateOfandformatBucketDate, moved out ofchart_data.js.dimensionLabelFormatterinchart_data.jsnow delegates to it. presenter_registry.js:presenterFortakes aparametersoption and routes string values with agranularitytoDateBucketPresenter, ahead of the field-key, typename, and primitive dispatch.field.vuegets a newparametersprop (defaultundefined), forwarded to the presenter.table.vueandlist.vuepassfield.parametersthrough.presenter_registry.js:dataForFieldnow takespresenterKeyand compares the base field key (not the alias) for the title shortcut.
Design decisions
- Cells always show the year; charts drop it unless the series spans multiple years, since the axis carries that context and a cell doesn't. The work item's weekly example ("13 - 19 Jul") renders as "13 - 19 Jul 2026" here. Open to pushback on this.
- Routing checks for a string value plus a
granularityparameter, not whether the value parses as a bucket date. If a bucket start ever stops parsing, the presenter falls back to showing the raw value in both tables and charts, rather than silently regressing to relative time. A hypothetical non-date field with agranularitystill gets regular dispatch for number or object values. WDYT? FieldPresenterforwardsparametersto every presenter, but onlyDateBucketPresenterdeclares the prop. On other parameterised fields (e.g.durationQuantilewith{ quantile: 0.5 }), it lands as a harmlessparameters="[object Object]"attribute.itemalready falls through the same way on every data-only presenter today, so guardingparametersalone felt inconsistent - gating both is a possible follow-up if reviewers prefer.
Also fixed: aliasing a bucket field as title (e.g. created(weekly) as "title") was matching the alias instead of the base field key, sending the whole row to LinkPresenter.
Standard-mode timestamps without a granularity (e.g. a work item's created field) still show relative time, unchanged.
Found while working on !254823 (merged). Related earlier decision on chart date formatting: !245952 (merged).
Screenshots or screen recordings
Local GDK data (AI usage events): timestamp(monthly) table, timestamp(weekly) list, timestamp(daily) table.
| Before | After |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Standard mode is unchanged (relative time stays):
How to set up and validate locally
On a GDK with ClickHouse and analytics data for a group, embed GLQL blocks on any issue or wiki page. Local GDK ClickHouse usually has no pipeline or merge request rows, so the examples below use AI usage events - any analytics source with a date bucket dimension behaves the same, for example finished(weekly) on pipelines or created(monthly) on merge requests.
display: table
mode: analytics
query: type = AiUsageEvent and group = "gitlab-org" and timestamp > -365d
dimensions: timestamp(monthly)
metrics: totalCountdisplay: list
mode: analytics
query: type = AiUsageEvent and group = "gitlab-org" and timestamp > -365d
dimensions: timestamp(weekly)
metrics: totalCountMR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
- I have evaluated the MR acceptance checklist for this MR.






