You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2026/04/13 19:05:16 UTC
(superset) 11/45: fix(explore): handle boolean false values correctly in control rendering (#39172)
This is an automated email from the ASF dual-hosted git repository.
michaelsmolina pushed a commit to branch 6.1
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 1e4ad1ba5290a92658f23a1b56359c94ffd3fc8c
Author: Maxime Beauchemin <ma...@gmail.com>
AuthorDate: Tue Apr 7 18:23:03 2026 -0700
fix(explore): handle boolean false values correctly in control rendering (#39172)
Co-authored-by: Claude Opus 4.6 <no...@anthropic.com>
(cherry picked from commit 6ba90968701fbce377d6479842329fed86c31e4f)
---
.../ConditionalFormattingControl.test.tsx | 83 ++++++++++++++++++++++
.../ConditionalFormattingControl.tsx | 5 ++
...AdhocFilterEditPopoverSimpleTabContent.test.tsx | 20 ++++++
.../index.tsx | 5 +-
.../exploreUtils/getSimpleSQLExpression.test.ts | 12 ++++
.../src/explore/exploreUtils/index.ts | 3 +-
6 files changed, 125 insertions(+), 3 deletions(-)
diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.test.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.test.tsx
new file mode 100644
index 00000000000..11716aa43a1
--- /dev/null
+++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.test.tsx
@@ -0,0 +1,83 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { render, screen } from 'spec/helpers/testing-library';
+import { Comparator } from '@superset-ui/chart-controls';
+import { GenericDataType } from '@apache-superset/core/common';
+import ConditionalFormattingControl from './ConditionalFormattingControl';
+import { ConditionalFormattingConfig } from './types';
+
+const columnOptions = [
+ { label: 'My Column', value: 'my_col', dataType: GenericDataType.Boolean },
+];
+
+const defaultProps = {
+ columnOptions,
+ verboseMap: {} as Record<string, string>,
+ removeIrrelevantConditions: false,
+ label: 'Conditional Formatting',
+ description: 'Test',
+ name: 'conditional_formatting',
+ onChange: jest.fn(),
+};
+
+test('renders "is false" operator label without trailing undefined', () => {
+ const value: ConditionalFormattingConfig[] = [
+ { column: 'my_col', operator: Comparator.IsFalse, colorScheme: 'colorSuccess' },
+ ];
+ render(<ConditionalFormattingControl {...defaultProps} value={value} />);
+ expect(screen.getByText('my_col is false')).toBeInTheDocument();
+});
+
+test('renders "is true" operator label without trailing undefined', () => {
+ const value: ConditionalFormattingConfig[] = [
+ { column: 'my_col', operator: Comparator.IsTrue, colorScheme: 'colorSuccess' },
+ ];
+ render(<ConditionalFormattingControl {...defaultProps} value={value} />);
+ expect(screen.getByText('my_col is true')).toBeInTheDocument();
+});
+
+test('renders "is null" operator label without trailing undefined', () => {
+ const value: ConditionalFormattingConfig[] = [
+ { column: 'my_col', operator: Comparator.IsNull, colorScheme: 'colorSuccess' },
+ ];
+ render(<ConditionalFormattingControl {...defaultProps} value={value} />);
+ expect(screen.getByText('my_col is null')).toBeInTheDocument();
+});
+
+test('renders "is not null" operator label without trailing undefined', () => {
+ const value: ConditionalFormattingConfig[] = [
+ { column: 'my_col', operator: Comparator.IsNotNull, colorScheme: 'colorSuccess' },
+ ];
+ render(<ConditionalFormattingControl {...defaultProps} value={value} />);
+ expect(screen.getByText('my_col is not null')).toBeInTheDocument();
+});
+
+test('renders verbose column name when available', () => {
+ const value: ConditionalFormattingConfig[] = [
+ { column: 'my_col', operator: Comparator.IsFalse, colorScheme: 'colorSuccess' },
+ ];
+ render(
+ <ConditionalFormattingControl
+ {...defaultProps}
+ verboseMap={{ my_col: 'My Column' }}
+ value={value}
+ />,
+ );
+ expect(screen.getByText('My Column is false')).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
index c476cc67772..52cb263f503 100644
--- a/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
+++ b/superset-frontend/src/explore/components/controls/ConditionalFormattingControl/ConditionalFormattingControl.tsx
@@ -136,6 +136,11 @@ const ConditionalFormattingControl = ({
return `${targetValueLeft} ${Comparator.LessOrEqual} ${columnName} ${Comparator.LessThan} ${targetValueRight}`;
case Comparator.BetweenOrRightEqual:
return `${targetValueLeft} ${Comparator.LessThan} ${columnName} ${Comparator.LessOrEqual} ${targetValueRight}`;
+ case Comparator.IsTrue:
+ case Comparator.IsFalse:
+ case Comparator.IsNull:
+ case Comparator.IsNotNull:
+ return `${columnName} ${operator}`;
default:
return `${columnName} ${operator} ${targetValue}`;
}
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 1c0cb00f7e6..a238ca38f28 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
@@ -269,6 +269,26 @@ test('will convert from individual comparator to array if the operator changes t
).toEqual(Operators.In);
});
+test('will preserve boolean false comparator when converting to multi operator', () => {
+ const booleanFalseFilter = new AdhocFilter({
+ expressionType: ExpressionTypes.Simple,
+ subject: 'value',
+ operatorId: Operators.Equals,
+ operator: OPERATOR_ENUM_TO_OPERATOR_TYPE[Operators.Equals].operation,
+ comparator: false,
+ clause: Clauses.Where,
+ });
+ const props = setup({ adhocFilter: booleanFalseFilter });
+ const { onOperatorChange } = useSimpleTabFilterProps(
+ props as unknown as Props,
+ );
+ onOperatorChange(Operators.In);
+ expect(
+ props.onChange.mock.calls[props.onChange.mock.calls.length - 1][0]
+ .comparator,
+ ).toEqual([false]);
+});
+
test('will convert from array to individual comparators if the operator changes from multi', () => {
const props = setup({
adhocFilter: simpleMultiAdhocFilter,
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 ff9c4db9274..64a590a01d8 100644
--- a/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
+++ b/superset-frontend/src/explore/components/controls/FilterControl/AdhocFilterEditPopoverSimpleTabContent/index.tsx
@@ -199,7 +199,7 @@ export const useSimpleTabFilterProps = (props: Props) => {
if (MULTI_OPERATORS.has(operatorId)) {
newComparator = Array.isArray(currentComparator)
? currentComparator
- : [currentComparator].filter(element => element);
+ : [currentComparator].filter(element => element != null);
} else {
newComparator = Array.isArray(currentComparator)
? currentComparator[0]
@@ -396,7 +396,8 @@ const AdhocFilterEditPopoverSimpleTabContent: FC<Props> = props => {
};
const comparatorHasValue =
- comparator &&
+ comparator != null &&
+ comparator !== '' &&
(Array.isArray(comparator)
? comparator.length > 0
: String(comparator).length > 0);
diff --git a/superset-frontend/src/explore/exploreUtils/getSimpleSQLExpression.test.ts b/superset-frontend/src/explore/exploreUtils/getSimpleSQLExpression.test.ts
index b13b0853035..d2319cf3268 100644
--- a/superset-frontend/src/explore/exploreUtils/getSimpleSQLExpression.test.ts
+++ b/superset-frontend/src/explore/exploreUtils/getSimpleSQLExpression.test.ts
@@ -70,3 +70,15 @@ test('Should return correct string when subject and operator are valid values',
]),
).toBe("subject operator 'comparator', 'comparator-2'");
});
+
+test('Should handle boolean false comparator as a string value', () => {
+ expect(getSimpleSQLExpression(params.subject, params.operator, false)).toBe(
+ "subject operator 'FALSE'",
+ );
+});
+
+test('Should handle boolean true comparator as a string value', () => {
+ expect(getSimpleSQLExpression(params.subject, params.operator, true)).toBe(
+ "subject operator 'TRUE'",
+ );
+});
diff --git a/superset-frontend/src/explore/exploreUtils/index.ts b/superset-frontend/src/explore/exploreUtils/index.ts
index d67e5f8474b..e0207f22dd7 100644
--- a/superset-frontend/src/explore/exploreUtils/index.ts
+++ b/superset-frontend/src/explore/exploreUtils/index.ts
@@ -458,7 +458,8 @@ export const getSimpleSQLExpression = (
isMulti && Array.isArray(comparator) ? comparator[0] : comparator;
const comparatorArray = ensureIsArray(comparator);
const isString =
- firstValue !== undefined && Number.isNaN(Number(firstValue));
+ firstValue !== undefined &&
+ (typeof firstValue === 'boolean' || Number.isNaN(Number(firstValue)));
const quote = isString ? "'" : '';
const [prefix, suffix] = isMulti ? ['(', ')'] : ['', ''];
if (comparatorArray.length > 0 && showComparator) {