aminghadersohi commented on code in PR #44148:
URL: https://github.com/apache/superset/pull/44148#discussion_r4175954794


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -2630,3 +2648,128 @@ def preserve_previous_adhoc_filters(
             merged_filters.append(generated_filter)
 
     new_form_data["adhoc_filters"] = merged_filters
+
+
+def _apply_geographic_temporal_update(
+    form_data: dict[str, Any], config: ChartConfig
+) -> None:
+    """Apply explicit time changes after native controls have been merged."""
+    if "temporal_column" not in config.model_fields_set:
+        return
+    # Native granularity takes precedence over the requested adhoc binding.
+    form_data.pop("granularity_sqla", None)
+    if config.temporal_column is None:
+        form_data["adhoc_filters"] = 
_without_generated_dashboard_time_filter(form_data)
+        form_data.pop(MCP_DASHBOARD_TIME_FILTER_SUBJECT, None)
+
+
+def merge_geographic_update_form_data(  # noqa: C901
+    existing: dict[str, Any],
+    mapped: dict[str, Any],
+    config: ChartConfig,
+    *,
+    dataset_rebind: bool,
+) -> dict[str, Any]:
+    """Preserve omitted native controls; clear explicit nullable roles and 
filters.
+
+    Rebinding drops old dataset references and Jinja/query state. All optional
+    roles are remapped from the complete target-dataset config.
+    """
+    if dataset_rebind:
+        existing = {
+            key: value
+            for key, value in existing.items()
+            if key
+            in {
+                "linear_color_scheme",
+                "number_format",
+                "max_bubble_size",
+                "point_unit",
+                "color_by",
+                "color_picker",
+                "color_scheme",
+                "y_axis_format",
+                "map_renderer",
+                "maplibre_style",
+                "mapbox_style",
+                "min_radius",
+                "max_radius",
+                "multiplier",
+            }
+        }
+    field_map = {
+        "country": "select_country",
+        "region_format": "region_format",
+        "country_format": "country_fieldtype",
+        "entity": "entity",
+        "metric": "metric",
+        "secondary_metric": "secondary_metric",
+        "dimension": "dimension",
+        "show_bubbles": "show_bubbles",
+        "max_bubble_size": "max_bubble_size",
+        "sort_by_metric": "sort_by_metric",
+        "linear_color_scheme": "linear_color_scheme",
+        "number_format": "number_format",
+        "row_limit": "row_limit",
+        "time_range": "time_range",
+        "point_unit": "point_unit",
+    }
+    fields = config.model_fields_set
+    patch = dict(mapped)
+    if "temporal_column" in fields and config.temporal_column is None:
+        patch["adhoc_filters"] = 
_without_generated_dashboard_time_filter(patch)
+    elif "temporal_column" not in fields and not dataset_rebind:
+        # Omission keeps the saved dashboard-time binding instead of remapping
+        # it to the dataset default the mapper generated for this update.
+        # An absent marker also preserves an explicitly cleared binding.
+        saved_binding = [
+            filter_
+            for filter_ in existing.get("adhoc_filters", [])
+            if filter_ not in 
_without_generated_dashboard_time_filter(existing)
+        ]
+        patch["adhoc_filters"] = [
+            *saved_binding,
+            *_without_generated_dashboard_time_filter(patch),
+        ]
+        if binding := existing.get(MCP_DASHBOARD_TIME_FILTER_SUBJECT):
+            patch[MCP_DASHBOARD_TIME_FILTER_SUBJECT] = binding
+        else:
+            patch.pop(MCP_DASHBOARD_TIME_FILTER_SUBJECT, None)
+    # Native UI-only presentation controls are not part of this public config.
+    for key in (
+        "color_by",
+        "color_picker",
+        "color_scheme",
+        "y_axis_format",
+        "map_renderer",
+        "maplibre_style",
+        "mapbox_style",
+        "viewport",
+        "autozoom",
+        "min_radius",
+        "max_radius",
+        "multiplier",
+    ):
+        if key in existing:
+            patch.pop(key, None)
+    for source, target in field_map.items():
+        if source not in fields and target in existing:
+            patch.pop(target, None)
+    if not {"radius", "radius_metric"} & fields and "point_radius_fixed" in 
existing:
+        patch.pop("point_radius_fixed", None)
+    if "filters" not in fields:
+        source_form = dict(existing)
+        if "temporal_column" in fields:
+            source_form["adhoc_filters"] = 
_without_generated_dashboard_time_filter(

Review Comment:
   Fixed in 283553d753349769df8237df36874da62ed9987f. All three map panels 
retain `_mcp_dashboard_time_filter_subject` as a shared hidden control through 
Explore hydration and saving. Round-trip frontend tests cover populated and 
cleared provenance; backend regressions cover explicit clearing and replacement 
after saving, preserving user-authored time filters while removing the old 
generated predicate. Validation: chart unit suite 3,249 passed, 3 skipped; 14 
Jest suites / 147 tests passed; touched-file pre-commit including MyPy and 
frontend type checking passed.



##########
superset-frontend/plugins/plugin-chart-country-map/src/controlPanel.ts:
##########
@@ -48,8 +48,30 @@ const config: ControlPanelConfig = {
           },
         ],
         ['entity'],
+        [
+          {
+            name: 'region_format',
+            config: {
+              type: 'SelectControl',
+              label: t('Region value format'),
+              default: null,
+              choices: [
+                [null, t('Legacy ISO values')],

Review Comment:
   Fixed in 283553d753349769df8237df36874da62ed9987f. Saved MCP country maps 
with null or omitted `region_format` validate full ISO 3166-2 boundary codes, 
matching the Legacy ISO values option. Saved and cached JSON/CSV/Excel 
regressions cover `US-CA`; negative tests ensure abbreviation-only, 
other-country and unknown identifiers remain rejected. Validation: chart unit 
suite 3,249 passed, 3 skipped; country-map and related Jest tests 147 passed; 
touched-file pre-commit passed.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -3736,6 +3736,170 @@ def validate_gantt_roles(self) -> "GanttChartConfig":
 
 
 # Discriminated union for runtime validation (not exposed in JSON Schema)
+def _omit_inherited_descriptions(schema: dict[str, Any]) -> None:
+    """Drop field descriptions the published parent schema already carries.
+
+    The geographic references and filters only tighten bounds on the shared
+    ``ColumnRef``/``FilterConfig`` fields, whose descriptions appear in the
+    same tool schema.
+    """
+    for prop in schema.get("properties", {}).values():
+        prop.pop("description", None)
+
+
+class GeographicColumnRef(ColumnRef):
+    """Closed ColumnRef for geographic roles."""
+
+    model_config = ConfigDict(
+        extra="forbid",
+        populate_by_name=True,
+        strict=True,
+        json_schema_extra=_omit_inherited_descriptions,
+    )
+    dtype: str | None = Field(None, max_length=128)
+
+
+GeographicFilterValue = Annotated[str, Field(max_length=1000)] | int | float | 
bool
+
+
+class GeographicFilterConfig(FilterConfig):
+    """Closed FilterConfig with bounded values."""
+
+    model_config = ConfigDict(
+        extra="forbid",
+        populate_by_name=True,
+        strict=True,
+        allow_inf_nan=False,
+        json_schema_extra=_omit_inherited_descriptions,
+    )
+    value: (
+        GeographicFilterValue
+        | Annotated[list[GeographicFilterValue], Field(max_length=1000)]
+        | None
+    ) = Field(None, validation_alias=AliasChoices("value", "val"))
+
+
+class GeographicChartConfig(BaseChartConfig):
+    """Bounded controls shared by geographic visualizations."""
+
+    model_config = ConfigDict(extra="forbid", strict=True)
+    filters: list[GeographicFilterConfig] | None = Field(None, max_length=100)
+    row_limit: int = Field(10000, ge=1, le=10000)
+    time_range: str | None = Field(None, max_length=1000)
+
+    @field_validator("time_range")
+    @classmethod
+    def validate_geographic_time_range(cls, value: str | None) -> str | None:
+        """Reject time expressions the query parser would silently ignore."""
+        return validate_time_range(value)
+
+    @model_validator(mode="after")
+    def validate_geographic_roles(self) -> "GeographicChartConfig":
+        """Reject metric dimensions and unaggregated metric roles."""
+        for field in ("entity", "latitude", "longitude", "dimension"):
+            ref = getattr(self, field, None)
+            if ref is not None and (not ref.name or ref.is_metric):
+                raise ValueError(
+                    f"{field} requires a named dataset column, not a metric"
+                )
+        for field in ("metric", "secondary_metric", "radius_metric"):
+            ref = getattr(self, field, None)
+            if ref is not None and not ref.is_metric:
+                raise ValueError(
+                    f"{field} requires aggregate, saved_metric, or 
sql_expression"
+                )
+        # These helpers import chart schemas, so defer to avoid a cycle.
+        from superset.mcp_service.chart.chart_utils import create_metric_object
+        from superset.mcp_service.chart.query_result import metric_result_label
+
+        dimensions = {
+            ref.name
+            for field in ("entity", "latitude", "longitude", "dimension")
+            if (ref := getattr(self, field, None)) is not None
+        }
+        seen_metrics: dict[str | None, ColumnRef] = {}
+        for field in ("metric", "secondary_metric", "radius_metric"):
+            ref = getattr(self, field, None)
+            if ref is None:
+                continue
+            label = metric_result_label(create_metric_object(ref))

Review Comment:
   Fixed in 283553d753349769df8237df36874da62ed9987f. Scatter radius aliases 
`position`, `weight`, and `extraProps` are rejected by config validation 
because they collide with native spatial feature fields. Transform regressions 
cover all three labels, including `position`: the typed renderer skips these 
rows before the native spatial transform if a label is changed through Explore, 
rather than emitting a corrupt coordinate tuple. Existing valid metric-radius 
coverage remains passing. Validation: chart unit suite 3,249 passed, 3 skipped; 
related Jest tests 147 passed; touched-file pre-commit passed.



-- 
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