Fix order
diff --git a/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts b/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts index 07b296c..5eb2261 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/DatasourceKey.ts
@@ -35,9 +35,10 @@ constructor(key: string) { const [idStr, typeStr] = key.split('__'); - // Try to parse as integer, fall back to string (UUID) if NaN - const parsedId = parseInt(idStr, 10); - this.id = Number.isNaN(parsedId) ? idStr : parsedId; + // Only parse as integer if the entire string is numeric + // (parseInt would incorrectly parse "85d3139f..." as 85) + const isNumeric = /^\d+$/.test(idStr); + this.id = isNumeric ? parseInt(idStr, 10) : idStr; this.type = DATASOURCE_TYPE_MAP[typeStr] ?? DatasourceType.Table; }
diff --git a/superset/semantic_layers/mapper.py b/superset/semantic_layers/mapper.py index f6bfbeb..2bc485f 100644 --- a/superset/semantic_layers/mapper.py +++ b/superset/semantic_layers/mapper.py
@@ -270,6 +270,27 @@ ) +def _normalize_column(column: str | dict, dimension_names: set[str]) -> str: + """ + Normalize a column to its dimension name. + + Columns can be either: + - A string (dimension name directly) + - A dict with isColumnReference=True and sqlExpression containing the dimension name + """ + if isinstance(column, str): + return column + + if isinstance(column, dict): + # Handle column references (e.g., from time-series charts) + if column.get("isColumnReference") and column.get("sqlExpression"): + sql_expr = column["sqlExpression"] + if sql_expr in dimension_names: + return sql_expr + + raise ValueError("Adhoc dimensions are not supported in Semantic Views.") + + def map_query_object(query_object: ValidatedQueryObject) -> list[SemanticQuery]: """ Convert a `QueryObject` into a list of `SemanticQuery`. @@ -277,8 +298,6 @@ This function maps the `QueryObject` into query objects that focus less on visualization and more on semantics. """ - print("BETO") - print(query_object) semantic_view = query_object.datasource.implementation all_metrics = {metric.name: metric for metric in semantic_view.metrics} @@ -286,6 +305,12 @@ dimension.name: dimension for dimension in semantic_view.dimensions } + # Normalize columns (may be dicts with isColumnReference=True for time-series) + dimension_names = set(all_dimensions.keys()) + normalized_columns = { + _normalize_column(column, dimension_names) for column in query_object.columns + } + metrics = [all_metrics[metric] for metric in (query_object.metrics or [])] grain = ( @@ -296,7 +321,7 @@ dimensions = [ dimension for dimension in semantic_view.dimensions - if dimension.name in query_object.columns + if dimension.name in normalized_columns and ( # if a grain is specified, only include the time dimension if its grain # matches the requested grain @@ -794,12 +819,14 @@ Make sure all dimensions are defined in the semantic view. """ semantic_view = query_object.datasource.implementation - - if any(not isinstance(column, str) for column in query_object.columns): - raise ValueError("Adhoc dimensions are not supported in Semantic Views.") - dimension_names = {dimension.name for dimension in semantic_view.dimensions} - if not set(query_object.columns) <= dimension_names: + + # Normalize all columns to dimension names + normalized_columns = [ + _normalize_column(column, dimension_names) for column in query_object.columns + ] + + if not set(normalized_columns) <= dimension_names: raise ValueError("All dimensions must be defined in the Semantic View.")
diff --git a/superset/semantic_layers/snowflake/semantic_view.py b/superset/semantic_layers/snowflake/semantic_view.py index 4c7ae77..9f34e6f 100644 --- a/superset/semantic_layers/snowflake/semantic_view.py +++ b/superset/semantic_layers/snowflake/semantic_view.py
@@ -51,6 +51,7 @@ NUMBER, OBJECT, Operator, + OrderDirection, OrderTuple, PredicateType, SemanticRequest, @@ -479,6 +480,49 @@ for element, direction in order ) + def _get_temporal_dimension( + self, + dimensions: list[Dimension], + ) -> Dimension | None: + """ + Find the first temporal dimension in the list. + + Returns the first dimension with a temporal type (DATE, DATETIME, TIME), + or None if no temporal dimension is found. + """ + temporal_types = {DATE, DATETIME, TIME} + for dimension in dimensions: + if dimension.type in temporal_types: + return dimension + return None + + def _get_default_order( + self, + dimensions: list[Dimension], + order: list[OrderTuple] | None, + ) -> list[OrderTuple] | None: + """ + Get the order to use, prepending temporal sort if needed. + + If there's a temporal dimension in the query and it's not already + in the order, prepends an ascending sort by that dimension. + This ensures time-series data is always sorted chronologically first. + """ + temporal_dimension = self._get_temporal_dimension(dimensions) + if not temporal_dimension: + return order + + # Check if temporal dimension is already in the order + if order: + for element, _ in order: + if isinstance(element, Dimension) and element.id == temporal_dimension.id: + return order + # Prepend temporal dimension to existing order + return [(temporal_dimension, OrderDirection.ASC)] + list(order) + + # No order specified, use temporal dimension + return [(temporal_dimension, OrderDirection.ASC)] + def _build_simple_query( self, metrics: list[Metric], @@ -495,7 +539,9 @@ self._alias_element(dimension) for dimension in dimensions ) metric_arguments = ", ".join(self._alias_element(metric) for metric in metrics) - order_clause = self._build_order_clause(order) + # Use default temporal ordering if no explicit order is provided + effective_order = self._get_default_order(dimensions, order) + order_clause = self._build_order_clause(effective_order) return dedent( f""" @@ -740,7 +786,9 @@ select_clause = ",\n ".join(select_columns) # Build ORDER BY clause (need to reference the aliased columns) - order_clause = self._build_order_clause(order) + # Use default temporal ordering if no explicit order is provided + effective_order = self._get_default_order(dimensions, order) + order_clause = self._build_order_clause(effective_order) query = dedent( f""" @@ -794,7 +842,9 @@ self._alias_element(dimension) for dimension in dimensions ) metric_arguments = ", ".join(self._alias_element(metric) for metric in metrics) - order_clause = self._build_order_clause(order) + # Use default temporal ordering if no explicit order is provided + effective_order = self._get_default_order(dimensions, order) + order_clause = self._build_order_clause(effective_order) top_groups_cte, cte_params = self._build_top_groups_cte( group_limit,