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


##########
superset/mcp_service/dashboard/schemas.py:
##########
@@ -1770,15 +1777,122 @@ def serialize_chart_summary(
     )
 
 
+def _native_filter_value_is_valid(filter_type: str, value: Any) -> bool:
+    """Validate display-value shapes without interpreting them as 
predicates."""
+    if value is None:
+        return True
+    if filter_type == "filter_time":
+        return isinstance(value, str)
+    if filter_type == "filter_range":
+        return (
+            isinstance(value, list)
+            and len(value) == 2
+            and all(
+                item is None
+                or (isinstance(item, (int, float)) and not isinstance(item, 
bool))
+                for item in value
+            )
+        )
+    if filter_type == "filter_timegrain":
+        return (
+            isinstance(value, list)
+            and len(value) <= 1
+            and all(isinstance(item, str) for item in value)
+        )
+    # Select values may be JSON scalars or flat scalar lists, never nested 
metadata.
+    values = value if isinstance(value, list) else [value]
+    return all(
+        item is None or isinstance(item, (str, int, float, bool)) for item in 
values
+    )
+
+
 def redact_filter_state_data_model_metadata(
     filter_state: Dict[str, Any],
+    native_filters: list[NativeFilterSummary] | None = None,
 ) -> Dict[str, Any]:
-    """Remove permalink filter state fields that expose data-model metadata."""
-    return {
+    """Hide raw metadata, retaining known native filters' display values.
+
+    Match IDs and types against dashboard configuration, not caller-supplied
+    mask metadata. Time-column and custom filters can carry column names even
+    in their value or label, and therefore are not projected.
+    """
+    result = {
         key: value
         for key, value in filter_state.items()
-        if key not in {"dataMask", "chartStates"}
+        if key
+        not in {
+            "dataMask",
+            "chartStates",
+            "native_filter_values",
+            "native_filter_values_incomplete",
+        }
     }
+    if native_filters is None or not (
+        {"dataMask", "chartStates"} & filter_state.keys()
+    ):
+        return result
+
+    summaries: list[dict[str, Any]] = []
+    mask = filter_state.get("dataMask", {})
+    incomplete = bool(filter_state.get("chartStates")) or not isinstance(mask, 
dict)
+    known_filters = {item.id: item for item in native_filters}
+    for filter_id, entry in mask.items() if isinstance(mask, dict) else []:
+        native_filter = known_filters.get(filter_id)
+        if (
+            native_filter is None
+            or native_filter.filter_type
+            not in {
+                "filter_select",
+                "filter_range",
+                "filter_time",
+                "filter_timegrain",
+            }
+            or not isinstance(entry, dict)
+            or not isinstance(entry.get("filterState"), dict)
+            or "value" not in entry["filterState"]
+        ):
+            incomplete = True
+            continue
+        extra = entry.get("extraFormData", {})
+        # Filter IDs survive type changes. A saved time-column mask can contain
+        # column names even when the dashboard config describes a supported 
type.
+        if not isinstance(extra, dict) or "granularity_sqla" in extra:
+            incomplete = True
+            continue

Review Comment:
   Addressed in b17d9977d6. Empty extraFormData is not sufficient to prove that 
a filter was cleared: caller-supplied display-only state is also supported. 
Retained values with empty or missing extraFormData are now marked incomplete, 
without guessing whether a predicate is active. Explicitly cleared 
null/empty-list values remain supported. Added regression coverage for all four 
supported types and documented this distinction.



##########
superset/mcp_service/dashboard/schemas.py:
##########
@@ -1770,15 +1777,122 @@ def serialize_chart_summary(
     )
 
 
+def _native_filter_value_is_valid(filter_type: str, value: Any) -> bool:
+    """Validate display-value shapes without interpreting them as 
predicates."""
+    if value is None:
+        return True
+    if filter_type == "filter_time":
+        return isinstance(value, str)
+    if filter_type == "filter_range":
+        return (
+            isinstance(value, list)
+            and len(value) == 2
+            and all(
+                item is None
+                or (isinstance(item, (int, float)) and not isinstance(item, 
bool))
+                for item in value
+            )
+        )
+    if filter_type == "filter_timegrain":
+        return (
+            isinstance(value, list)
+            and len(value) <= 1
+            and all(isinstance(item, str) for item in value)
+        )
+    # Select values may be JSON scalars or flat scalar lists, never nested 
metadata.
+    values = value if isinstance(value, list) else [value]
+    return all(
+        item is None or isinstance(item, (str, int, float, bool)) for item in 
values
+    )
+
+
 def redact_filter_state_data_model_metadata(
     filter_state: Dict[str, Any],
+    native_filters: list[NativeFilterSummary] | None = None,
 ) -> Dict[str, Any]:
-    """Remove permalink filter state fields that expose data-model metadata."""
-    return {
+    """Hide raw metadata, retaining known native filters' display values.
+
+    Match IDs and types against dashboard configuration, not caller-supplied
+    mask metadata. Time-column and custom filters can carry column names even
+    in their value or label, and therefore are not projected.
+    """
+    result = {
         key: value
         for key, value in filter_state.items()
-        if key not in {"dataMask", "chartStates"}
+        if key
+        not in {
+            "dataMask",
+            "chartStates",
+            "native_filter_values",
+            "native_filter_values_incomplete",
+        }
     }
+    if native_filters is None or not (
+        {"dataMask", "chartStates"} & filter_state.keys()
+    ):
+        return result
+
+    summaries: list[dict[str, Any]] = []
+    mask = filter_state.get("dataMask", {})
+    incomplete = bool(filter_state.get("chartStates")) or not isinstance(mask, 
dict)
+    known_filters = {item.id: item for item in native_filters}
+    for filter_id, entry in mask.items() if isinstance(mask, dict) else []:
+        native_filter = known_filters.get(filter_id)
+        if (
+            native_filter is None
+            or native_filter.filter_type
+            not in {
+                "filter_select",
+                "filter_range",
+                "filter_time",
+                "filter_timegrain",
+            }
+            or not isinstance(entry, dict)
+            or not isinstance(entry.get("filterState"), dict)
+            or "value" not in entry["filterState"]
+        ):
+            incomplete = True
+            continue
+        extra = entry.get("extraFormData", {})
+        # Filter IDs survive type changes. A saved time-column mask can contain
+        # column names even when the dashboard config describes a supported 
type.
+        if not isinstance(extra, dict) or "granularity_sqla" in extra:
+            incomplete = True
+            continue
+        # A display value does not describe SQL predicates or wildcard
+        # matching. Signal that the summary cannot express these semantics.
+        predicates = extra.get("filters", [])
+        if extra.get("adhoc_filters") or (
+            native_filter.filter_type == "filter_select"
+            and (
+                not isinstance(predicates, list)
+                or any(
+                    not isinstance(predicate, dict)
+                    or predicate.get("op") not in ("IN", "NOT IN")
+                    for predicate in predicates
+                )
+            )
+        ):

Review Comment:
   Fixed in b17d9977d6. Predicate validation now covers every supported type: 
select allows IN/NOT IN, range allows >=/<=/==, and time/time-grain filters do 
not accept column-filter predicates as complete context. Malformed predicate 
lists and unsupported operators mark the summary incomplete. Added negative 
coverage across all four types and positive coverage for built-in predicates. 
All 536 dashboard tests and changed-file pre-commit hooks pass.



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