Skip to content

Human-readable labels for Ad Hoc filters (keyLabel / DisplayNameFromDS)#106

Open
samjewell wants to merge 6 commits into
mainfrom
sj/display-name-from-ds
Open

Human-readable labels for Ad Hoc filters (keyLabel / DisplayNameFromDS)#106
samjewell wants to merge 6 commits into
mainfrom
sj/display-name-from-ds

Conversation

@samjewell

@samjewell samjewell commented Feb 8, 2026

Copy link
Copy Markdown
Collaborator

Human-readable labels for Ad Hoc filters (keyLabel / DisplayNameFromDS)

BLOCKED! ⚠️

Do not merge until Grafana PR #117640 has been reviewed and merged, and shipped

Context

Grafana is adding support for human-readable labels when adding ad hoc filters from panels (e.g. Bar Chart tooltips, Table cell “Filter for value”). See Grafana PR #117640. To make that work end-to-end, datasources need to:

  1. getTagKeys – return a human-readable label for each key so the Ad Hoc key dropdown shows e.g. “Order Status” instead of orders.status.
  2. Query result frames – set DisplayNameFromDS on fields so that when a user clicks “Filter for value” from a panel, Grafana can use it as keyLabel on the filter chip.

This PR adds both behaviours to the Cube datasource so the full flow is testable and so Cube users get friendly names in the UI.

What this PR does

  1. Metadata (getTagKeys)
    When the Cube /meta API provides a Title (or ShortTitle isn’t used here; we use Title) for a dimension or measure, we now use it as the metadata label. The frontend already maps metadata.dimensions[].label → Ad Hoc key picker text, so the key dropdown shows human-readable names. We keep value as the technical name (e.g. orders.status) for queries. If Title is empty, we fall back to the existing behaviour (label = name).

  2. Query frames (DisplayNameFromDS)
    When building frames from the Cube /load response, we set field.Config.DisplayNameFromDS from the annotation’s Title for each field (dimensions, measures, time dimensions). That lets Grafana show a human-readable key label when adding filters from Bar Chart tooltips or Table cell actions.

Breaking change?

No. Dashboard JSON and query shape are unchanged: queries and filters still use technical names (e.g. dimensions: ["orders.status"], member: "orders.status"). Only the UI (Ad Hoc key picker, filter chips, panel-originated filter labels) shows human-readable names. The metadata API still returns the same structure (label, value, type); when Cube provides a Title, we use it for label instead of the technical name. The value field is unchanged and remains the stable identifier.

Testing

  • Backend: go test ./pkg/plugin/ (includes TestExtractMetadataFromResponse, TestHandleMetadata, TestQueryDataWithCubeQuery with DisplayNameFromDS assertion).
  • Frontend: npm test -- --watchAll=false.

To manually test with Grafana PR 117640: run Grafana on the branch that includes the ad hoc filter label changes, build this plugin, load it, add a Cube datasource and an Ad Hoc filter variable, then use “Filter for value” from a Bar Chart or Table and confirm the filter chip shows the human-readable label (e.g. “Order Status”) instead of only the technical name.


Note

Low Risk
Label-only changes that affect UI display (label and DisplayNameFromDS) without altering query semantics or data handling; main risk is minor UI regressions if a Title is missing/unexpected.

Overview
Adds human-readable labels throughout the Cube datasource by using Cube-provided Title metadata.

Query result frames now set field.Config.DisplayNameFromDS from /load response annotations (dimensions/measures/timeDimensions) so panel-driven “Filter for value” and column headers show friendly names. The metadata extraction for Ad Hoc key selection now uses Title as the label (instead of the technical field name), and tests were updated/expanded (helpers, new assertion) to lock in the new labeling behavior.

Written by Cursor Bugbot for commit 0f641e9. This will update automatically on new commits. Configure here.

samjewell and others added 2 commits February 8, 2026 21:43
…icker

