Long-running integration branch: experimental → main#352
Conversation
…141) (#351) * feat: show visual editor alongside read-only JSON for unsupported features Previously, when a query contained features the visual builder cannot represent (time dimensions, AND/OR filter groups, advanced operators, template-variable filter values), the entire visual editor was replaced by a read-only JSON viewer, leaving users unable to edit anything visually. This makes the layout hybrid: the visual editor is always rendered, and when unsupported features are detected, a compact collapsible JSON callout (UnsupportedFieldsViewer) shows only the unsupported top-level keys. - Always render the visual editor; show UnsupportedFieldsViewer additively - New UnsupportedFieldsViewer replaces JsonQueryViewer (now removed); it shows only unsupported keys (via getUnsupportedQueryKeys), collapsed by default - SQLPreview is now collapsed by default behind a "SQL Preview" toggle and rendered once at the top level regardless of supported state - Filters label gains a compact tooltip; the visual FilterField only receives equals/notEquals filters so advanced operators no longer leak into it - onFiltersChange merges visual-builder edits back with non-visual filters (AND/OR groups, advanced operators, template-variable filters) so they are no longer silently dropped Reimplements the intent of the now-closed #141 cleanly on top of current main. Co-authored-by: Cursor <cursoragent@cursor.com> * test(e2e): expand collapsed SQL preview before asserting The SQL preview is now collapsed by default behind a "SQL Preview" toggle, so the Playwright specs must click the toggle before the generated SQL and "Edit SQL in Explore" button become visible. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Template variable filters dropped
- Fixed by excluding template variable filters from FilterField and preserving them in onFiltersChange via the new filterHasTemplateVariables check.
Or push these changes by commenting:
@cursor push 49471fd43b
Preview (49471fd43b)
diff --git a/src/components/QueryEditor.tsx b/src/components/QueryEditor.tsx
--- a/src/components/QueryEditor.tsx
+++ b/src/components/QueryEditor.tsx
@@ -11,7 +11,7 @@
import { FilterField } from './FilterField/FilterField';
import { useQueryEditorHandlers } from '../hooks/useQueryEditorHandlers';
import { buildCubeQueryJson } from '../utils/buildCubeQuery';
-import { detectUnsupportedFeatures, getUnsupportedQueryKeys } from '../utils/detectUnsupportedFeatures';
+import { detectUnsupportedFeatures, getUnsupportedQueryKeys, filterHasTemplateVariables } from '../utils/detectUnsupportedFeatures';
import { decorateWithViewSelection, getViewSelectionState } from '../utils/viewSelection';
import { UnsupportedFieldsViewer } from './UnsupportedFieldsViewer';
@@ -216,7 +216,8 @@
>
<FilterField
filters={query.filters?.filter(
- (f): f is CubeFilter => isCubeFilter(f) && VISUAL_BUILDER_OPERATORS.has(f.operator)
+ (f): f is CubeFilter =>
+ isCubeFilter(f) && VISUAL_BUILDER_OPERATORS.has(f.operator) && !filterHasTemplateVariables(f)
)}
dimensions={dimensionOptions}
onChange={onFiltersChange}
diff --git a/src/hooks/useQueryEditorHandlers.ts b/src/hooks/useQueryEditorHandlers.ts
--- a/src/hooks/useQueryEditorHandlers.ts
+++ b/src/hooks/useQueryEditorHandlers.ts
@@ -1,6 +1,7 @@
import type { TQueryOrderArray } from '@cubejs-client/core';
import { ChangeEvent } from 'react';
import { CubeQuery, CubeFilter, CubeFilterItem, Order, DEFAULT_ORDER, VISUAL_BUILDER_OPERATORS, isCubeFilter } from '../types';
+import { filterHasTemplateVariables } from '../utils/detectUnsupportedFeatures';
import { SelectableValue } from '@grafana/data';
import { normalizeOrder } from '../utils/normalizeOrder';
@@ -91,7 +92,10 @@
// non-visual-builder filters (advanced operators, AND/OR groups,
// template-variable filters) so editing in the visual builder doesn't drop them.
const nonVisualFilters = (query.filters ?? []).filter(
- (f: CubeFilterItem) => !isCubeFilter(f) || !VISUAL_BUILDER_OPERATORS.has(f.operator)
+ (f: CubeFilterItem) =>
+ !isCubeFilter(f) ||
+ !VISUAL_BUILDER_OPERATORS.has(f.operator) ||
+ filterHasTemplateVariables(f)
);
const merged = [...filters, ...nonVisualFilters];
updateQueryAndRun({ filters: merged.length > 0 ? merged : undefined });
diff --git a/src/utils/detectUnsupportedFeatures.ts b/src/utils/detectUnsupportedFeatures.ts
--- a/src/utils/detectUnsupportedFeatures.ts
+++ b/src/utils/detectUnsupportedFeatures.ts
@@ -1,8 +1,16 @@
-import { CubeFilterItem, CubeQuery, VISUAL_BUILDER_OPERATORS, isCubeFilter, isCubeAndFilter, isCubeOrFilter } from '../types';
+import { CubeFilter, CubeFilterItem, CubeQuery, VISUAL_BUILDER_OPERATORS, isCubeFilter, isCubeAndFilter, isCubeOrFilter } from '../types';
-const TEMPLATE_VARIABLE_PATTERN = /(?:\$(?:[a-zA-Z_]\w*|\{[a-zA-Z_]\w*(?::[^}]+)?\})|\[\[[^\]]+\]\])/;
+export const TEMPLATE_VARIABLE_PATTERN = /(?:\$(?:[a-zA-Z_]\w*|\{[a-zA-Z_]\w*(?::[^}]+)?\})|\[\[[^\]]+\]\])/;
/**
+ * Returns true if the given flat CubeFilter contains a template variable
+ * (e.g. $var, ${var}, ${var:raw}, or [[var]]) in any of its values.
+ */
+export function filterHasTemplateVariables(filter: CubeFilter): boolean {
+ return filter.values?.some((v) => TEMPLATE_VARIABLE_PATTERN.test(v)) ?? false;
+}
+
+/**
* Detects query features that the visual builder cannot represent.
*
* Uses a blocklist approach: we check for specific patterns we know areYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 8333314. Configure here.
| (f: CubeFilterItem) => !isCubeFilter(f) || !VISUAL_BUILDER_OPERATORS.has(f.operator) | ||
| ); | ||
| const merged = [...filters, ...nonVisualFilters]; | ||
| updateQueryAndRun({ filters: merged.length > 0 ? merged : undefined }); |
There was a problem hiding this comment.
Template variable filters dropped
High Severity
Hybrid mode shows equals/notEquals filters (including dashboard variable values) in the visual FilterField, but onFiltersChange only re-attaches advanced operators and AND/OR groups as non-visual filters. Clearing or replacing visual filters removes template-variable filters from the saved query even though they are flagged unsupported elsewhere.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8333314. Configure here.
A dashboard-variable filter (e.g. {operator: equals, values: ['$var']})
uses a visual operator, so it was both handed to the visual FilterField
and excluded from onFiltersChange's preserved non-visual set. FilterField
can't render a $var value as a selectable chip, so editing any filter in
the visual builder could silently drop it.
- Exclude template-variable filters from FilterField's input so they're
never echoed back by its onChange (avoids dropping and avoids duplication)
- Preserve them in onFiltersChange regardless of operator
- Export a shared TEMPLATE_VARIABLE_PATTERN + filterValuesContainTemplateVariable
helper from detectUnsupportedFeatures so detection and filter handling agree
- Add unit tests covering $var, ${var:format}, and [[var]] syntaxes
Fixes the Cursor Bugbot finding on #351.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
This deploy to the Ops environment (catalog) from this branch worked fine: And the plugin itself at version This image happens to show the feature which is already on this feature branch, and the screenshot was taken from our Ops instance. So this works as a way to get plugin improvements merged to a feature branch, then built and deployed to our ops env. Not sure exactly how we can reference the build artifacts from a grafana instance outside our Ops hosted-grafana environment though - that's another question, and something we might need to look into. cc @adrapereira |
|
|




Purpose — long-running integration branch
This is a standing, long-lived PR. It is intentionally kept open and is not meant to be merged ad-hoc. It serves as the integration point between our fast-iteration
experimentalbranch andmain.How we work with
experimentalexperimental(notmain).main) only — see rulesetPublic repo required PRs(~DEFAULT_BRANCH).experimentalis deployed to theopsenvironment via the existingPlugins - CD(publish.yaml)workflow_dispatch, which explicitly supports deploying non-mainbranches. This lets us combine several in-flight PRs into one iterable build and collaborate without clobbering each other's dev/ops deploys.Why this preserves the org control
Everything that reaches customers / releases / the plugin catalogue still flows exclusively through
main, and this PR is the gate: promotingexperimental → mainhappens here and requires the normal human code review (1 approval, enforced by the org ruleset that cannot be bypassed). Somainprotection is fully intact — we've only moved fast-iteration/non-prod deploys onto a long-lived branch instead of short-lived ones.Current contents
When we want to promote, we review the accumulated diff here and merge. Until then, this PR stays open as the running ledger of what's ahead of
mainonexperimental.Note
Do not squash/auto-merge this PR casually. It is a deliberate human-review checkpoint.
Made with Cursor
Note
Medium Risk
Changes query editor behavior and filter merge logic for mixed visual/JSON queries; mistakes could drop or mishandle advanced filters, though new hook tests cover preservation.
Overview
Replaces the full JSON-only query editor with a hybrid layout: the visual builder always renders, and queries with unsupported Cube features (e.g. time dimensions, advanced filters) also show a compact Additional query configuration callout via
UnsupportedFieldsViewer, which lists reasons and exposes only unsupported query keys in collapsible JSON (not the whole query).JsonQueryVieweris removed.QueryEditor hoists SQL compilation and SQL Preview to the top level (one preview for all modes). SQL Preview is collapsed by default behind a toggle; unit and e2e tests expand it before asserting on SQL.
The visual Filters UI is limited to equals/notEquals dimension filters (
VISUAL_BUILDER_OPERATORS); advanced filters and AND/OR groups stay in query state but are hidden fromFilterField.onFiltersChangemerges visual filter edits with preserved non-visual filters so JSON-defined filters are not dropped when editing in the builder. Filter limits are documented via an info tooltip instead of inline hint text.Reviewed by Cursor Bugbot for commit 8333314. Bugbot is set up for automated code reviews on this repo. Configure here.