You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2026/06/29 21:24:41 UTC

(superset) 01/01: fix(semantic layers): disable Simple tab in columns

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

betodealmeida pushed a commit to branch sl-hide-simple
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 6db1fe893aab97fc5d638bc81cd71253d2b3c6e9
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Mon Jun 29 17:24:27 2026 -0400

    fix(semantic layers): disable Simple tab in columns
---
 .../ColumnSelectPopover.test.tsx                   | 34 ++++++++++++++++++++++
 .../DndColumnSelectControl/ColumnSelectPopover.tsx | 26 +++++++++++++----
 .../DndColumnMetricSelect.tsx                      |  8 +++--
 .../DndColumnSelectControl/DndColumnSelect.tsx     |  8 +++--
 superset/semantic_layers/models.py                 |  9 ++++++
 tests/unit_tests/semantic_layers/models_test.py    |  9 ++++++
 6 files changed, 84 insertions(+), 10 deletions(-)

diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
index 4c3d0fa13aa..5065b5b4463 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.test.tsx
@@ -128,6 +128,40 @@ test('open with Saved tab selected when there is a saved column selected', () =>
   expect(getByText('Custom SQL')).toHaveAttribute('aria-selected', 'false');
 });
 
+test('shows Simple and Custom SQL as disabled when disabledTabs requests it', () => {
+  // Semantic-view contexts disable both Simple and Custom SQL so only
+  // "Saved" is offered, matching how SV metrics behave in the metric
+  // popover. The disabled tabs stay visible (greyed out) so users see all
+  // the options that could exist in another context.
+  const store = mockStore({
+    explore: { datasource: { type: 'semantic_view' } },
+  });
+  const { getByText } = render(
+    <ColumnSelectPopover
+      columns={[{ column_name: 'year', expression: 'year' } as any]}
+      editedColumn={undefined}
+      getCurrentTab={jest.fn()}
+      onChange={jest.fn()}
+      hasCustomLabel
+      isTemporal={false}
+      label="Custom Label"
+      onClose={jest.fn()}
+      setDatasetModal={jest.fn()}
+      setLabel={jest.fn()}
+      disabledTabs={new Set(['simple', 'sqlExpression'])}
+    />,
+    { store },
+  );
+
+  expect(getByText('Saved')).toHaveAttribute('aria-selected', 'true');
+  // Simple and Custom SQL stay rendered but are marked disabled by Antd and
+  // are not the initially selected tab.
+  expect(getByText('Simple')).toHaveAttribute('aria-disabled', 'true');
+  expect(getByText('Simple')).toHaveAttribute('aria-selected', 'false');
+  expect(getByText('Custom SQL')).toHaveAttribute('aria-disabled', 'true');
+  expect(getByText('Custom SQL')).toHaveAttribute('aria-selected', 'false');
+});
+
 test('open with Custom SQL tab selected when there is a custom SQL selected', () => {
   const { getByText } = renderPopover({
     columns: [{ column_name: 'year' }],
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
index 1e69be2c45e..9aa0421e7be 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/ColumnSelectPopover.tsx
@@ -281,11 +281,20 @@ const ColumnSelectPopover = ({
     [columnMap, metricMap, onSimpleColumnChange, onSimpleMetricChange],
   );
 
-  const defaultActiveTabKey = initialAdhocColumn
-    ? 'sqlExpression'
-    : selectedCalculatedColumn
-      ? 'saved'
-      : 'simple';
+  // Pick the most natural starting tab, but never one that's disabled —
+  // semantic-view contexts disable Simple and Custom SQL, leaving Saved.
+  let defaultActiveTabKey: string;
+  if (initialAdhocColumn && !disabledTabs.has('sqlExpression')) {
+    defaultActiveTabKey = 'sqlExpression';
+  } else if (selectedCalculatedColumn && !disabledTabs.has('saved')) {
+    defaultActiveTabKey = 'saved';
+  } else if (!disabledTabs.has('simple')) {
+    defaultActiveTabKey = 'simple';
+  } else if (!disabledTabs.has('saved')) {
+    defaultActiveTabKey = 'saved';
+  } else {
+    defaultActiveTabKey = 'sqlExpression';
+  }
 
   useEffect(() => {
     getCurrentTab(defaultActiveTabKey);
@@ -501,8 +510,15 @@ const ColumnSelectPopover = ({
                 },
               ]),
           {
+            // The Simple tab is rendered even when disabled (mirroring how
+            // Custom SQL behaves below) so users see all the options that
+            // could exist in another context and understand why they're
+            // unavailable here. Semantic-view contexts disable it because
+            // dimensions there are pre-defined items in the semantic model,
+            // not physical columns to wrap.
             key: TABS_KEYS.SIMPLE,
             label: t('Simple'),
+            disabled: disabledTabs.has('simple'),
             children: (
               <>
                 {isTemporal && simpleColumns.length === 0 ? (
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnMetricSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnMetricSelect.tsx
index f820bcd0d20..cca02f40cef 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnMetricSelect.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnMetricSelect.tsx
@@ -130,12 +130,14 @@ function DndColumnMetricSelect(props: DndColumnMetricSelectProps) {
     formData,
   } = props;
 
-  // Semantic views do not support arbitrary SQL expressions as dimensions.
-  // Merge 'sqlExpression' into disabledTabs so the Custom SQL tab is hidden.
+  // Semantic-view dimensions and metrics are pre-defined items in the
+  // semantic model; ad-hoc construction from a physical column (Simple) or
+  // an arbitrary SQL expression (Custom SQL) doesn't apply. Hide both tabs
+  // so only "Saved" is offered.
   const effectiveDisabledTabs = useMemo(
     () =>
       String(datasource?.type) === 'semantic_view'
-        ? new Set([...(disabledTabs ?? []), 'sqlExpression'])
+        ? new Set([...(disabledTabs ?? []), 'simple', 'sqlExpression'])
         : disabledTabs,
     [datasource?.type, disabledTabs],
   );
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
index dea06e9f792..161eed7470e 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndColumnSelect.tsx
@@ -53,14 +53,18 @@ function DndColumnSelect(props: DndColumnSelectProps) {
     disabledTabs,
   } = props;
 
-  // Semantic views do not support arbitrary SQL expressions as dimensions.
+  // Semantic-view dimensions are pre-defined items in the semantic model;
+  // there is no notion of a physical column to wrap (Simple) or an ad-hoc
+  // SQL expression (Custom SQL), so both of those popover tabs are
+  // disabled. Only the "Saved" tab — which lists the SV's dimensions —
+  // is offered, mirroring how SV metrics behave.
   const datasourceType = useSelector<ExplorePageState, string | undefined>(
     state => state.explore.datasource?.type,
   );
   const effectiveDisabledTabs = useMemo(
     () =>
       datasourceType === 'semantic_view'
-        ? new Set([...(disabledTabs ?? []), 'sqlExpression'])
+        ? new Set([...(disabledTabs ?? []), 'simple', 'sqlExpression'])
         : disabledTabs,
     [datasourceType, disabledTabs],
   );
diff --git a/superset/semantic_layers/models.py b/superset/semantic_layers/models.py
index 34cdfbc4792..4f0e59c03d2 100644
--- a/superset/semantic_layers/models.py
+++ b/superset/semantic_layers/models.py
@@ -315,6 +315,11 @@ class SemanticView(AuditMixinNullable, Model):
 
     @property
     def columns(self) -> list[ColumnMetadata]:
+        # ``expression`` is set to ``dimension.definition`` so the explore
+        # column picker routes SV dimensions to the "Saved" tab, mirroring
+        # how SV metrics land in their "Saved" tab. The metaphor is "pick
+        # from the semantic layer's pre-defined items" — SV exposes neither
+        # arbitrary physical columns (Simple) nor ad-hoc SQL (Custom SQL).
         return [
             ColumnMetadata(
                 column_name=dimension.name,
@@ -350,6 +355,10 @@ class SemanticView(AuditMixinNullable, Model):
                     "certified_by": None,
                     "column_name": dimension.name,
                     "description": dimension.description,
+                    # See ``columns`` property: ``expression`` carries
+                    # ``dimension.definition`` so SV dimensions land in the
+                    # "Saved" tab of the explore column picker, matching
+                    # how SV metrics already behave.
                     "expression": dimension.definition,
                     "filterable": True,
                     "groupby": True,
diff --git a/tests/unit_tests/semantic_layers/models_test.py b/tests/unit_tests/semantic_layers/models_test.py
index dd8c1e67dd1..c67d3f0c115 100644
--- a/tests/unit_tests/semantic_layers/models_test.py
+++ b/tests/unit_tests/semantic_layers/models_test.py
@@ -506,6 +506,11 @@ def test_semantic_view_columns(
         assert columns[1].column_name == "category"
         assert columns[1].type == "string"
         assert columns[1].is_dttm is False
+        # ``expression`` carries the dimension definition so the explore
+        # column picker routes SV dimensions to the "Saved" tab, matching
+        # how SV metrics behave (no Simple/Custom SQL path is supported).
+        assert columns[0].expression == "orders.order_date"
+        assert columns[1].expression == "products.category"
 
 
 def test_semantic_view_column_names(
@@ -635,6 +640,10 @@ def test_semantic_view_data(
         assert data["columns"][1]["column_name"] == "category"
         assert data["columns"][1]["type"] == "string"
         assert data["columns"][1]["type_generic"] == GenericDataType.STRING
+        # ``expression`` carries the dimension definition so the explore
+        # column picker routes SV dimensions to the "Saved" tab.
+        assert data["columns"][0]["expression"] == "orders.order_date"
+        assert data["columns"][1]["expression"] == "products.category"
 
         # Check metrics
         assert len(data["metrics"]) == 2