codeant-ai-for-open-source[bot] commented on code in PR #43943:
URL: https://github.com/apache/superset/pull/43943#discussion_r3944877027


##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -469,11 +457,424 @@ def _is_nan(value: Any) -> bool:
         return False
 
 
+_GAUGE_COLORS = (
+    "#1FA8C9",
+    "#454E7C",
+    "#5AC189",
+    "#FF7F44",
+    "#666666",
+    "#E04355",
+    "#FCC700",
+    "#A868B7",
+)
+
+
+def _gauge_column_label(column: Any) -> str | None:
+    """Resolve the result label for a native QueryFormColumn."""
+    if isinstance(column, str) and column:
+        return column
+    if not isinstance(column, dict) or not 0 < len(column) <= 20:
+        return None
+    label = (
+        column.get("label")
+        or column.get("sqlExpression")
+        or column.get("column_name")
+        or column.get("columnName")
+    )
+    return label if isinstance(label, str) and label else None
+
+
+def _parse_gauge_number_list(value: Any, field_name: str) -> list[float]:
+    """Parse a bounded comma-separated Gauge control."""
+    if value in (None, ""):
+        return []
+    if not isinstance(value, str) or len(value) > 1000:
+        raise ValueError(f"Gauge {field_name} must be a bounded string")
+    try:
+        parsed = [float(part.strip()) for part in value.split(",")]
+    except ValueError as ex:
+        raise ValueError(
+            f"Gauge {field_name} must be a comma-separated list of numbers"
+        ) from ex
+    if not all(math.isfinite(number) for number in parsed):
+        raise ValueError(f"Gauge {field_name} values must be finite")
+    return parsed
+
+
+def _prepare_gauge_preview(  # noqa: C901
+    data: Any, form_data: Dict[str, Any]
+) -> tuple[list[dict[str, Any]], dict[str, Any]] | ChartError:
+    """Validate Gauge rows and derive display values used by both previews."""
+    failure = validate_gauge_query_result({"queries": [{"data": data}]}, 
form_data)
+    if failure is not None:
+        return failure
+    assert isinstance(data, list)
+    metric_label = metric_result_label(form_data.get("metric"))
+    assert metric_label is not None
+
+    raw_groupby = form_data.get("groupby") or []
+    if isinstance(raw_groupby, str):
+        raw_groupby = [raw_groupby]
+    if not isinstance(raw_groupby, list) or len(raw_groupby) > 10:
+        return ChartError(
+            error="Gauge groupby must contain at most 10 column references.",
+            error_type="InvalidGaugeFormData",
+        )
+    group_labels: list[str] = []
+    for index, column in enumerate(raw_groupby):
+        label = _gauge_column_label(column)
+        if label is None:
+            return ChartError(
+                error=f"Gauge groupby[{index}] has no resolvable result 
label.",
+                error_type="InvalidGaugeFormData",
+            )
+        group_labels.append(label)
+
+    values = [float(row[metric_label]) for row in data]
+
+    def numeric_bound(field_name: str) -> float | None:
+        value = form_data.get(field_name)
+        if value in (None, ""):
+            return None
+        if isinstance(value, bool) or not isinstance(value, (int, float, str)):
+            raise ValueError(f"Gauge {field_name} must be numeric")
+        try:
+            converted = float(value)
+        except ValueError as ex:
+            raise ValueError(f"Gauge {field_name} must be numeric") from ex
+        if not math.isfinite(converted):
+            raise ValueError(f"Gauge {field_name} must be finite")
+        return converted
+
+    try:
+        minimum = numeric_bound("min_val")
+        maximum = numeric_bound("max_val")
+        interval_bounds = _parse_gauge_number_list(
+            form_data.get("intervals", ""), "intervals"
+        )
+        color_indices = _parse_gauge_number_list(
+            form_data.get("interval_color_indices", ""),
+            "interval_color_indices",
+        )
+    except ValueError as ex:
+        return ChartError(error=str(ex), error_type="InvalidGaugeFormData")
+
+    # Match transformProps auto-range semantics: twice the extrema including 0.
+    if minimum is None:
+        minimum = 2 * min([*values, 0]) if values else 0
+    if maximum is None:
+        maximum = 2 * max([*values, 0]) if values else 1
+    if minimum >= maximum:
+        return ChartError(
+            error=(
+                f"Gauge preview range is invalid: min_val {minimum:g} must be "
+                f"less than max_val {maximum:g}."
+            ),
+            error_type="InvalidGaugeRange",
+        )
+    if any(
+        left >= right
+        for left, right in zip(interval_bounds, interval_bounds[1:], 
strict=False)
+    ):
+        return ChartError(
+            error="Gauge intervals must be strictly increasing.",
+            error_type="InvalidGaugeFormData",
+        )
+    if any(bound <= minimum or bound > maximum for bound in interval_bounds):
+        return ChartError(
+            error="Gauge intervals must fall within the resolved min/max 
range.",
+            error_type="InvalidGaugeFormData",
+        )

