* Restrict operators when column is boolean * refactor 'isOperatorRelevant' a little bit * Include 'BOOLEAN' to handle presto * Update tests * number column should show bool operators * fix test - some dbs translate true/false to 1/0 * Fix tests and add linting * When column type is boolean, show bool operators * Address PR comments - simplify conditions * Fix a linting error * Addressing PR comment - remove unused variables
This commit is contained in:
parent
84e8dc71f6
commit
90378ed94e
|
|
@ -16,7 +16,7 @@
|
|||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { CUSTOM_OPERATORS } from 'src/explore/constants';
|
||||
import { CUSTOM_OPERATORS, OPERATORS } from 'src/explore/constants';
|
||||
import { getSimpleSQLExpression } from 'src/explore/exploreUtils';
|
||||
|
||||
export const EXPRESSION_TYPES = {
|
||||
|
|
@ -42,6 +42,8 @@ const OPERATORS_TO_SQL = {
|
|||
REGEX: 'REGEX',
|
||||
'IS NOT NULL': 'IS NOT NULL',
|
||||
'IS NULL': 'IS NULL',
|
||||
'IS TRUE': 'IS TRUE',
|
||||
'IS FALSE': 'IS FALSE',
|
||||
'LATEST PARTITION': ({ datasource }) =>
|
||||
`= '{{ presto.latest_partition('${datasource.schema}.${datasource.datasource_name}') }}'`,
|
||||
};
|
||||
|
|
@ -116,7 +118,14 @@ export default class AdhocFilter {
|
|||
|
||||
isValid() {
|
||||
if (this.expressionType === EXPRESSION_TYPES.SIMPLE) {
|
||||
if (this.operator === 'IS NOT NULL' || this.operator === 'IS NULL') {
|
||||
if (
|
||||
[
|
||||
OPERATORS['IS TRUE'],
|
||||
OPERATORS['IS FALSE'],
|
||||
OPERATORS['IS NULL'],
|
||||
OPERATORS['IS NOT NULL'],
|
||||
].indexOf(this.operator) >= 0
|
||||
) {
|
||||
return !!(this.operator && this.subject);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -196,4 +196,82 @@ describe('AdhocFilterEditPopoverSimpleTabContent', () => {
|
|||
}),
|
||||
);
|
||||
});
|
||||
it('will display boolean operators only when column type is boolean', () => {
|
||||
const { wrapper } = setup({
|
||||
datasource: {
|
||||
type: 'table',
|
||||
datasource_name: 'table1',
|
||||
schema: 'schema',
|
||||
columns: [{ column_name: 'value', type: 'BOOL' }],
|
||||
},
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
});
|
||||
const booleanOnlyOperators = [
|
||||
'IS TRUE',
|
||||
'IS FALSE',
|
||||
'IS NULL',
|
||||
'IS NOT NULL',
|
||||
];
|
||||
booleanOnlyOperators.forEach(operator => {
|
||||
expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
it('will display boolean operators when column type is number', () => {
|
||||
const { wrapper } = setup({
|
||||
datasource: {
|
||||
type: 'table',
|
||||
datasource_name: 'table1',
|
||||
schema: 'schema',
|
||||
columns: [{ column_name: 'value', type: 'INT' }],
|
||||
},
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
});
|
||||
const booleanOnlyOperators = ['IS TRUE', 'IS FALSE'];
|
||||
booleanOnlyOperators.forEach(operator => {
|
||||
expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
it('will not display boolean operators when column type is string', () => {
|
||||
const { wrapper } = setup({
|
||||
datasource: {
|
||||
type: 'table',
|
||||
datasource_name: 'table1',
|
||||
schema: 'schema',
|
||||
columns: [{ column_name: 'value', type: 'STRING' }],
|
||||
},
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
});
|
||||
const booleanOnlyOperators = ['IS TRUE', 'IS FALSE'];
|
||||
booleanOnlyOperators.forEach(operator => {
|
||||
expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
it('will display boolean operators when column is an expression', () => {
|
||||
const { wrapper } = setup({
|
||||
datasource: {
|
||||
type: 'table',
|
||||
datasource_name: 'table1',
|
||||
schema: 'schema',
|
||||
columns: [
|
||||
{
|
||||
column_name: 'value',
|
||||
expression: 'case when value is 0 then "NO"',
|
||||
},
|
||||
],
|
||||
},
|
||||
adhocFilter: simpleAdhocFilter,
|
||||
});
|
||||
const booleanOnlyOperators = ['IS TRUE', 'IS FALSE'];
|
||||
booleanOnlyOperators.forEach(operator => {
|
||||
expect(wrapper.instance().isOperatorRelevant(operator, 'value')).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -235,11 +235,30 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
|||
}
|
||||
|
||||
isOperatorRelevant(operator, subject) {
|
||||
const column = this.props.datasource.columns?.find(
|
||||
col => col.column_name === subject,
|
||||
);
|
||||
const isColumnBoolean =
|
||||
!!column && (column.type === 'BOOL' || column.type === 'BOOLEAN');
|
||||
const isColumnNumber = !!column && column.type === 'INT';
|
||||
const isColumnFunction = !!column && !!column.expression;
|
||||
|
||||
if (operator && CUSTOM_OPERATORS.has(operator)) {
|
||||
const { partitionColumn } = this.props;
|
||||
return partitionColumn && subject && subject === partitionColumn;
|
||||
}
|
||||
|
||||
if (
|
||||
operator === OPERATORS['IS TRUE'] ||
|
||||
operator === OPERATORS['IS FALSE']
|
||||
) {
|
||||
return isColumnBoolean || isColumnNumber || isColumnFunction;
|
||||
}
|
||||
if (isColumnBoolean) {
|
||||
return (
|
||||
operator === OPERATORS['IS NULL'] ||
|
||||
operator === OPERATORS['IS NOT NULL']
|
||||
);
|
||||
}
|
||||
return !(
|
||||
(this.props.datasource.type === 'druid' &&
|
||||
TABLE_ONLY_OPERATORS.indexOf(operator) >= 0) ||
|
||||
|
|
@ -318,7 +337,7 @@ export default class AdhocFilterEditPopoverSimpleTabContent extends React.Compon
|
|||
OPERATORS_OPTIONS.filter(op => this.isOperatorRelevant(op, subject))
|
||||
.length,
|
||||
),
|
||||
// like AGGREGTES_OPTIONS, operator options are string
|
||||
// like AGGREGATES_OPTIONS, operator options are string
|
||||
value: operator,
|
||||
onChange: this.onOperatorChange,
|
||||
filterOption: (input, option) =>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ export const OPERATORS = {
|
|||
'IS NOT NULL': 'IS NOT NULL',
|
||||
'IS NULL': 'IS NULL',
|
||||
'LATEST PARTITION': 'LATEST PARTITION',
|
||||
'IS TRUE': 'IS TRUE',
|
||||
'IS FALSE': 'IS FALSE',
|
||||
};
|
||||
|
||||
export const OPERATORS_OPTIONS = Object.values(OPERATORS);
|
||||
|
||||
export const TABLE_ONLY_OPERATORS = [OPERATORS.LIKE];
|
||||
|
|
@ -65,6 +68,8 @@ export const DISABLE_INPUT_OPERATORS = [
|
|||
OPERATORS['IS NOT NULL'],
|
||||
OPERATORS['IS NULL'],
|
||||
OPERATORS['LATEST PARTITION'],
|
||||
OPERATORS['IS TRUE'],
|
||||
OPERATORS['IS FALSE'],
|
||||
];
|
||||
|
||||
export const sqlaAutoGeneratedMetricNameRegex = /^(sum|min|max|avg|count|count_distinct)__.*$/i;
|
||||
|
|
|
|||
|
|
@ -1205,6 +1205,10 @@ class SqlaTable( # pylint: disable=too-many-public-methods,too-many-instance-at
|
|||
where_clause_and.append(col_obj.get_sqla_col().is_(None))
|
||||
elif op == utils.FilterOperator.IS_NOT_NULL.value:
|
||||
where_clause_and.append(col_obj.get_sqla_col().isnot(None))
|
||||
elif op == utils.FilterOperator.IS_TRUE.value:
|
||||
where_clause_and.append(col_obj.get_sqla_col().is_(True))
|
||||
elif op == utils.FilterOperator.IS_FALSE.value:
|
||||
where_clause_and.append(col_obj.get_sqla_col().is_(False))
|
||||
else:
|
||||
if eq is None:
|
||||
raise QueryObjectValidationError(
|
||||
|
|
|
|||
|
|
@ -211,6 +211,8 @@ class FilterOperator(str, Enum):
|
|||
IN = "IN" # pylint: disable=invalid-name
|
||||
NOT_IN = "NOT IN"
|
||||
REGEX = "REGEX"
|
||||
IS_TRUE = "IS TRUE"
|
||||
IS_FALSE = "IS FALSE"
|
||||
|
||||
|
||||
class PostProcessingBoxplotWhiskerType(str, Enum):
|
||||
|
|
|
|||
|
|
@ -169,11 +169,14 @@ class TestDatabaseModel(SupersetTestCase):
|
|||
class FilterTestCase(NamedTuple):
|
||||
operator: str
|
||||
value: Union[float, int, List[Any], str]
|
||||
expected: str
|
||||
expected: Union[str, List[str]]
|
||||
|
||||
filters: Tuple[FilterTestCase, ...] = (
|
||||
FilterTestCase(FilterOperator.IS_NULL, "", "IS NULL"),
|
||||
FilterTestCase(FilterOperator.IS_NOT_NULL, "", "IS NOT NULL"),
|
||||
# Some db backends translate true/false to 1/0
|
||||
FilterTestCase(FilterOperator.IS_TRUE, "", ["IS 1", "IS true"]),
|
||||
FilterTestCase(FilterOperator.IS_FALSE, "", ["IS 0", "IS false"]),
|
||||
FilterTestCase(FilterOperator.GREATER_THAN, 0, "> 0"),
|
||||
FilterTestCase(FilterOperator.GREATER_THAN_OR_EQUALS, 0, ">= 0"),
|
||||
FilterTestCase(FilterOperator.LESS_THAN, 0, "< 0"),
|
||||
|
|
@ -199,7 +202,12 @@ class TestDatabaseModel(SupersetTestCase):
|
|||
}
|
||||
sqla_query = table.get_sqla_query(**query_obj)
|
||||
sql = table.database.compile_sqla_query(sqla_query.sqla_query)
|
||||
self.assertIn(filter_.expected, sql)
|
||||
if isinstance(filter_.expected, list):
|
||||
self.assertTrue(
|
||||
any([candidate in sql for candidate in filter_.expected])
|
||||
)
|
||||
else:
|
||||
self.assertIn(filter_.expected, sql)
|
||||
|
||||
def test_incorrect_jinja_syntax_raises_correct_exception(self):
|
||||
query_obj = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue