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

beto 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 41bf215367 fix: boolean filters in Explore (#32701)
41bf215367 is described below

commit 41bf2153672964d58080966abee464b7fb847c6b
Author: Beto Dealmeida <[email protected]>
AuthorDate: Mon Mar 17 14:38:00 2025 -0400

    fix: boolean filters in Explore (#32701)
---
 .../FilterControl/AdhocFilter/AdhocFilter.test.js  | 50 ++++++++++++++------
 .../controls/FilterControl/AdhocFilter/index.js    | 54 ++++++++--------------
 ...AdhocFilterEditPopoverSimpleTabContent.test.tsx | 16 +++----
 .../index.tsx                                      |  3 --
 superset-frontend/src/explore/constants.ts         |  4 +-
 .../src/explore/exploreUtils/index.js              | 19 +++++---
 superset/models/helpers.py                         |  4 +-
 7 files changed, 81 insertions(+), 69 deletions(-)

diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js
index ed25e78c12..7ac2a79e4b 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/AdhocFilter.test.js
@@ -225,8 +225,8 @@ describe('AdhocFilter', () => {
     });
     expect(adhocFilter2.translateToSql()).toBe('SUM(value) <> 5');
   });
-  it('sets comparator to null when operator is IS_NULL', () => {
-    const adhocFilter2 = new AdhocFilter({
+  it('sets comparator to undefined when operator is IS_NULL', () => {
+    const adhocFilter = new AdhocFilter({
       expressionType: ExpressionTypes.Simple,
       subject: 'SUM(value)',
       operator: 'IS NULL',
@@ -234,10 +234,10 @@ describe('AdhocFilter', () => {
       comparator: '5',
       clause: Clauses.Having,
     });
-    expect(adhocFilter2.comparator).toBe(null);
+    expect(adhocFilter.comparator).toBe(undefined);
   });
-  it('sets comparator to null when operator is IS_NOT_NULL', () => {
-    const adhocFilter2 = new AdhocFilter({
+  it('sets comparator to undefined when operator is IS_NOT_NULL', () => {
+    const adhocFilter = new AdhocFilter({
       expressionType: ExpressionTypes.Simple,
       subject: 'SUM(value)',
       operator: 'IS NOT NULL',
@@ -245,38 +245,60 @@ describe('AdhocFilter', () => {
       comparator: '5',
       clause: Clauses.Having,
     });
-    expect(adhocFilter2.comparator).toBe(null);
+    expect(adhocFilter.comparator).toBe(undefined);
+  });
+  it('sets comparator to undefined when operator is IS_TRUE', () => {
+    const adhocFilter = new AdhocFilter({
+      expressionType: ExpressionTypes.Simple,
+      subject: 'col',
+      operator: 'IS TRUE',
+      operatorId: Operators.IsTrue,
+      comparator: '5',
+      clause: Clauses.Having,
+    });
+    expect(adhocFilter.comparator).toBe(undefined);
+  });
+  it('sets comparator to undefined when operator is IS_FALSE', () => {
+    const adhocFilter = new AdhocFilter({
+      expressionType: ExpressionTypes.Simple,
+      subject: 'col',
+      operator: 'IS FALSE',
+      operatorId: Operators.IsFalse,
+      comparator: '5',
+      clause: Clauses.Having,
+    });
+    expect(adhocFilter.comparator).toBe(undefined);
   });
   it('sets the label properly if subject is a string', () => {
-    const adhocFilter2 = new AdhocFilter({
+    const adhocFilter = new AdhocFilter({
       expressionType: ExpressionTypes.Simple,
       subject: 'order_date',
     });
-    expect(adhocFilter2.getDefaultLabel()).toBe('order_date');
+    expect(adhocFilter.getDefaultLabel()).toBe('order_date');
   });
   it('sets the label properly if subject is an object with the column_date 
property', () => {
-    const adhocFilter2 = new AdhocFilter({
+    const adhocFilter = new AdhocFilter({
       expressionType: ExpressionTypes.Simple,
       subject: {
         column_name: 'year',
       },
     });
-    expect(adhocFilter2.getDefaultLabel()).toBe('year');
+    expect(adhocFilter.getDefaultLabel()).toBe('year');
   });
   it('sets the label to empty is there is no column_name in the object', () => 
{
-    const adhocFilter2 = new AdhocFilter({
+    const adhocFilter = new AdhocFilter({
       expressionType: ExpressionTypes.Simple,
       subject: {
         unknown: 'year',
       },
     });
-    expect(adhocFilter2.getDefaultLabel()).toBe('');
+    expect(adhocFilter.getDefaultLabel()).toBe('');
   });
   it('sets the label to empty is there is no subject', () => {
-    const adhocFilter2 = new AdhocFilter({
+    const adhocFilter = new AdhocFilter({
       expressionType: ExpressionTypes.Simple,
       subject: undefined,
     });
-    expect(adhocFilter2.getDefaultLabel()).toBe('');
+    expect(adhocFilter.getDefaultLabel()).toBe('');
   });
 });
diff --git 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
index 92cb69eebc..7c13236573 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilter/index.js
@@ -18,7 +18,7 @@
  */
 import {
   CUSTOM_OPERATORS,
-  Operators,
+  DISABLE_INPUT_OPERATORS,
   OPERATOR_ENUM_TO_OPERATOR_TYPE,
 } from 'src/explore/constants';
 import { translateToSql } from '../utils/translateToSQL';
@@ -36,18 +36,8 @@ export default class AdhocFilter {
       this.operator = adhocFilter.operator?.toUpperCase();
       this.operatorId = adhocFilter.operatorId;
       this.comparator = adhocFilter.comparator;
-      if (
-        [Operators.IsTrue, Operators.IsFalse].indexOf(adhocFilter.operatorId) 
>=
-        0
-      ) {
-        this.comparator = adhocFilter.operatorId === Operators.IsTrue;
-      }
-      if (
-        [Operators.IsNull, Operators.IsNotNull].indexOf(
-          adhocFilter.operatorId,
-        ) >= 0
-      ) {
-        this.comparator = null;
+      if (DISABLE_INPUT_OPERATORS.indexOf(adhocFilter.operatorId) >= 0) {
+        this.comparator = undefined;
       }
       this.clause = adhocFilter.clause || Clauses.Where;
       this.sqlExpression = null;
@@ -103,34 +93,30 @@ export default class AdhocFilter {
   }
 
   isValid() {
-    const nullCheckOperators = [Operators.IsNotNull, Operators.IsNull].map(
-      op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation,
-    );
-    const truthCheckOperators = [Operators.IsTrue, Operators.IsFalse].map(
-      op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation,
-    );
     if (this.expressionType === ExpressionTypes.Simple) {
-      if (nullCheckOperators.indexOf(this.operator) >= 0) {
-        return !!(this.operator && this.subject);
-      }
-      if (truthCheckOperators.indexOf(this.operator) >= 0) {
-        return !!(this.subject && this.comparator !== null);
+      // operators where the comparator is not used
+      if (
+        DISABLE_INPUT_OPERATORS.map(
+          op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation,
+        ).indexOf(this.operator) >= 0
+      ) {
+        return !!this.subject;
       }
+
       if (this.operator && this.subject && this.clause) {
         if (Array.isArray(this.comparator)) {
-          if (this.comparator.length > 0) {
-            // A non-empty array of values ('IN' or 'NOT IN' clauses)
-            return true;
-          }
-        } else if (this.comparator !== null) {
-          // A value has been selected or typed
-          return true;
+          // A non-empty array of values ('IN' or 'NOT IN' clauses)
+          return this.comparator.length > 0;
         }
+        // A value has been selected or typed
+        return this.comparator !== null;
       }
-    } else if (this.expressionType === ExpressionTypes.Sql) {
-      return !!(this.sqlExpression && this.clause);
     }
-    return false;
+
+    return (
+      this.expressionType === ExpressionTypes.Sql &&
+      !!(this.sqlExpression && this.clause)
+    );
   }
 
   getDefaultLabel() {
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 bbae865763..f7614deaf8 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
@@ -330,25 +330,25 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
       expect(isOperatorRelevant(operator, 'value')).toBe(true);
     });
   });
-  it('sets comparator to true when operator is IS_TRUE', () => {
+  it('sets comparator to undefined when operator is IS_TRUE', () => {
     const props = setup();
     const { onOperatorChange } = useSimpleTabFilterProps(props);
     onOperatorChange(Operators.IsTrue);
     expect(props.onChange.calledOnce).toBe(true);
     expect(props.onChange.lastCall.args[0].operatorId).toBe(Operators.IsTrue);
-    expect(props.onChange.lastCall.args[0].operator).toBe('==');
-    expect(props.onChange.lastCall.args[0].comparator).toBe(true);
+    expect(props.onChange.lastCall.args[0].operator).toBe('IS TRUE');
+    expect(props.onChange.lastCall.args[0].comparator).toBe(undefined);
   });
-  it('sets comparator to false when operator is IS_FALSE', () => {
+  it('sets comparator to undefined when operator is IS_FALSE', () => {
     const props = setup();
     const { onOperatorChange } = useSimpleTabFilterProps(props);
     onOperatorChange(Operators.IsFalse);
     expect(props.onChange.calledOnce).toBe(true);
     expect(props.onChange.lastCall.args[0].operatorId).toBe(Operators.IsFalse);
-    expect(props.onChange.lastCall.args[0].operator).toBe('==');
-    expect(props.onChange.lastCall.args[0].comparator).toBe(false);
+    expect(props.onChange.lastCall.args[0].operator).toBe('IS FALSE');
+    expect(props.onChange.lastCall.args[0].comparator).toBe(undefined);
   });
-  it('sets comparator to null when operator is IS_NULL or IS_NOT_NULL', () => {
+  it('sets comparator to undefined when operator is IS_NULL or IS_NOT_NULL', 
() => {
     const props = setup();
     const { onOperatorChange } = useSimpleTabFilterProps(props);
     [Operators.IsNull, Operators.IsNotNull].forEach(op => {
@@ -358,7 +358,7 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
       expect(props.onChange.lastCall.args[0].operator).toBe(
         OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation,
       );
-      expect(props.onChange.lastCall.args[0].comparator).toBe(null);
+      expect(props.onChange.lastCall.args[0].comparator).toBe(undefined);
     });
   });
 });
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 904c005c14..bfd2b409a8 100644
--- 
a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
+++ 
b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
@@ -209,9 +209,6 @@ export const useSimpleTabFilterProps = (props: Props) => {
         ? currentComparator[0]
         : currentComparator;
     }
-    if (operatorId === Operators.IsTrue || operatorId === Operators.IsFalse) {
-      newComparator = Operators.IsTrue === operatorId;
-    }
     if (operatorId && CUSTOM_OPERATORS.has(operatorId)) {
       props.onChange(
         props.adhocFilter.duplicateWith({
diff --git a/superset-frontend/src/explore/constants.ts 
b/superset-frontend/src/explore/constants.ts
index cb56e31393..6b927b4198 100644
--- a/superset-frontend/src/explore/constants.ts
+++ b/superset-frontend/src/explore/constants.ts
@@ -83,8 +83,8 @@ export const OPERATOR_ENUM_TO_OPERATOR_TYPE: {
     display: t('use latest_partition template'),
     operation: 'LATEST PARTITION',
   },
-  [Operators.IsTrue]: { display: t('Is true'), operation: '==' },
-  [Operators.IsFalse]: { display: t('Is false'), operation: '==' },
+  [Operators.IsTrue]: { display: t('Is true'), operation: 'IS TRUE' },
+  [Operators.IsFalse]: { display: t('Is false'), operation: 'IS FALSE' },
   [Operators.TemporalRange]: {
     display: t('TEMPORAL_RANGE'),
     operation: 'TEMPORAL_RANGE',
diff --git a/superset-frontend/src/explore/exploreUtils/index.js 
b/superset-frontend/src/explore/exploreUtils/index.js
index 374445c979..ed4d03c6a2 100644
--- a/superset-frontend/src/explore/exploreUtils/index.js
+++ b/superset-frontend/src/explore/exploreUtils/index.js
@@ -32,6 +32,7 @@ import { safeStringify } from 'src/utils/safeStringify';
 import { optionLabel } from 'src/utils/common';
 import { URL_PARAMS } from 'src/constants';
 import {
+  DISABLE_INPUT_OPERATORS,
   MULTI_OPERATORS,
   OPERATOR_ENUM_TO_OPERATOR_TYPE,
   UNSAVED_CHART_ID,
@@ -300,6 +301,10 @@ export const getSimpleSQLExpression = (subject, operator, 
comparator) => {
     [...MULTI_OPERATORS]
       .map(op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation)
       .indexOf(operator) >= 0;
+  const showComparator =
+    DISABLE_INPUT_OPERATORS.map(
+      op => OPERATOR_ENUM_TO_OPERATOR_TYPE[op].operation,
+    ).indexOf(operator) === -1;
   // If returned value is an object after changing dataset
   let expression =
     typeof subject === 'object'
@@ -314,13 +319,13 @@ export const getSimpleSQLExpression = (subject, operator, 
comparator) => {
       firstValue !== undefined && Number.isNaN(Number(firstValue));
     const quote = isString ? "'" : '';
     const [prefix, suffix] = isMulti ? ['(', ')'] : ['', ''];
-    const formattedComparators = comparatorArray
-      .map(val => optionLabel(val))
-      .map(
-        val =>
-          `${quote}${isString ? String(val).replace(/'/g, "''") : 
val}${quote}`,
-      );
-    if (comparatorArray.length > 0) {
+    if (comparatorArray.length > 0 && showComparator) {
+      const formattedComparators = comparatorArray
+        .map(val => optionLabel(val))
+        .map(
+          val =>
+            `${quote}${isString ? String(val).replace(/'/g, "''") : 
val}${quote}`,
+        );
       expression += ` ${prefix}${formattedComparators.join(', ')}${suffix}`;
     }
   }
diff --git a/superset/models/helpers.py b/superset/models/helpers.py
index 1cad46ca62..33c7c17d17 100644
--- a/superset/models/helpers.py
+++ b/superset/models/helpers.py
@@ -722,6 +722,8 @@ class ExploreMixin:  # pylint: 
disable=too-many-public-methods
     }
     fetch_values_predicate = None
 
+    normalize_columns = False
+
     @property
     def type(self) -> str:
         raise NotImplementedError()
@@ -1976,7 +1978,7 @@ class ExploreMixin:  # pylint: 
disable=too-many-public-methods
 
         self.make_orderby_compatible(select_exprs, orderby_exprs)
 
-        for col, (orig_col, ascending) in zip(orderby_exprs, orderby, 
strict=False):  # noqa: B007
+        for col, (_orig_col, ascending) in zip(orderby_exprs, orderby, 
strict=False):  # noqa: B007
             if not db_engine_spec.allows_alias_in_orderby and isinstance(col, 
Label):
                 # if engine does not allow using SELECT alias in ORDER BY
                 # revert to the underlying column

Reply via email to