Review Comment:
   **Suggestion:** With omitted bounds, a low current value can make the 
automatic maximum smaller than configured intervals, causing valid stable Gauge 
settings to be rejected during preview. [incorrect condition logic]
   
   **Assessment:** ๐ŸŸ  `Major` ยท ๐Ÿ” `Occurrence: Sometimes`
   
   [![Use CodeAnt 
Skill](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/use-codeant-skill-flat-v2.svg)](https://docs.codeant.ai/cli/resolve-pr-comments-skill)
 [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=7bbb982ed21a41b2b26c16eb70acb246&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=7bbb982ed21a41b2b26c16eb70acb246&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent ๐Ÿค– </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/chart/preview_utils.py
   **Line:** 562:587
   **Comment:**
        *Incorrect Condition Logic: With omitted bounds, a low current value 
can make the automatic maximum smaller than configured intervals, causing valid 
stable Gauge settings to be rejected during preview.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=8f7045b2f033636c8ea0ca436e5246761cbb1a8ef52473fd0ab3f6cddc144747&reaction=like'>๐Ÿ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=8f7045b2f033636c8ea0ca436e5246761cbb1a8ef52473fd0ab3f6cddc144747&reaction=dislike'>๐Ÿ‘Ž</a>



##########
superset/mcp_service/chart/preview_utils.py:
##########
@@ -469,11 +457,424 @@ def _is_nan(value: Any) -> bool:
         return False
 
 
+_GAUGE_COLORS = (
+    "#1FA8C9",
+    "#454E7C",
+    "#5AC189",
+    "#FF7F44",
+    "#666666",
+    "#E04355",
+    "#FCC700",
+    "#A868B7",
+)
+
+
+def _gauge_column_label(column: Any) -> str | None:
+    """Resolve the result label for a native QueryFormColumn."""
+    if isinstance(column, str) and column:
+        return column
+    if not isinstance(column, dict) or not 0 < len(column) <= 20:
+        return None
+    label = (
+        column.get("label")
+        or column.get("sqlExpression")
+        or column.get("column_name")
+        or column.get("columnName")
+    )
+    return label if isinstance(label, str) and label else None
+
+
+def _parse_gauge_number_list(value: Any, field_name: str) -> list[float]:
+    """Parse a bounded comma-separated Gauge control."""
+    if value in (None, ""):
+        return []
+    if not isinstance(value, str) or len(value) > 1000:
+        raise ValueError(f"Gauge {field_name} must be a bounded string")
+    try:
+        parsed = [float(part.strip()) for part in value.split(",")]
+    except ValueError as ex:
+        raise ValueError(
+            f"Gauge {field_name} must be a comma-separated list of numbers"
+        ) from ex
+    if not all(math.isfinite(number) for number in parsed):
+        raise ValueError(f"Gauge {field_name} values must be finite")
+    return parsed
+
+
+def _prepare_gauge_preview(  # noqa: C901
+    data: Any, form_data: Dict[str, Any]
+) -> tuple[list[dict[str, Any]], dict[str, Any]] | ChartError:
+    """Validate Gauge rows and derive display values used by both previews."""
+    failure = validate_gauge_query_result({"queries": [{"data": data}]}, 
form_data)
+    if failure is not None:
+        return failure
+    assert isinstance(data, list)
+    metric_label = metric_result_label(form_data.get("metric"))
+    assert metric_label is not None
+
+    raw_groupby = form_data.get("groupby") or []
+    if isinstance(raw_groupby, str):
+        raw_groupby = [raw_groupby]
+    if not isinstance(raw_groupby, list) or len(raw_groupby) > 10:
+        return ChartError(
+            error="Gauge groupby must contain at most 10 column references.",
+            error_type="InvalidGaugeFormData",
+        )
+    group_labels: list[str] = []
+    for index, column in enumerate(raw_groupby):
+        label = _gauge_column_label(column)
+        if label is None:
+            return ChartError(
+                error=f"Gauge groupby[{index}] has no resolvable result 
label.",
+                error_type="InvalidGaugeFormData",
+            )
+        group_labels.append(label)
+
+    values = [float(row[metric_label]) for row in data]
+
+    def numeric_bound(field_name: str) -> float | None:
+        value = form_data.get(field_name)
+        if value in (None, ""):
+            return None
+        if isinstance(value, bool) or not isinstance(value, (int, float, str)):
+            raise ValueError(f"Gauge {field_name} must be numeric")
+        try:
+            converted = float(value)
+        except ValueError as ex:
+            raise ValueError(f"Gauge {field_name} must be numeric") from ex
+        if not math.isfinite(converted):
+            raise ValueError(f"Gauge {field_name} must be finite")
+        return converted
+
+    try:
+        minimum = numeric_bound("min_val")
+        maximum = numeric_bound("max_val")
+        interval_bounds = _parse_gauge_number_list(
+            form_data.get("intervals", ""), "intervals"
+        )
+        color_indices = _parse_gauge_number_list(
+            form_data.get("interval_color_indices", ""),
+            "interval_color_indices",
+        )
+    except ValueError as ex:
+        return ChartError(error=str(ex), error_type="InvalidGaugeFormData")
+
+    # Match transformProps auto-range semantics: twice the extrema including 0.
+    if minimum is None:
+        minimum = 2 * min([*values, 0]) if values else 0
+    if maximum is None:
+        maximum = 2 * max([*values, 0]) if values else 1
+    if minimum >= maximum:
+        return ChartError(
+            error=(
+                f"Gauge preview range is invalid: min_val {minimum:g} must be "
+                f"less than max_val {maximum:g}."
+            ),
+            error_type="InvalidGaugeRange",
+        )
+    if any(
+        left >= right
+        for left, right in zip(interval_bounds, interval_bounds[1:], 
strict=False)
+    ):
+        return ChartError(
+            error="Gauge intervals must be strictly increasing.",
+            error_type="InvalidGaugeFormData",
+        )
+    if any(bound <= minimum or bound > maximum for bound in interval_bounds):
+        return ChartError(
+            error="Gauge intervals must fall within the resolved min/max 
range.",
+            error_type="InvalidGaugeFormData",
+        )
+    if color_indices and len(color_indices) != len(interval_bounds):
+        return ChartError(
+            error="Gauge interval colors must match the number of interval 
bounds.",
+            error_type="InvalidGaugeFormData",
+        )
+    if any(index < 1 or not index.is_integer() for index in color_indices):
+        return ChartError(
+            error="Gauge interval color indices must be positive integers.",
+            error_type="InvalidGaugeFormData",
+        )
+
+    currency = form_data.get("currency_format")
+    if currency is not None and not isinstance(currency, dict):
+        return ChartError(
+            error="Gauge currency_format must be an object.",
+            error_type="InvalidGaugeFormData",
+        )
+    number_format = form_data.get("number_format", "SMART_NUMBER")
+    value_formatter = form_data.get("value_formatter", "{value}")
+    if not isinstance(number_format, str) or not isinstance(value_formatter, 
str):
+        return ChartError(
+            error="Gauge number_format and value_formatter must be strings.",
+            error_type="InvalidGaugeFormData",
+        )
+    from superset.utils.number_format import format_number_with_config
+
+    decorated: list[dict[str, Any]] = []
+    for row_index, row in enumerate(data):
+        value = float(row[metric_label])
+        group = (
+            ", ".join(f"{label}: {row.get(label)}" for label in group_labels) 
or "Value"
+        )
+        formatted = format_number_with_config(number_format, currency, value)
+        decorated.append(
+            {
+                **row,
+                "__mcp_gauge_group": group,
+                "__mcp_gauge_ratio": (value - minimum) / (maximum - minimum),
+                "__mcp_gauge_display": value_formatter.replace(
+                    "{value}", str(formatted), 1
+                ),
+                "__mcp_gauge_row": row_index,
+            }
+        )
+
+    colors = [
+        _GAUGE_COLORS[(int(index) - 1) % len(_GAUGE_COLORS)] for index in 
color_indices
+    ] or list(_GAUGE_COLORS[: max(1, len(interval_bounds))])
+    return decorated, {
+        "metric_label": metric_label,
+        "group_labels": group_labels,
+        "minimum": minimum,
+        "maximum": maximum,
+        "interval_bounds": interval_bounds,
+        "interval_colors": colors,
+    }
+
+
+def generate_gauge_ascii_preview(
+    data: Any, form_data: Dict[str, Any], width: int = 80
+) -> str | ChartError:
+    """Render a bounded dial-like ASCII Gauge using the configured value 
format."""
+    prepared = _prepare_gauge_preview(data, form_data)
+    if isinstance(prepared, ChartError):
+        return prepared
+    decorated, metadata = prepared
+    minimum = metadata["minimum"]
+    maximum = metadata["maximum"]
+    lines = [
+        "Gauge Chart",
+        f"Range: {minimum:g} to {maximum:g}",
+    ]
+    if metadata["interval_bounds"]:
+        lines.append(
+            "Intervals: "
+            + ", ".join(f"{value:g}" for value in metadata["interval_bounds"])
+        )
+    if not decorated:
+        lines.append("No data available")
+        return "\n".join(lines)
+
+    width = min(max(width, 40), 200)
+    bar_width = max(10, min(40, width - 38))
+    for row in decorated[:10]:
+        ratio = min(1.0, max(0.0, row["__mcp_gauge_ratio"]))
+        filled = round(ratio * bar_width)
+        bar = "โ–ˆ" * filled + "โ–‘" * (bar_width - filled)
+        label = str(row["__mcp_gauge_group"])
+        lines.append(f"{label[:20]:>20} [{bar}] {row['__mcp_gauge_display']}")
+    return "\n".join(lines)
+
+
+def generate_gauge_vega_lite_preview(  # noqa: C901
+    data: Any, form_data: Dict[str, Any]
+) -> VegaLitePreview | ChartError:
+    """Build a Gauge-specific layered radial Vega-Lite preview."""
+    prepared = _prepare_gauge_preview(data, form_data)
+    if isinstance(prepared, ChartError):
+        return prepared
+    decorated, metadata = prepared
+    start_angle = form_data.get("start_angle", 225)
+    end_angle = form_data.get("end_angle", -45)
+
+    def finite_angle(value: Any) -> float | None:
+        if isinstance(value, bool) or not isinstance(value, (int, float, str)):
+            return None
+        try:
+            converted = float(value)
+        except ValueError:
+            return None
+        return converted if math.isfinite(converted) else None
+
+    numeric_start_angle = finite_angle(start_angle)
+    numeric_end_angle = finite_angle(end_angle)
+    if numeric_start_angle is None or numeric_end_angle is None:
+        return ChartError(
+            error="Gauge start_angle and end_angle must be finite numbers.",
+            error_type="InvalidGaugeFormData",
+        )
+
+    angle_range = [math.radians(numeric_start_angle), 
math.radians(numeric_end_angle)]
+    theta_scale = {"domain": [0, 1], "range": angle_range}
+    metric_label = metadata["metric_label"]
+    tooltip = [
+        {"field": field, "type": "nominal"} for field in 
metadata["group_labels"]
+    ]
+    tooltip.extend(
+        [
+            {"field": metric_label, "type": "quantitative"},
+            {"field": "__mcp_gauge_display", "type": "nominal", "title": 
"Value"},
+        ]
+    )
+    progress_color: dict[str, Any]
+    interval_bounds = metadata["interval_bounds"]
+    if interval_bounds:
+        progress_color = {
+            "field": metric_label,
+            "type": "quantitative",
+            "scale": {
+                "type": "threshold",
+                "domain": interval_bounds[:-1],
+                "range": metadata["interval_colors"],
+            },
+            "legend": None,
+        }
+    elif metadata["group_labels"]:
+        progress_color = {
+            "field": "__mcp_gauge_group",
+            "type": "nominal",
+            "legend": None,
+        }
+    else:
+        progress_color = {"value": _GAUGE_COLORS[0]}
+
+    background_layers: list[dict[str, Any]] = []
+    for bound, color in reversed(
+        list(zip(interval_bounds, metadata["interval_colors"], strict=False))
+    ):
+        background_layers.append(
+            {
+                "mark": {
+                    "type": "arc",
+                    "innerRadius": 55,
+                    "outerRadius": 82,
+                    "color": color,
+                    "opacity": 0.35,
+                },
+                "encoding": {
+                    "theta": {
+                        "datum": (bound - metadata["minimum"])
+                        / (metadata["maximum"] - metadata["minimum"]),
+                        "type": "quantitative",
+                        "scale": theta_scale,
+                    }
+                },
+            }

Review Comment:
   **Suggestion:** Each interval layer supplies only an endpoint angle, so 
Vega-Lite draws overlapping endpoint-sized arcs instead of threshold bands 
spanning adjacent ranges. [logic error]
   
   **Assessment:** ๐ŸŸ  `Major` ยท ๐Ÿ” `Occurrence: Sometimes`
   
   [![Use CodeAnt 
Skill](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/use-codeant-skill-flat-v2.svg)](https://docs.codeant.ai/cli/resolve-pr-comments-skill)
 [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=cb4d7718b03a434492c20b4f7dee2854&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=cb4d7718b03a434492c20b4f7dee2854&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent ๐Ÿค– </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/chart/preview_utils.py
   **Line:** 743:763
   **Comment:**
        *Logic Error: Each interval layer supplies only an endpoint angle, so 
Vega-Lite draws overlapping endpoint-sized arcs instead of threshold bands 
spanning adjacent ranges.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=a6a869de643ca1a52734cffc9032d77baf8b18e9e474d05b8eedac6ff4e1afe4&reaction=like'>๐Ÿ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=a6a869de643ca1a52734cffc9032d77baf8b18e9e474d05b8eedac6ff4e1afe4&reaction=dislike'>๐Ÿ‘Ž</a>



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -599,6 +599,145 @@ def merge_interactive_pivot_ui_config(
         new_form_data["pivot_table_state"] = {**existing_state, **new_state}
 
 
+_GAUGE_FORM_DATA_FIELD_MAP: dict[str, str] = {
+    "groupby": "groupby",
+    "sort_by_metric": "sort_by_metric",
+    "row_limit": "row_limit",
+    "min_val": "min_val",
+    "max_val": "max_val",
+    "color_scheme": "color_scheme",
+    "font_size": "font_size",
+    "number_format": "number_format",
+    "currency_format": "currency_format",
+    "value_formatter": "value_formatter",
+    "start_angle": "start_angle",
+    "end_angle": "end_angle",
+    "show_pointer": "show_pointer",
+    "animation": "animation",
+    "show_axis_tick": "show_axis_tick",
+    "show_split_line": "show_split_line",
+    "split_number": "split_number",
+    "show_progress": "show_progress",
+    "overlap": "overlap",
+    "round_cap": "round_cap",
+    "intervals": "intervals",
+    "interval_color_indices": "interval_color_indices",
+    "time_range": "time_range",
+    "granularity_sqla": "granularity_sqla",
+}
+
+_GAUGE_PRESENTATION_FORM_DATA_KEYS = frozenset(
+    {
+        "min_val",
+        "max_val",
+        "color_scheme",
+        "font_size",
+        "number_format",
+        "currency_format",
+        "value_formatter",
+        "start_angle",
+        "end_angle",
+        "show_pointer",
+        "animation",
+        "show_axis_tick",
+        "show_split_line",
+        "split_number",
+        "show_progress",
+        "overlap",
+        "round_cap",
+        "intervals",
+        "interval_color_indices",
+    }
+)
+
+
+def _without_generated_gauge_time_filter(
+    form_data: dict[str, Any],
+) -> list[Any]:
+    """Return cached filters without the mapper-owned temporal binding."""
+    generated_subject = form_data.get(MCP_DASHBOARD_TIME_FILTER_SUBJECT)
+    return [
+        filter_
+        for filter_ in form_data.get("adhoc_filters", [])
+        if not (
+            generated_subject
+            and isinstance(filter_, dict)
+            and filter_.get("operator") == FilterOperator.TEMPORAL_RANGE.value
+            and filter_.get("subject") == generated_subject

Review Comment:
   **Suggestion:** `_without_generated_gauge_time_filter` removes every 
temporal filter for the recorded subject, including user-authored ranges, when 
preserving filters during a Gauge update. [logic error]
   
   **Assessment:** ๐ŸŸ  `Major` ยท ๐Ÿ” `Occurrence: Sometimes`
   
   [![Use CodeAnt 
Skill](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/use-codeant-skill-flat-v2.svg)](https://docs.codeant.ai/cli/resolve-pr-comments-skill)
 [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=d8f203891dc54e2dac5ca027c90e823a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=d8f203891dc54e2dac5ca027c90e823a&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent ๐Ÿค– </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/chart/chart_utils.py
   **Line:** 665:666
   **Comment:**
        *Logic Error: `_without_generated_gauge_time_filter` removes every 
temporal filter for the recorded subject, including user-authored ranges, when 
preserving filters during a Gauge update.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=3fabf15bbe253a186fb0bc3ff9db0848e8cb88d899b5157b2c078a744819ee98&reaction=like'>๐Ÿ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=3fabf15bbe253a186fb0bc3ff9db0848e8cb88d899b5157b2c078a744819ee98&reaction=dislike'>๐Ÿ‘Ž</a>



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1093,10 +1097,168 @@ class GaugeChartConfig(BaseChartConfig):
         ),
         max_length=100,
     )
+    font_size: int = Field(15, description="Gauge text size", ge=10, le=20)
+    number_format: str = Field(
+        "SMART_NUMBER", description="D3 number format", max_length=50
+    )
+    currency_format: CurrencyFormat | None = Field(
+        None, description="Currency symbol applied to the gauge value"
+    )
+    value_formatter: str = Field(
+        "{value}",
+        description="Value template; {value} is replaced with the formatted 
metric",
+        max_length=200,
+    )
+    start_angle: float = Field(225, description="Gauge start angle in degrees")
+    end_angle: float = Field(-45, description="Gauge end angle in degrees")
+    show_pointer: bool = Field(True, description="Show the gauge pointer")
+    animation: bool = Field(True, description="Animate gauge value changes")
+    show_axis_tick: bool = Field(False, description="Show minor axis ticks")
+    show_split_line: bool = Field(False, description="Show axis split lines")
+    split_number: int = Field(10, description="Number of axis segments", ge=3, 
le=30)
+    show_progress: bool = Field(True, description="Show the progress arc")
+    overlap: bool = Field(
+        True, description="Overlap progress arcs when multiple groups are 
present"
+    )
+    round_cap: bool = Field(False, description="Use rounded progress-arc caps")
+    intervals: str = Field(
+        "",
+        description="Comma-separated interval upper bounds",
+        max_length=1000,
+    )
+    interval_color_indices: str = Field(
+        "",
+        description="Comma-separated 1-based color indices for intervals",
+        max_length=1000,
+    )
+    time_range: str | None = Field(
+        None,
+        description="Optional Superset time range applied to the gauge query",
+        max_length=1000,
+    )
+    granularity_sqla: str | None = Field(
+        None,
+        description="Temporal column associated with time_range in native 
form_data",
+        min_length=1,
+        max_length=255,
+    )
+
+    @model_validator(mode="before")
+    @classmethod
+    def adapt_native_form_data(cls, data: Any) -> Any:  # noqa: C901
+        """Accept the Gauge plugin's native form_data without weakening 
typing."""
+        if not isinstance(data, dict):
+            return data
+        data = dict(data)
+
+        # ``gauge`` is the public MCP discriminator; ``gauge_chart`` remains
+        # the native frontend viz_type and is accepted only as an input alias.
+        if data.get("chart_type") == "gauge_chart" or (
+            "chart_type" not in data and data.get("viz_type") == "gauge_chart"
+        ):
+            data["chart_type"] = "gauge"
+        data.pop("viz_type", None)
+
+        # These identify the Explore/chart envelope, not Gauge controls.
+        for key in (
+            "datasource",
+            "datasource_id",
+            "datasource_name",
+            "datasource_type",
+            "form_data_key",
+            "slice_id",
+            "slice_name",
+            "url",
+        ):
+            data.pop(key, None)
+        data.pop("_mcp_dashboard_time_filter_subject", None)
+
+        metric = data.get("metric")
+        if isinstance(metric, str):
+            data["metric"] = {"name": metric, "saved_metric": True}
+        elif isinstance(metric, dict) and metric.get("expressionType") in {
+            "SIMPLE",
+            "SQL",
+        }:
+            expression_type = metric.get("expressionType")
+            if expression_type == "SQL":
+                data["metric"] = {
+                    "sql_expression": metric.get("sqlExpression"),
+                    "label": metric.get("label"),
+                }
+            else:
+                column = metric.get("column")
+                column_name = (
+                    column.get("column_name") or column.get("columnName")
+                    if isinstance(column, dict)
+                    else None
+                )
+                data["metric"] = {
+                    "name": column_name,
+                    "aggregate": metric.get("aggregate"),
+                    "label": metric.get("label"),
+                }
+
+        groupby = data.get("groupby")
+        if isinstance(groupby, str):
+            groupby = [groupby]
+        if isinstance(groupby, list):
+            data["groupby"] = [
+                {"name": value} if isinstance(value, str) else value
+                for value in groupby
+            ]
+
+        # Native SIMPLE filters are losslessly representable by FilterConfig.
+        # SQL adhoc filters remain intentionally unsupported on the typed MCP
+        # surface. TEMPORAL_RANGE is represented by time_range/granularity.
+        if "adhoc_filters" in data:
+            if "filters" in data:
+                raise ValueError("Use either filters or adhoc_filters, not 
both")
+            native_filters = data.pop("adhoc_filters")
+            if not isinstance(native_filters, list):
+                raise ValueError("adhoc_filters must be a list")
+            filters: list[dict[str, Any]] = []
+            for index, filter_ in enumerate(native_filters):
+                if not isinstance(filter_, dict):
+                    raise ValueError(f"adhoc_filters[{index}] must be an 
object")
+                if filter_.get("expressionType") not in (None, "SIMPLE"):
+                    raise ValueError(
+                        f"adhoc_filters[{index}] must use 
expressionType='SIMPLE'"
+                    )
+                if str(filter_.get("clause", "WHERE")).upper() != "WHERE":
+                    raise ValueError(f"adhoc_filters[{index}] must use 
clause='WHERE'")
+                operator = filter_.get("operator") or filter_.get("op")
+                subject = filter_.get("subject") or filter_.get("col")
+                comparator = filter_.get("comparator", filter_.get("val"))
+                if operator == "TEMPORAL_RANGE":
+                    if not isinstance(subject, str) or not subject:
+                        raise ValueError(
+                            f"adhoc_filters[{index}] has no temporal subject"
+                        )
+                    data.setdefault("granularity_sqla", subject)
+                    data.setdefault("time_range", comparator)
+                    continue

Review Comment:
   **Suggestion:** A `TEMPORAL_RANGE` filter with no comparator is accepted, 
then its filter is discarded and `time_range` remains unset, so the query 
silently loses the requested time restriction. [incomplete implementation]
   
   **Assessment:** ๐ŸŸ  `Major` ยท ๐Ÿ” `Occurrence: Sometimes`
   
   [![Use CodeAnt 
Skill](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/use-codeant-skill-flat-v2.svg)](https://docs.codeant.ai/cli/resolve-pr-comments-skill)
 [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=a1ebd438075d4382935506bb8750cb7d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=a1ebd438075d4382935506bb8750cb7d&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   <details>
   <summary><b>Prompt for AI Agent ๐Ÿค– </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/chart/schemas.py
   **Line:** 1238:1240
   **Comment:**
        *Incomplete Implementation: A `TEMPORAL_RANGE` filter with no 
comparator is accepted, then its filter is discarded and `time_range` remains 
unset, so the query silently loses the requested time restriction.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=e91d530e16f1ce1f37755004b85cfbda2216bd240876203eeed731f5148e2270&reaction=like'>๐Ÿ‘</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F43943&comment_hash=e91d530e16f1ce1f37755004b85cfbda2216bd240876203eeed731f5148e2270&reaction=dislike'>๐Ÿ‘Ž</a>



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