sadpandajoe commented on code in PR #44152:
URL: https://github.com/apache/superset/pull/44152#discussion_r3986057386


##########
superset/mcp_service/chart/query_result.py:
##########
@@ -223,3 +223,79 @@ def validate_gauge_query_result(
     """Check Gauge results using the same finite-dial contract as rendering."""
     normalized = normalize_gauge_query_result(result, form_data)
     return normalized if isinstance(normalized, ChartError) else None
+
+
+def normalize_chart_query_result(result: Any, form_data: Mapping[str, Any]) -> 
Any:
+    """Validate chart-specific result contracts before consumers use rows."""
+    if form_data.get("viz_type") != "treemap_v2":
+        return normalize_gauge_query_result(result, form_data)
+    if failure := query_result_failure(result):
+        return failure
+    label = metric_result_label(form_data.get("metric"))
+    hierarchy = form_data.get("groupby")
+    if (
+        not label
+        or not isinstance(hierarchy, list)
+        or not hierarchy
+        or not all(isinstance(column, str) and column for column in hierarchy)
+        or len(set(hierarchy)) != len(hierarchy)
+        or label in hierarchy
+    ):
+        return ChartError(
+            error=(
+                "Treemap requires unique hierarchy columns and a distinct 
metric label."
+            ),
+            error_type="InvalidTreemapFormData",
+        )
+    queries = result.get("queries") if isinstance(result, Mapping) else None
+    if not isinstance(queries, list) or len(queries) != 1:
+        return ChartError(
+            error="Treemap requires exactly one query result.",
+            error_type="InvalidTreemapResult",
+        )
+    query = queries[0]
+    rows = query.get("data") if isinstance(query, Mapping) else None
+    if not isinstance(rows, list):
+        return ChartError(
+            error="Treemap query data must be an array of rows.",
+            error_type="InvalidTreemapResult",
+        )
+    if failure := _validate_treemap_rows(rows, hierarchy, label):
+        return failure
+    return result
+
+
+def _validate_treemap_rows(
+    rows: list[Any], hierarchy: list[str], label: str
+) -> ChartError | None:
+    """Require complete hierarchy outputs and finite numeric metric values."""
+    for index, row in enumerate(rows):
+        if not isinstance(row, Mapping) or any(
+            column not in row for column in [*hierarchy, label]
+        ):
+            return ChartError(
+                error=f"Treemap row {index} is missing hierarchy or metric 
outputs.",
+                error_type="InvalidTreemapResult",
+            )
+        value = row[label]
+        try:
+            valid = (
+                not isinstance(value, bool)
+                and isinstance(value, (int, float))

Review Comment:
   SQL DECIMAL/NUMERIC aggregates can still be `decimal.Decimal` in 
`ChartDataCommand` records before JSON conversion, so this `int`/`float` guard 
returns `InvalidTreemapMetric` for data the native chart can render. Could this 
accept finite `Decimal`/real values while still excluding booleans, with a 
Decimal regression?



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -1276,6 +1379,16 @@ def map_treemap_config(config: TreemapChartConfig) -> 
Dict[str, Any]:
         "row_limit": config.row_limit,
         "color_scheme": config.color_scheme or "supersetColors",
     }
+    for key in _TREEMAP_PRESENTATION_KEYS | {
+        "time_range",
+        "granularity_sqla",
+        "template_params",
+    }:
+        value = getattr(config, key)
+        if value is not None:
+            form_data[key] = (

Review Comment:
   This serializes `CurrencyFormat` as `symbol_position`, but the native 
Treemap formatter reads `symbolPosition`; an explicit suffix can therefore be 
ignored and replaced by the locale default in Explore. Should this call 
`CurrencyFormat.to_form_data()` like the other chart mappers?



##########
superset/mcp_service/chart/tool/get_chart_preview.py:
##########
@@ -470,6 +475,8 @@ def generate(self) -> VegaLitePreview | ChartError:  # 
noqa: C901
             if result and "queries" in result and len(result["queries"]) > 0:

Review Comment:
   These saved Treemap previews query with format fallbacks (50/20/1000 rows) 
because `_preview_row_limit()` only honors Gauge, so a chart configured with 
`row_limit: 1` can preview categories and geometry absent from the actual chart 
while the unsaved path honors 1. Should Treemap use its configured row limit 
here too?



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -672,6 +673,104 @@ def _without_generated_gauge_time_filter(
     ]
 
 
+def resolve_treemap_update_config(
+    config: ChartConfig | TreemapChartUpdateConfig,
+    existing: dict[str, Any],
+    *,
+    dataset_rebind: bool = False,
+) -> ChartConfig:
+    """Fill omitted required roles only from an authorized same-dataset 
Treemap."""
+    if not isinstance(config, TreemapChartUpdateConfig) or isinstance(
+        config, TreemapChartConfig
+    ):
+        return config
+    values = config.model_dump(exclude_unset=True)
+    if existing.get("viz_type") == "treemap_v2" and not dataset_rebind:
+        for field in ("groupby", "metric"):
+            if field not in config.model_fields_set and field in existing:
+                values[field] = existing[field]
+    resolved = TreemapChartConfig.model_validate(values)
+    resolved.__pydantic_fields_set__ = set(config.model_fields_set)
+    return resolved
+
+
+_TREEMAP_PRESENTATION_KEYS = frozenset(
+    {
+        "color_scheme",
+        "show_labels",
+        "show_upper_labels",
+        "label_type",
+        "label_position",
+        "number_format",
+        "date_format",
+        "currency_format",
+    }
+)
+
+
+def _merge_treemap_filters(
+    existing: dict[str, Any],
+    patch: dict[str, Any],
+    config: TreemapChartConfig,
+    dataset_rebind: bool,
+) -> None:
+    """Separate explicit filter/temporal changes from mapper-generated 
defaults."""
+    fields = config.model_fields_set
+    if "temporal_column" not in fields:
+        patch.pop(MCP_DASHBOARD_TIME_FILTER_SUBJECT, None)
+    if "filters" not in fields:

Review Comment:
   With `filters` explicit and `temporal_column` omitted, the mapper inserts a 
neutral filter for the dataset default time column; this branch removes only 
its marker and then overwrites the saved `adhoc_filters`. A Treemap previously 
bound to another time column therefore starts querying the default column while 
retaining a stale subject marker. Could the existing generated binding be 
preserved unless `temporal_column` is explicitly changed?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to