codeant-ai-for-open-source[bot] commented on code in PR #41860: URL: https://github.com/apache/superset/pull/41860#discussion_r3575715243
########## superset/mcp_service/chart/plugins/box_plot.py: ########## @@ -0,0 +1,155 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Box plot chart type plugin.""" + +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, ClassVar + +from superset.mcp_service.chart.chart_utils import ( + _summarize_filters, + map_box_plot_config, +) +from superset.mcp_service.chart.plugin import BaseChartPlugin +from superset.mcp_service.chart.schemas import BoxPlotChartConfig, ColumnRef +from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator +from superset.mcp_service.common.error_schemas import ChartGenerationError + + +class BoxPlotChartPlugin(BaseChartPlugin): + """Plugin for box plot chart type.""" + + chart_type = "box_plot" + display_name = "Box Plot" + native_viz_types: ClassVar[Mapping[str, str]] = { + "box_plot": "Box Plot", + } + + def pre_validate( + self, + config: dict[str, Any], + ) -> ChartGenerationError | None: + missing_fields = [] + if "metrics" not in config: + missing_fields.append("'metrics' (values whose spread is plotted)") + if "distribute_across" not in config: + missing_fields.append( + "'distribute_across' (columns whose values form the samples " + "inside each box, e.g. a temporal column)" + ) Review Comment: **Suggestion:** `pre_validate` only recognizes `distribute_across`, but the schema intentionally accepts the frontend alias `columns` for the same field. Requests that send valid box-plot configs using `columns` (for example copied from existing Superset form_data) will be rejected before Pydantic parsing with a false “missing required field” error. Update this check to treat either key as satisfying the requirement. [api mismatch] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx - ❌ MCP generate_chart fails for box_plot using columns. - ⚠️ Copying frontend form_data yields spurious validation errors. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. In the MCP service, locate the generate_chart tool implementation at `superset/mcp_service/chart/tool/generate_chart.py:22x-26x`, where it calls `ValidationPipeline.validate_request_with_warnings(request.model_dump())` before creating the chart. 2. Follow the call into `ValidationPipeline.validate_request_with_warnings` at `superset/mcp_service/chart/validation/pipeline.py:96-106`, which in turn calls `SchemaValidator.validate_request(request_data)` (see `schema_validator.py:36-45`). 3. In `SchemaValidator._pre_validate_chart_type` at `superset/mcp_service/chart/validation/schema_validator.py:111-124`, observe that the registry plugin for `chart_type="box_plot"` is fetched and `plugin.pre_validate(config)` is invoked at line 196 before any Pydantic parsing. 4. Inspect `BoxPlotChartPlugin.pre_validate` in `superset/mcp_service/chart/plugins/box_plot.py:48-55`, which only checks `if "metrics" not in config` and `if "distribute_across" not in config`, appending a missing-field error if `distribute_across` is absent, without considering the alias key `columns`. 5. Inspect the box plot schema at `superset/mcp_service/chart/schemas.py:18-25`, where `distribute_across` is declared with `validation_alias=AliasChoices("distribute_across", "columns")`, meaning Pydantic will accept either `distribute_across` or `columns` when parsing a `BoxPlotChartConfig`. 6. Call the MCP `generate_chart` tool (documented in `superset/mcp_service/app.py:180-101`) with a request such as: `{"dataset_id": 1, "config": {"chart_type": "box_plot", "metrics": [{"name": "fare", "aggregate": "AVG"}], "columns": [{"name": "month"}]}}`, matching the frontend box plot form_data shape that uses `columns`. 7. During validation, `_pre_validate_chart_type` passes `config` to `BoxPlotChartPlugin.pre_validate`; because `config` contains `columns` but not `distribute_across`, the condition `if "distribute_across" not in config` at `box_plot.py:51` fires, `missing_fields` is populated, and a `ChartGenerationError` with `error_type="missing_box_plot_fields"` is returned at `box_plot.py:59-78`. 8. `SchemaValidator.validate_request` returns `(False, None, error)` (see `schema_validator.py:14-26`), `ValidationPipeline.validate_request_with_warnings` propagates this failure (pipeline.py:99-113), and `generate_chart` returns a `GenerateChartResponse` with `success=False` and the `missing_box_plot_fields` error, even though the same config would be accepted by `BoxPlotChartConfig` thanks to the `columns` validation alias, demonstrating a real alias-handling bug in `pre_validate`. ``` </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=bcabd75ee1e041688a09c4d5979bdba3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=bcabd75ee1e041688a09c4d5979bdba3&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <details> <summary><b>Prompt for AI Agent 🤖 </b></summary> ```mdx This is a comment left during a code review. **Path:** superset/mcp_service/chart/plugins/box_plot.py **Line:** 51:55 **Comment:** *Api Mismatch: `pre_validate` only recognizes `distribute_across`, but the schema intentionally accepts the frontend alias `columns` for the same field. Requests that send valid box-plot configs using `columns` (for example copied from existing Superset form_data) will be rejected before Pydantic parsing with a false “missing required field” error. Update this check to treat either key as satisfying the requirement. 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%2F41860&comment_hash=13ee6f8fcd0ae5baf97edfb25a9c0a0b7128e449088e903fd15b35d0125744fb&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=13ee6f8fcd0ae5baf97edfb25a9c0a0b7128e449088e903fd15b35d0125744fb&reaction=dislike'>👎</a> ########## superset/mcp_service/chart/schemas.py: ########## @@ -1846,6 +1847,164 @@ def validate_unique_column_labels(self) -> "XYChartConfig": return self +class HistogramChartConfig(UnknownFieldCheckMixin): + """Config for histogram charts (viz_type ``histogram_v2``).""" + + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + chart_type: Literal["histogram"] = "histogram" + column: ColumnRef = Field( + ..., + description="Numeric column to bin (a physical dataset column)", + ) + groupby: List[ColumnRef] | None = Field( + None, + description="Optional dimensions to split the distribution into series", + ) + bins: int = Field(5, description="Number of histogram bins", ge=1, le=1000) + normalize: bool = Field(False, description="Normalize bin counts to proportions") + cumulative: bool = Field(False, description="Accumulate bin counts left to right") + filters: List[FilterConfig] | None = Field( + None, + description="Structured filters (column/op/value). " + "Do NOT use adhoc_filters or raw SQL expressions.", + ) + row_limit: int = Field(10000, description="Max rows sampled", ge=1, le=100000) + + @model_validator(mode="after") + def reject_metric_style_column(self) -> "HistogramChartConfig": + """The binned column is a physical column, not a metric.""" + _reject_sql_expression_on_dimension(self.column, "column") + if self.column and self.column.saved_metric: + raise ValueError( + "column cannot use saved_metric=True; histograms bin a " + "physical numeric column" + ) + for i, col in enumerate(self.groupby or []): + _reject_sql_expression_on_dimension(col, f"groupby[{i}]") + if col.saved_metric: + raise ValueError( + f"groupby[{i}] cannot use saved_metric=True; " + "saved metrics are not dimensions" + ) + return self + + +class BoxPlotChartConfig(UnknownFieldCheckMixin): + """Config for box plot charts (viz_type ``box_plot``).""" + + model_config = ConfigDict(extra="ignore", populate_by_name=True) + + chart_type: Literal["box_plot"] = "box_plot" + metrics: List[ColumnRef] = Field( + ..., + min_length=1, + description="Metrics whose distributions are plotted (use aggregate " + "e.g. AVG, SUM for ad-hoc, or saved_metric=True for saved metrics)", + ) + distribute_across: List[ColumnRef] = Field( + ..., + min_length=1, + validation_alias=AliasChoices("distribute_across", "columns"), + description="Columns whose distinct values form the SAMPLES inside " + "each box (typically a temporal column such as month) — the " + "distribution is computed across these values; maps to the " + "frontend's 'Distribute across' control (form_data 'columns'). " + "This does NOT split boxes; use 'dimensions' for that.", + ) + dimensions: List[ColumnRef] | None = Field( + None, + validation_alias=AliasChoices("dimensions", "groupby"), + description="Columns whose values split the chart into boxes — one " + "box per value on the x-axis (form_data 'groupby'). Omit for a " + "single box showing each metric's overall distribution.", + ) + whisker_type: Literal["tukey", "min_max", "percentile"] = Field( + "tukey", + description="Whisker algorithm: 'tukey' (1.5 IQR), 'min_max' (no " + "outliers), or 'percentile' (requires percentile_low/percentile_high)", + ) + percentile_low: int | None = Field( + None, description="Lower whisker percentile (0-100)", ge=0, le=100 + ) + percentile_high: int | None = Field( + None, description="Upper whisker percentile (0-100)", ge=0, le=100 + ) + filters: List[FilterConfig] | None = Field( + None, + description="Structured filters (column/op/value). " + "Do NOT use adhoc_filters or raw SQL expressions.", + ) + row_limit: int = Field( + 10000, + description="Max grouped rows (frontend shared default)", + ge=1, + le=50000, + ) + number_format: str = Field("SMART_NUMBER", max_length=50) + date_format: str = Field("smart_date", max_length=50) + + @model_validator(mode="before") + @classmethod + def accept_frontend_whisker_options(cls, data: Any) -> Any: + """Translate the frontend's whiskerOptions strings ('Tukey', + 'Min/max (no outliers)', '<low>/<high> percentiles') so configs + copied from existing Superset form_data are accepted rather than + refused.""" + if ( + isinstance(data, dict) + and "whiskerOptions" in data + and "whisker_type" not in data + ): Review Comment: **Suggestion:** The pre-normalizer only translates `whiskerOptions` when `whisker_type` is absent, so requests that include both keys leave `whiskerOptions` unconsumed and then fail unknown-field validation from `UnknownFieldCheckMixin`. This breaks compatibility for clients that pass through existing form_data while also setting explicit `whisker_type`; consume/remove `whiskerOptions` even when `whisker_type` is present (and optionally validate consistency) to avoid false validation errors. [api mismatch] <details> <summary><b>Severity Level:</b> Major ⚠️</summary> ```mdx ❌ MCP generate_chart fails for box_plot configs with duplicate whiskers. ⚠️ LLM clients copying form_data hit unexpected validation error. ``` </details> <details> <summary><b>Steps of Reproduction ✅ </b></summary> ```mdx 1. Start the Superset MCP service and call the `generate_chart` MCP tool defined in `superset/mcp_service/chart/tool/generate_chart.py:98-100` via the MCP server surface documented in `superset/mcp_service/app.py:182-214`, sending a JSON request with top-level `request` containing `dataset_id` and `config`. 2. In the request payload, set `config.chart_type` to `"box_plot"` (as described in `app.py:210-213`) and provide a config dict that includes required fields `metrics` and `distribute_across` plus both `"whisker_type": "tukey"` and `"whiskerOptions": "Tukey"`, matching the `BoxPlotChartConfig` schema in `superset/mcp_service/chart/schemas.py:1893-1945`. 3. Pydantic constructs `GenerateChartRequest` (`schemas.py:2193-2197`), whose `config` field is the discriminated union `ChartConfig` (`schemas.py:2008-2027`); based on `chart_type="box_plot"`, Pydantic chooses `BoxPlotChartConfig`, which inherits `UnknownFieldCheckMixin` (`schemas.py:782-789`) so its `check_unknown_fields()` model-level `mode="before"` validator runs on the raw config data. 4. Because `accept_frontend_whisker_options()` (`schemas.py:1947-1974) only pops `"whiskerOptions"` when `"whisker_type"` is absent, a config containing both keys leaves `"whiskerOptions"` in the data; `_check_unknown_fields()` (`schemas.py:757-779`) computes the known field/alias set (which does not include `"whiskerOptions"`) and raises `ValueError("Unknown field 'whiskerOptions' — did you mean 'whisker_type'?")`, causing the `generate_chart` request using combined legacy+canonical whisker fields to be rejected with a validation error instead of succeeding. ``` </details> [](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ccc8d82e96e74d14812c0db02f8087b9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) [](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ccc8d82e96e74d14812c0db02f8087b9&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset) *(Use Cmd/Ctrl + Click for best experience)* <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:** 1954:1958 **Comment:** *Api Mismatch: The pre-normalizer only translates `whiskerOptions` when `whisker_type` is absent, so requests that include both keys leave `whiskerOptions` unconsumed and then fail unknown-field validation from `UnknownFieldCheckMixin`. This breaks compatibility for clients that pass through existing form_data while also setting explicit `whisker_type`; consume/remove `whiskerOptions` even when `whisker_type` is present (and optionally validate consistency) to avoid false validation errors. 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%2F41860&comment_hash=82fc4a0a25bc050459b7b08664b53e89f737c5af449b83a7f10a8ffae91a133d&reaction=like'>👍</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=82fc4a0a25bc050459b7b08664b53e89f737c5af449b83a7f10a8ffae91a133d&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]
