diff --git a/null_byte.csv b/null_byte.csv new file mode 100644 index 0000000000..55132aaa63 Binary files /dev/null and b/null_byte.csv differ diff --git a/superset/common/query_context_processor.py b/superset/common/query_context_processor.py index 75b3e4e976..eb3fa9b6bb 100644 --- a/superset/common/query_context_processor.py +++ b/superset/common/query_context_processor.py @@ -60,7 +60,7 @@ from superset.utils.core import ( get_column_names_from_columns, get_column_names_from_metrics, get_metric_names, - get_xaxis_label, + get_x_axis_label, normalize_dttm_col, TIME_COMPARISON, ) @@ -399,7 +399,7 @@ class QueryContextProcessor: for offset in query_object.time_offsets: try: # pylint: disable=line-too-long - # Since the xaxis is also a column name for the time filter, xaxis_label will be set as granularity + # Since the x-axis is also a column name for the time filter, x_axis_label will be set as granularity # these query object are equivalent: # 1) { granularity: 'dttm_col', time_range: '2020 : 2021', time_offsets: ['1 year ago']} # 2) { columns: [ @@ -414,9 +414,9 @@ class QueryContextProcessor: ) query_object_clone.to_dttm = get_past_or_future(offset, outer_to_dttm) - xaxis_label = get_xaxis_label(query_object.columns) + x_axis_label = get_x_axis_label(query_object.columns) query_object_clone.granularity = ( - query_object_clone.granularity or xaxis_label + query_object_clone.granularity or x_axis_label ) except ValueError as ex: raise QueryObjectValidationError(str(ex)) from ex @@ -450,7 +450,7 @@ class QueryContextProcessor: query_object_clone.filter = [ flt for flt in query_object_clone.filter - if flt.get("col") != xaxis_label + if flt.get("col") != x_axis_label ] # `offset` is added to the hash function diff --git a/superset/common/query_object_factory.py b/superset/common/query_object_factory.py index fe4cca3f48..63d1ef0966 100644 --- a/superset/common/query_object_factory.py +++ b/superset/common/query_object_factory.py @@ -28,7 +28,7 @@ from superset.utils.core import ( DatasourceDict, DatasourceType, FilterOperator, - get_xaxis_label, + get_x_axis_label, QueryObjectFilterClause, ) @@ -122,9 +122,9 @@ class QueryObjectFactory: # pylint: disable=too-few-public-methods # Use the temporal filter as the time range. # if the temporal filters uses x-axis as the temporal filter # then use it or use the first temporal filter - xaxis_label = get_xaxis_label(columns or []) + x_axis_label = get_x_axis_label(columns) match_flt = [ - flt for flt in temporal_flt if flt.get("col") == xaxis_label + flt for flt in temporal_flt if flt.get("col") == x_axis_label ] if match_flt: time_range = cast(str, match_flt[0].get("val")) diff --git a/superset/common/utils/time_range_utils.py b/superset/common/utils/time_range_utils.py index 4969988657..4dc78317dd 100644 --- a/superset/common/utils/time_range_utils.py +++ b/superset/common/utils/time_range_utils.py @@ -52,7 +52,7 @@ def get_since_until_from_query_object( """ this function will return since and until by tuple if 1) the time_range is in the query object. - 2) the xaxis column is in the columns field + 2) the x-axis column is in the columns field and its corresponding `temporal_range` filter is in the adhoc filters. :param query_object: a valid query object :return: since and until by tuple diff --git a/superset/models/helpers.py b/superset/models/helpers.py index e707284706..48b95566af 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -90,6 +90,7 @@ from superset.utils import core as utils, json from superset.utils.core import ( GenericDataType, get_column_name, + get_non_base_axis_columns, get_user_id, is_adhoc_column, MediumText, @@ -2083,7 +2084,7 @@ class ExploreMixin: # pylint: disable=too-many-public-methods "filter": filter, "orderby": orderby, "extras": extras, - "columns": columns, + "columns": get_non_base_axis_columns(columns), "order_desc": True, } diff --git a/superset/utils/core.py b/superset/utils/core.py index 710710eaf2..ee5e451f3e 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -1056,16 +1056,23 @@ def is_adhoc_column(column: Column) -> TypeGuard[AdhocColumn]: ) +def is_base_axis(column: Column) -> bool: + return is_adhoc_column(column) and column.get("columnType") == "BASE_AXIS" + + +def get_base_axis_columns(columns: list[Column] | None) -> list[Column]: + return [column for column in columns or [] if is_base_axis(column)] + + +def get_non_base_axis_columns(columns: list[Column] | None) -> list[Column]: + return [column for column in columns or [] if not is_base_axis(column)] + + def get_base_axis_labels(columns: list[Column] | None) -> tuple[str, ...]: - axis_cols = [ - col - for col in columns or [] - if is_adhoc_column(col) and col.get("columnType") == "BASE_AXIS" - ] - return tuple(get_column_name(col) for col in axis_cols) + return tuple(get_column_name(column) for column in get_base_axis_columns(columns)) -def get_xaxis_label(columns: list[Column] | None) -> str | None: +def get_x_axis_label(columns: list[Column] | None) -> str | None: labels = get_base_axis_labels(columns) return labels[0] if labels else None