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