Use Cube dimension/measure Title as metadata label when present, so
getTagKeys returns human-readable text for the Ad Hoc variable key
dropdown. Fall back to Name when Title is empty.

Ref: Grafana PR 117640 (ad hoc filter labels)
Co-authored-by: Cursor <cursoragent@cursor.com>
Set field.Config.DisplayNameFromDS when building query result frames
so panels can show human-readable keyLabel when adding Ad Hoc filters
from Bar Chart tooltips or Table cell actions.

Ref: Grafana PR 117640 (ad hoc filter labels)
Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread pkg/plugin/datasource_test.go
@cursor

This comment has been minimized.

Comment thread pkg/plugin/datasource.go Outdated
Comment thread pkg/plugin/datasource.go Outdated
Comment thread pkg/plugin/datasource_test.go
Comment thread pkg/plugin/datasource.go Outdated
Use two separate checks so t.Fatalf never dereferences Config when nil.

Co-authored-by: Cursor <cursoragent@cursor.com>
samjewell and others added 3 commits February 9, 2026 16:28
- displayLabel now prefers Title over ShortTitle for query builder,
  Ad Hoc key picker, and frame DisplayNameFromDS
- Keeps labels unambiguous in flat lists; view nesting can follow later
- Tests updated to expect Title-based labels

Co-authored-by: Cursor <cursoragent@cursor.com>
- Use Title only for labels; remove ShortTitle from logic (struct fields kept for API unmarshalling)
- Add NewTestDimension/NewTestMeasure(name, title, typ) so test mocks always set Title
- Replace per-field three-map checks with annotationFieldMap + single lookup; use maps.Copy for merge
- Inline former displayLabel (was identity); update setDisplayNamesFromAnnotation comment (Cube always provides Title)

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is ON. A Cloud Agent has been kicked off to fix the reported issue.

Comment thread pkg/plugin/datasource.go
if !processedDimensions[dimension.Name] {
dimensions = append(dimensions, SelectOption{
Label: dimension.Name,
Label: dimension.Title,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No fallback to Name when Title is empty

Medium Severity

extractMetadataFromResponse uses dimension.Title and measure.Title directly as the Label without any fallback, while setDisplayNamesFromAnnotation defensively checks info.Title != "" before using it. If Title is ever empty (the schema marks it optional), the ad hoc filter dropdown would show empty labels instead of the previous behavior of showing the technical name. The old code used dimension.Name which is always populated.

Additional Locations (2)

Fix in Cursor Fix in Web

@cursor

cursor Bot commented Feb 10, 2026

Copy link
Copy Markdown

Bugbot Autofix prepared fixes for 1 of the 1 bugs found in the latest run.

  • ✅ Fixed: No fallback to Name when Title is empty
    • Added fallback logic in extractMetadataFromResponse to use dimension.Name and measure.Name when Title is empty, making it consistent with setDisplayNamesFromAnnotation's defensive check.

Create PR

Or push these changes by commenting:

@cursor push 3d5b2024ad
Preview (3d5b2024ad)
diff --git a/pkg/plugin/datasource.go b/pkg/plugin/datasource.go
--- a/pkg/plugin/datasource.go
+++ b/pkg/plugin/datasource.go
@@ -885,8 +885,12 @@
 		// Collect dimensions
 		for _, dimension := range item.Dimensions {
 			if !processedDimensions[dimension.Name] {
+				label := dimension.Title
+				if label == "" {
+					label = dimension.Name
+				}
 				dimensions = append(dimensions, SelectOption{
-					Label: dimension.Title,
+					Label: label,
 					Value: dimension.Name,
 					Type:  dimension.Type,
 				})
@@ -897,8 +901,12 @@
 		// Collect measures
 		for _, measure := range item.Measures {
 			if !processedMeasures[measure.Name] {
+				label := measure.Title
+				if label == "" {
+					label = measure.Name
+				}
 				measures = append(measures, SelectOption{
-					Label: measure.Title,
+					Label: label,
 					Value: measure.Name,
 					Type:  measure.Type,
 				})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant