You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/08/24 19:11:58 UTC
(superset) branch master updated: fix(explore): honor column Label in filter search and pill (#43404)
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new c635d0754fc fix(explore): honor column Label in filter search and pill (#43404)
c635d0754fc is described below
commit c635d0754fc3ea35eee1fe95e04140bc90a21749
Author: Joe Li <jo...@preset.io>
AuthorDate: Mon Aug 24 12:11:52 2026 -0700
fix(explore): honor column Label in filter search and pill (#43404)
---
.../DndAdhocFilterOption.tsx | 2 +-
.../DndFilterSelect.test.tsx | 31 +++++++++++++++++-
.../FilterControl/AdhocFilter/AdhocFilter.test.ts | 28 ++++++++++++++++
.../controls/FilterControl/AdhocFilter/index.ts | 10 +++---
...AdhocFilterEditPopoverSimpleTabContent.test.tsx | 37 ++++++++++++++++++++++
.../index.tsx | 4 +++
.../AdhocFilterOption/AdhocFilterOption.test.tsx | 18 +++++++++++
.../FilterControl/AdhocFilterOption/index.tsx | 2 +-
.../controls/FilterControl/utils/translateToSQL.ts | 34 ++++++++++++++++++--
9 files changed, 156 insertions(+), 10 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndAdhocFilterOption.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndAdhocFilterOption.tsx
index 525b2350496..34517365ee8 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndAdhocFilterOption.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndAdhocFilterOption.tsx
@@ -60,7 +60,7 @@ export default function DndAdhocFilterOption({
<OptionWrapper
key={index}
index={index}
- label={actualTimeRange ?? adhocFilter.getDefaultLabel()}
+ label={actualTimeRange ?? adhocFilter.getDefaultLabel(options)}
tooltipTitle={title ?? adhocFilter.getTooltipTitle()}
clickClose={onClickClose}
onShiftOptions={onShiftOptions}
diff --git a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx
index cca9e101e8d..dc94dc802bb 100644
--- a/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx
+++ b/superset-frontend/src/explore/components/controls/DndColumnSelectControl/DndFilterSelect.test.tsx
@@ -43,7 +43,7 @@ import {
DndFilterSelectProps,
} from 'src/explore/components/controls/DndColumnSelectControl/DndFilterSelect';
import { PLACEHOLDER_DATASOURCE } from 'src/dashboard/constants';
-import { ExpressionTypes } from '../FilterControl/types';
+import { Clauses, ExpressionTypes } from '../FilterControl/types';
import { DndItemType } from '../../DndItemType';
import { Datasource } from '../../../types';
import {
@@ -137,6 +137,35 @@ test('renders with value', async () => {
expect(await screen.findByText('COUNT(*)')).toBeInTheDocument();
});
+test('renders the pill using the column verbose_name when one is set', async () => {
+ const value = new AdhocFilter({
+ expressionType: ExpressionTypes.Simple,
+ subject: 'num',
+ operator: '>',
+ comparator: '500',
+ clause: Clauses.Where,
+ });
+ render(
+ setup({
+ value,
+ columns: [
+ {
+ id: 1,
+ type: 'BIGINT',
+ type_generic: GenericDataType.Numeric,
+ column_name: 'num',
+ verbose_name: 'total_count',
+ },
+ ],
+ }),
+ {
+ useDndKit: true,
+ store,
+ },
+ );
+ expect(await screen.findByText('total_count > 500')).toBeInTheDocument();
+});
+
test('renders options with saved metric', async () => {
render(
setup({
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.ts b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.ts
index 18d58848df7..116387ce28c 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.ts
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.ts
@@ -370,4 +370,32 @@ describe('AdhocFilter', () => {
});
expect(adhocFilter.getDefaultLabel()).toBe('');
});
+ test('uses the column verbose_name in the label when one is given', () => {
+ const adhocFilter = new AdhocFilter({
+ expressionType: ExpressionTypes.Simple,
+ subject: 'num',
+ operator: '>',
+ comparator: '500',
+ clause: Clauses.Where,
+ });
+ expect(
+ adhocFilter.getDefaultLabel([
+ { column_name: 'num', verbose_name: 'total_count' },
+ ]),
+ ).toBe('total_count > 500');
+ });
+ test('falls back to the column_name when no verbose_name is set', () => {
+ const adhocFilter = new AdhocFilter({
+ expressionType: ExpressionTypes.Simple,
+ subject: 'num',
+ operator: '>',
+ comparator: '500',
+ clause: Clauses.Where,
+ });
+ expect(
+ adhocFilter.getDefaultLabel([{ column_name: 'num', verbose_name: '' }]),
+ ).toBe('num > 500');
+ expect(adhocFilter.getDefaultLabel([])).toBe('num > 500');
+ expect(adhocFilter.getDefaultLabel()).toBe('num > 500');
+ });
});
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.ts b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.ts
index 649b8135ba3..f5502c348fe 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.ts
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.ts
@@ -23,7 +23,7 @@ import {
OPERATOR_ENUM_TO_OPERATOR_TYPE,
Operators,
} from 'src/explore/constants';
-import { translateToSql } from '../utils/translateToSQL';
+import { translateToSql, VerboseColumn } from '../utils/translateToSQL';
import { Clauses, ExpressionTypes } from '../types';
const CUSTOM_OPERATIONS = [...CUSTOM_OPERATORS].map(
@@ -193,8 +193,8 @@ export default class AdhocFilter {
);
}
- getDefaultLabel(): string {
- const label = this.translateToSql();
+ getDefaultLabel(columns?: VerboseColumn[]): string {
+ const label = this.translateToSql({ columns });
return label.length < 43 ? label : `${label.substring(0, 40)}...`;
}
@@ -202,8 +202,8 @@ export default class AdhocFilter {
return this.translateToSql();
}
- translateToSql(): string {
- return translateToSql(this as unknown as CoreAdhocFilter);
+ translateToSql(params: { columns?: VerboseColumn[] } = {}): string {
+ return translateToSql(this as unknown as CoreAdhocFilter, params);
}
}
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx
index f9687b49172..8e0f9f16ae8 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/AdhocFilterEditPopoverSimpleTabContent.test.tsx
@@ -23,6 +23,7 @@ import {
screen,
userEvent,
waitFor,
+ within,
} from 'spec/helpers/testing-library';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
@@ -914,3 +915,39 @@ test('dropdown should remain open when clicked after filter is configured', asyn
expect(operatorDropdown).toHaveAttribute('aria-expanded', 'true');
});
+
+test('filters the subject select by column verbose_name as well as column_name', async () => {
+ setup({
+ options: [
+ {
+ type: 'BIGINT',
+ column_name: 'num',
+ verbose_name: 'total_count',
+ id: 1,
+ },
+ {
+ type: 'VARCHAR(255)',
+ column_name: 'name',
+ verbose_name: 'Full Name',
+ id: 2,
+ },
+ ],
+ });
+
+ const combobox = screen.getByRole('combobox', { name: 'Select subject' });
+ userEvent.click(combobox);
+
+ await userEvent.type(combobox, 'total');
+
+ const dropdown = document.querySelector(
+ '.ant-select-dropdown-list',
+ ) as HTMLElement;
+ expect(within(dropdown).getByText('total_count')).toBeInTheDocument();
+ expect(within(dropdown).queryByText('Full Name')).not.toBeInTheDocument();
+
+ await userEvent.clear(combobox);
+ await userEvent.type(combobox, 'num');
+
+ expect(within(dropdown).getByText('total_count')).toBeInTheDocument();
+ expect(within(dropdown).queryByText('Full Name')).not.toBeInTheDocument();
+});
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
index c093e703acf..692d84b3135 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
@@ -639,7 +639,11 @@ const AdhocFilterEditPopoverSimpleTabContent: FC<Props> = props => {
('optionName' in column && column.optionName) ||
undefined,
label: renderSubjectOptionLabel(column),
+ column_name: 'column_name' in column ? column.column_name : undefined,
+ verbose_name:
+ 'verbose_name' in column ? column.verbose_name : undefined,
}))}
+ optionFilterProps={['column_name', 'verbose_name']}
{...subjectSelectProps}
/>
);
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/AdhocFilterOption.test.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/AdhocFilterOption.test.tsx
index 1469ef1ac64..05b5b2be93f 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/AdhocFilterOption.test.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/AdhocFilterOption.test.tsx
@@ -71,6 +71,24 @@ test('should render the control label', async () => {
expect(await screen.findByText('value > 10')).toBeInTheDocument();
});
+test('should render the control label using the column verbose_name when one is set', async () => {
+ render(
+ setup({
+ ...mockedProps,
+ options: [
+ {
+ type: 'DOUBLE',
+ column_name: 'value',
+ verbose_name: 'total_count',
+ id: 3,
+ },
+ ],
+ }),
+ { useDnd: true, useRedux: true },
+ );
+ expect(await screen.findByText('total_count > 10')).toBeInTheDocument();
+});
+
test('should render the remove button', async () => {
render(setup(mockedProps), { useDnd: true, useRedux: true });
const removeBtn = await screen.findByTestId('remove-control-button');
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/index.tsx b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/index.tsx
index ca406143ec3..d521cbc0948 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/index.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterOption/index.tsx
@@ -65,7 +65,7 @@ export default function AdhocFilterOption({
partitionColumn={partitionColumn ?? undefined}
>
<OptionControlLabel
- label={actualTimeRange ?? adhocFilter.getDefaultLabel()}
+ label={actualTimeRange ?? adhocFilter.getDefaultLabel(options)}
tooltipTitle={title ?? adhocFilter.getTooltipTitle()}
onRemove={() =>
onRemoveFilter({
diff --git a/superset-frontend/src/explore/components/controls/FilterControl/utils/translateToSQL.ts b/superset-frontend/src/explore/components/controls/FilterControl/utils/translateToSQL.ts
index df42774bada..6e102cc84f9 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/utils/translateToSQL.ts
+++ b/superset-frontend/src/explore/components/controls/FilterControl/utils/translateToSQL.ts
@@ -63,9 +63,35 @@ export const OPERATORS_TO_SQL = {
`= '{{ presto.latest_partition('${datasource.schema}.${datasource.datasource_name}') }}'`,
};
+export interface VerboseColumn {
+ column_name?: string;
+ verbose_name?: string | null;
+}
+
+// Resolves the display label for a filter's subject: the verbose_name of the
+// matching column when one is supplied, falling back to the technical
+// subject used for SQL generation.
+const getDisplaySubject = (
+ subject: string | { column_name?: string } | null | undefined,
+ columns?: VerboseColumn[],
+) => {
+ if (!columns) {
+ return subject ?? undefined;
+ }
+ const columnName =
+ typeof subject === 'object' ? subject?.column_name : subject;
+ const verboseName = columns.find(
+ column => column.column_name === columnName,
+ )?.verbose_name;
+ return verboseName || (subject ?? undefined);
+};
+
export const translateToSql = (
adhocFilter: AdhocFilter,
- { useSimple }: { useSimple: boolean } = { useSimple: false },
+ {
+ useSimple,
+ columns,
+ }: { useSimple?: boolean; columns?: VerboseColumn[] } = {},
) => {
if (isSimpleAdhocFilter(adhocFilter) || useSimple) {
const { subject, operator } = adhocFilter as SimpleAdhocFilter;
@@ -81,7 +107,11 @@ export const translateToSql = (
OPERATORS_TO_SQL[operator](adhocFilter)
: // @ts-expect-error TODO: fix missing operator type `NOT LIKE` and `TEMPORAL RANGE`.
OPERATORS_TO_SQL[operator];
- return getSimpleSQLExpression(subject, op, comparator);
+ return getSimpleSQLExpression(
+ getDisplaySubject(subject, columns),
+ op,
+ comparator,
+ );
}
if (isFreeFormAdhocFilter(adhocFilter)) {
return adhocFilter.sqlExpression;