You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2026/08/27 13:12:52 UTC

(superset) 01/46: docs: add design spec for dataset-aware widget controls

This is an automated email from the ASF dual-hosted git repository.

EnxDev pushed a commit to branch enxdev/poc/dashboard-v2-editing-ui-flow
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 5a1a81b937dce53b696c1d516318c736ed84d8ed
Author: Enzo Martellucci <en...@gmail.com>
AuthorDate: Thu Aug 20 14:39:43 2026 +0200

    docs: add design spec for dataset-aware widget controls
    
    Extends the widget control panel's x-control vocabulary with
    column/metric-reference controls so fields like dimensions and metrics
    render dataset-aware pickers instead of plain text/JSON inputs.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 ...6-08-20-dataset-aware-widget-controls-design.md | 170 +++++++++++++++++++++
 1 file changed, 170 insertions(+)

diff --git a/docs/superpowers/specs/2026-08-20-dataset-aware-widget-controls-design.md b/docs/superpowers/specs/2026-08-20-dataset-aware-widget-controls-design.md
new file mode 100644
index 00000000000..bfcfd9ec0b4
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-20-dataset-aware-widget-controls-design.md
@@ -0,0 +1,170 @@
+# Dataset-Aware Widget Controls — Design
+
+**Status:** Approved for planning
+**Branch:** `enxdev/poc/dashboard-v2-editing-ui-flow`
+**Date:** 2026-08-20
+
+## Problem
+
+Dashboard V2's widget control panel is schema-driven end to end: a backend
+`Widget.controls_class` (a Pydantic model) becomes a JSON Schema served at
+`/api/v1/widgets/type/<widget_type>/control-schema`, rendered generically by
+JsonForms in the Inspector's Form tab
+([SchemaControlPanel.tsx](../../../superset-frontend/src/pages/DashboardBuilderV2/SchemaControlPanel.tsx)),
+and written back through `provider.updateProps` — the same call an
+AI/MCP-driven edit would use.
+
+That machinery renders every field as a generic input keyed only to its JSON
+type (string, number, array). It has no concept that a field's value is a
+*reference into the widget's bound dataset* — a column name, a metric — so
+fields like `BalloonsControls.dimensions` or `color_dimension`
+([superset/widgets/controls.py](../../../superset/widgets/controls.py)) render
+as bare text inputs, and `metrics` is forced into a raw-JSON editor
+(`x-control: "code"`) purely because its entries are heterogeneous, not
+because a real picker is impossible.
+
+Reference UIs (Looker Studio / Power BI-style panels) tie each such field to
+the dataset's column metadata: a type icon (ABC for string, 1.2 for numeric),
+an ordered add/remove/drag list for multi-value fields (X axis, Y axis), and
+per-category swatches for a chosen dimension's distinct values. The last of
+these already exists in spirit — `BalloonsControls.Customization.series` uses
+`x-dynamic` + `x-key-source` to enrich per-series color controls once a
+grouping dimension is set — but the column/metric-reference fields themselves
+have no equivalent.
+
+SIP-182 (Semantic Layer support, apache/superset#35003) establishes the same
+principle one layer up — an `Explorable` protocol with typed columns driving
+a "reactive metric/dimension compatibility matrix" for semantic-layer
+*connections*. It does not specify per-field UI rendering and this design
+does not depend on it; the vocabulary (column type gates which control
+renders) is the only thing borrowed.
+
+## Scope
+
+In scope: making fields *within* an existing widget's control schema
+type-aware (column pickers, metric pickers, ordered multi-value lists).
+
+Out of scope (deferred): swapping an already-placed widget's type in place
+(the "Visualization" dropdown in the reference screenshots, e.g. Bar → Table).
+That is a separate feature — changing `node.type` on a live node — and is not
+addressed here.
+
+## Approach
+
+Two options were considered:
+
+- **Extend the existing `x-control` vocabulary** (chosen) — add new
+  `x-control` values alongside the existing `code`/`color` entries in
+  [schemaControlRenderers.tsx](../../../superset-frontend/src/pages/DashboardBuilderV2/schemaControlRenderers.tsx),
+  following the exact pattern already established there: a JsonForms tester
+  matches the `x-control` value, the renderer fetches whatever data it needs
+  and calls `handleChange`. The backend control model stays the single source
+  of truth for which fields are column/metric references and which types
+  they accept, declared declaratively via `json_schema_extra` — the same way
+  `x-control: code` and `x-dynamic` are declared today.
+- **Embed Superset's classic Explore controls** (`MetricsControl`,
+  `DndColumnSelect`) directly — rejected. Those components are coupled to the
+  Explore Redux slice; using them in Dashboard V2 would mean faking
+  Explore-shaped state to borrow a widget, which works against the reason the
+  schema-driven JsonForms system exists in the first place, and reintroduces
+  exactly the legacy-pattern coupling the frontend modernization effort is
+  moving away from.
+
+## Design
+
+### New `x-control` values
+
+Declared via `json_schema_extra` on a Pydantic field, identically to how
+`x-control: "code"` is declared today:
+
+- `x-control: "column"` — single column reference. Optional
+  `x-column-types: ["numeric" | "temporal" | "string"]` restricts which
+  dataset columns are selectable; omitted means any column.
+- `x-control: "column-multi"` — ordered list of column references (for
+  `dimensions`-shaped fields): add, remove, and reorder (drag).
+- `x-control: "metric"` — single metric reference (a saved metric name, or an
+  ad-hoc aggregate expressible as one).
+- `x-control: "metric-multi"` — ordered list of metric references.
+
+`metrics` fields keep their existing raw-JSON (`x-control: "code"`) path as a
+fallback for anything not expressible through the picker (this design does
+not change or remove that path — see Error handling).
+
+### Dataset metadata plumbing
+
+A new `fetchDatasetColumns(datasetId)` helper is added to
+`superset-frontend/src/core/dashboard/`, parallel to the existing
+`fetchQueryData` in
+[chartData.ts](../../../superset-frontend/src/core/dashboard/chartData.ts).
+It calls the existing `/api/v1/dataset/<id>` REST endpoint (already used by
+V1 Explore; no backend endpoint work is needed for this) and returns
+`{ columns: [{ name, type, isTemporal, isNumeric }], metrics: [{ metricName,
+verboseName }] }`. Results are cached per `datasetId` for the lifetime of the
+Inspector session, mirroring the fetch-once-and-cache pattern
+`schemaControlledWidgets.ts` already uses for widget types.
+
+### New renderers
+
+Added to `schemaControlRenderers.tsx` beside `CodeControl`/`ColorControl`:
+
+- `ColumnControl` / `ColumnMultiControl` — fetch via
+  `fetchDatasetColumns`, filter by the field's `x-column-types`, render an
+  antd `Select` (single) or an ordered add/remove/drag list (multi). Each
+  option is labeled with `ColumnTypeLabel` from `@superset-ui/chart-controls`
+  (confirmed presentational, no Redux coupling — safe to reuse as-is).
+- `MetricControl` / `MetricMultiControl` — same fetch, options are the
+  dataset's saved metrics (`MetricOption` from the same package for the
+  icon/label treatment), plus an escape hatch that drops to the existing
+  `CodeControl` for a value not expressible as a saved metric.
+- The two multi-controls share one small drag-list primitive implemented
+  locally (plain `onDragStart`/`onDrop`) — no new dependency, since nothing
+  in this layer currently pulls in a drag library.
+
+### Data flow
+
+Unchanged at the edges: every control still reads and writes `node.props`
+through `provider.updateProps`, and the schema itself still comes from the
+existing `/api/v1/widgets/type/<t>/control-schema` endpoint. The only new
+network call is the dataset-columns/metrics fetch, and it is triggered
+exactly the way `x-dynamic` already triggers `SchemaControlPanel`'s
+`loadSeries` call today — off the presence of a field carrying the new
+`x-control` value in the already-fetched schema, not off a new schema
+capability flag.
+
+Backend changes are additive and narrow, and land on the shared `DataBinding`
+model so every widget that embeds it (`MetricTileControls`,
+`AgGridTableControls`, `BalloonsControls`, `EchartsControls`) picks them up
+uniformly rather than needing per-widget duplication:
+`DataBinding.dimensions` gets `x-control: "column-multi"`, and
+`DataBinding.metrics` gets `x-control: "metric-multi"` in place of its
+current `x-control: "code"`. `BalloonsControls.color_dimension` separately
+gets `x-control: "column"`. `EchartsControls.echarts_options` (a distinct
+field, not `metrics`) is unaffected and stays free-form JSON, since its
+`$bind` markers are not a fixed set of fields.
+
+### Error handling
+
+- A failed `fetchDatasetColumns` call fails open to a plain text/code input
+  for the affected field, rather than blocking the panel — the same fail-open
+  behavior `useSchemaControlledWidgetTypes` already applies to the widget
+  registry fetch.
+- A previously-picked column or metric that no longer exists on the dataset
+  is shown selected but visually flagged (not silently cleared), consistent
+  with `BalloonsControls`'s existing philosophy of surfacing invalid state
+  through `validate_control_values` rather than swallowing it.
+
+### Testing
+
+- Backend: schema-shape assertions in the style of
+  `tests/unit_tests/widgets/test_registry.py`, asserting each newly annotated
+  field serves the expected `x-control`/`x-column-types` extras.
+- Frontend: one Jest + React Testing Library test per new renderer (mirroring
+  `SchemaControlPanel.test.tsx`), mocking `fetchDatasetColumns`; one
+  integration-style test per control confirming a pick round-trips through
+  `provider.updateProps` into `node.props`.
+
+## Open questions
+
+None outstanding — visualization-type swapping is explicitly deferred (see
+Scope), and the reuse-vs-rebuild fork was resolved in favor of extending
+`x-control` during design review.