You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by "gkneighb (via GitHub)" <gi...@apache.org> on 2026/07/07 17:23:20 UTC

[PR] feat(mcp): histogram and box plot chart type plugins [superset]

gkneighb opened a new pull request, #41860:
URL: https://github.com/apache/superset/pull/41860

   ### SUMMARY
   
   Adds **histogram** and **box plot** chart type plugins to the MCP `generate_chart` registry (#39922), extending the creatable chart types from 7 families to 9. These are the first statistical charts the MCP service can generate β€” agents doing exploratory analysis previously had no way to look at a value distribution or compare spreads.
   
   Design notes:
   
   - **Follows the plugin contract exactly**: each type is a self-contained `ChartTypePlugin` under `superset/mcp_service/chart/plugins/` (schema pre-validation with LLM-friendly fix suggestions, column-ref extraction/canonicalization, form_data mapping, name generation), registered in `plugins/__init__.py`. No registry or core changes.
   - **form_data matches the frontend buildQuery contracts** for `viz_type="histogram_v2"` (single `column` string, `groupby` names, `bins`/`normalize`/`cumulative` β€” bins default 5, same as the controlPanel) and `viz_type="box_plot"` (`columns` = distribute-across values, `groupby` = series dimensions, `metrics`, `whiskerOptions`). Box plot whiskers use an enum-style `whisker_type` (`tukey` / `min_max` / `percentile` with validated `percentile_low < percentile_high`) that serializes to the exact strings the frontend `boxplotOperator` parses, including its `"<low>/<high> percentiles"` regex format β€” an LLM never has to guess the magic strings.
   - **Config schemas are strict** (unknown fields rejected, SQL expressions rejected on dimension-position columns, saved_metric rejected on the histogram's binned column) with descriptive Pydantic errors, consistent with the existing chart configs.
   - `histogram_v2` added to the viz_type→chart_type alias map so clients that send Superset's public viz name instead of the discriminator get translated rather than rejected; `get_chart_type_schema` gains both types with examples; `generate_chart` docstring and server instructions updated.
   
   Part of a series extending MCP chart-type coverage (registry groundwork: #39922).
   
   ### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
   
   N/A (MCP service; no UI changes)
   
   ### TESTING INSTRUCTIONS
   
   ```bash
   pytest tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py  # 24 new tests
   pytest tests/unit_tests/mcp_service/chart/                                  # full chart suite
   ```
   
   Manual: via an MCP client, `generate_chart` with
   `{"dataset_id": <id>, "config": {"chart_type": "histogram", "column": {"name": "<numeric_col>"}, "bins": 20}}`
   returns a working preview; same for
   `{"chart_type": "box_plot", "metrics": [{"name": "<col>", "aggregate": "AVG"}], "distribute_across": [{"name": "<category_col>"}]}`.
   
   ### ADDITIONAL INFORMATION
   
   - [ ] Has associated issue:
   - [ ] Required feature flags:
   - [ ] Changes UI
   - [ ] Includes DB Migration (follow approval process in [SIP-59](https://github.com/apache/superset/issues/13351))
     - [ ] Migration is atomic, supports rollback & is backwards-compatible
     - [ ] Confirm DB migration upgrade and downgrade tested
     - [ ] Runtime estimates and downtime expectations provided
   - [x] Introduces new feature or API
   - [ ] Removes existing feature or API
   
   πŸ€– Generated with [Claude Code](https://claude.com/claude-code)
   
   https://claude.ai/code/session_014z1fQxTadTEvpCJLx9WnB8
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codecov[bot] (via GitHub)" <gi...@apache.org>.
codecov[bot] commented on PR #41860:
URL: https://github.com/apache/superset/pull/41860#issuecomment-4906883141

   ## [Codecov](https://app.codecov.io/gh/apache/superset/pull/41860?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) Report
   :x: Patch coverage is `43.37349%` with `94 lines` in your changes missing coverage. Please review.
   :white_check_mark: Project coverage is 55.91%. Comparing base ([`04f8b70`](https://app.codecov.io/gh/apache/superset/commit/04f8b700d762db56df951a4c746732f347e29a24?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)) to head ([`5332ec9`](https://app.codecov.io/gh/apache/superset/commit/5332ec9754a50af5ba3ff4fa88571cc1a4840d60?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache)).
   :warning: Report is 4 commits behind head on master.
   
   | [Files with missing lines](https://app.codecov.io/gh/apache/superset/pull/41860?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Patch % | Lines |
   |---|---|---|
   | [superset/mcp\_service/chart/plugins/box\_plot.py](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&filepath=superset%2Fmcp_service%2Fchart%2Fplugins%2Fbox_plot.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvbWNwX3NlcnZpY2UvY2hhcnQvcGx1Z2lucy9ib3hfcGxvdC5weQ==) | 32.75% | [39 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [superset/mcp\_service/chart/plugins/histogram.py](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&filepath=superset%2Fmcp_service%2Fchart%2Fplugins%2Fhistogram.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvbWNwX3NlcnZpY2UvY2hhcnQvcGx1Z2lucy9oaXN0b2dyYW0ucHk=) | 40.42% | [28 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [superset/mcp\_service/chart/schemas.py](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&filepath=superset%2Fmcp_service%2Fchart%2Fschemas.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvbWNwX3NlcnZpY2UvY2hhcnQvc2NoZW1hcy5weQ==) | 60.00% | [18 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   | [superset/mcp\_service/chart/chart\_utils.py](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&filepath=superset%2Fmcp_service%2Fchart%2Fchart_utils.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#diff-c3VwZXJzZXQvbWNwX3NlcnZpY2UvY2hhcnQvY2hhcnRfdXRpbHMucHk=) | 25.00% | [9 Missing :warning: ](https://app.codecov.io/gh/apache/superset/pull/41860?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) |
   > :exclamation:  There is a different number of reports uploaded between BASE (04f8b70) and HEAD (5332ec9). Click for more details.
   > 
   > <details><summary>HEAD has 24 uploads less than BASE</summary>
   >
   >| Flag | BASE (04f8b70) | HEAD (5332ec9) |
   >|------|------|------|
   >|python|22|2|
   >|presto|3|1|
   >|hive|3|1|
   ></details>
   
   <details><summary>Additional details and impacted files</summary>
   
   
   
   ```diff
   @@            Coverage Diff             @@
   ##           master   #41860      +/-   ##
   ==========================================
   - Coverage   64.78%   55.91%   -8.88%     
   ==========================================
     Files        2694     2703       +9     
     Lines      149309   149997     +688     
     Branches    34443    34525      +82     
   ==========================================
   - Hits        96730    83866   -12864     
   - Misses      50804    65378   +14574     
   + Partials     1775      753    -1022     
   ```
   
   | [Flag](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | Coverage Ξ” | |
   |---|---|---|
   | [hive](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `39.17% <43.37%> (+0.03%)` | :arrow_up: |
   | [mysql](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [postgres](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [presto](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `40.68% <43.37%> (+0.01%)` | :arrow_up: |
   | [python](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `40.74% <43.37%> (-18.54%)` | :arrow_down: |
   | [sqlite](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   | [unit](https://app.codecov.io/gh/apache/superset/pull/41860/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache) | `?` | |
   
   Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache#carryforward-flags-in-the-pull-request-comment) to find out more.
   </details>
   
   [:umbrella: View full report in Codecov by Harness](https://app.codecov.io/gh/apache/superset/pull/41860?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).   
   :loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=apache).
   <details><summary> :rocket: New features to boost your workflow: </summary>
   
   - :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
   - :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
   </details>


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "rusackas (via GitHub)" <gi...@apache.org>.
rusackas merged PR #41860:
URL: https://github.com/apache/superset/pull/41860


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #41860:
URL: https://github.com/apache/superset/pull/41860#issuecomment-4906527189

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565561998


##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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]] = {

Review Comment:
   **Suggestion:** Add an explicit type annotation to this class attribute (for example as a typed `ClassVar[str]`) instead of leaving it untyped. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This class attribute is also an unannotated string constant. Since the rule requires type hints on relevant variables that can be annotated, the suggestion correctly identifies a real omission.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=c1087fa2895943a29ee2f86a68bd240b&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=c1087fa2895943a29ee2f86a68bd240b&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:** 40:40
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this class attribute (for example as a typed `ClassVar[str]`) instead of leaving it untyped.
   
   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=2e01cf366a67acd576d864abf5ddd1ab7277d506582c056a474593ccb11c47e8&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=2e01cf366a67acd576d864abf5ddd1ab7277d506582c056a474593ccb11c47e8&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565446523


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"

Review Comment:
   **Suggestion:** Add an explicit type annotation to this class-level variable (for example as a class variable string) to comply with the required type hints rule. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This is a newly added class-level variable that lacks an explicit type annotation. The rule requires type hints on relevant variables that can be annotated, and `chart_type` is a string class attribute that should be annotated consistently with the base class.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=bbbc7b00c5bf42c8b32c9d4240809511&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=bbbc7b00c5bf42c8b32c9d4240809511&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/histogram.py
   **Line:** 38:38
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this class-level variable (for example as a class variable string) to comply with the required type hints rule.
   
   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=69f7d67f8f8a76edadfeb978af3dfaf642f09b7f97256e508ae31f3fb3d0f53a&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=69f7d67f8f8a76edadfeb978af3dfaf642f09b7f97256e508ae31f3fb3d0f53a&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"

Review Comment:
   **Suggestion:** Add an explicit type annotation to this class-level variable (for example as a class variable string) to satisfy the requirement for type-hinted relevant variables. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This class attribute is introduced without a type hint. Because the custom rule covers relevant variables that can be annotated, `display_name` should be explicitly typed here.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=c11201e76fa844f0a006e87f199ccdc0&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=c11201e76fa844f0a006e87f199ccdc0&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/histogram.py
   **Line:** 39:39
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this class-level variable (for example as a class variable string) to satisfy the requirement for type-hinted relevant variables.
   
   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=341b912ae1c9950a1d9f03fe4bdb21aa9de669c4036aa54621a3cd9a0cab0fbe&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=341b912ae1c9950a1d9f03fe4bdb21aa9de669c4036aa54621a3cd9a0cab0fbe&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565575459


##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(

Review Comment:
   **Suggestion:** Provide an explicit annotation for this union-validated configuration object to keep variable typing explicit in new code. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This local variable is introduced without a type annotation in new Python code, and it can be annotated with the validated chart config type.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=d660673ffb374907be05d0c7cc704fe6&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=d660673ffb374907be05d0c7cc704fe6&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:** tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py
   **Line:** 98:98
   **Comment:**
   	*Custom Rule: Provide an explicit annotation for this union-validated configuration object to keep variable typing explicit in new code.
   
   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=668ea32f99624e7ac8f099a9e9add4d913368ce14952f4a85866f22e2f660701&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=668ea32f99624e7ac8f099a9e9add4d913368ce14952f4a85866f22e2f660701&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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."""
+

Review Comment:
   **Suggestion:** Add an explicit class-level type annotation for this string constant. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This new class-level string constant is introduced without an explicit type annotation, which matches the rule requiring type hints for annotatable Python variables.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=922219f2fa2441c384223c6ea724e807&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=922219f2fa2441c384223c6ea724e807&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:** 37:37
   **Comment:**
   	*Custom Rule: Add an explicit class-level type annotation for this string constant.
   
   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=027606f6ee9f8d380dc8e4972c06b0eb7910d9248bb0371a87b6fc26cb8ad7fe&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=027606f6ee9f8d380dc8e4972c06b0eb7910d9248bb0371a87b6fc26cb8ad7fe&reaction=dislike'>πŸ‘Ž</a>



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(

Review Comment:
   **Suggestion:** Add an explicit type annotation for this test-local configuration variable to satisfy the required type-hint rule. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This new local variable is introduced without any type annotation, which matches the custom rule requiring explicit type hints for relevant Python variables that can be annotated.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=050e5d2d0d3c4060955d51521c301be9&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=050e5d2d0d3c4060955d51521c301be9&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:** tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py
   **Line:** 43:43
   **Comment:**
   	*Custom Rule: Add an explicit type annotation for this test-local configuration variable to satisfy the required type-hint rule.
   
   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=0fe371bdd8288f3d5760a82058fef33dd2477a6a69eee8218376083b001508d7&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=0fe371bdd8288f3d5760a82058fef33dd2477a6a69eee8218376083b001508d7&reaction=dislike'>πŸ‘Ž</a>



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)

Review Comment:
   **Suggestion:** Add a concrete type annotation for this mapped form-data variable so the new code complies with type-hint requirements. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This is a new local variable assigned the result of a function call and it has no explicit type hint, so it violates the type-hint rule for annotatable variables.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=75b69481775a4687b333a9c5e5da2b36&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=75b69481775a4687b333a9c5e5da2b36&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:** tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py
   **Line:** 111:111
   **Comment:**
   	*Custom Rule: Add a concrete type annotation for this mapped form-data variable so the new code complies with type-hint requirements.
   
   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=45dfd037d5f766510ec80fcd9f86a175d997552f1a6880e2046aa486cc14c9de&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=45dfd037d5f766510ec80fcd9f86a175d997552f1a6880e2046aa486cc14c9de&reaction=dislike'>πŸ‘Ž</a>



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["viz_type"] == "histogram_v2"
+        assert form_data["column"] == "trip_duration"
+        assert form_data["groupby"] == []
+        assert form_data["bins"] == 5
+        assert form_data["normalize"] is False
+        assert form_data["cumulative"] is False
+
+    def test_histogram_form_data_with_options_and_filters(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["bins"] == 20
+        assert form_data["normalize"] is True
+        assert form_data["cumulative"] is True
+        assert form_data["adhoc_filters"], "filters must map to adhoc_filters"
+
+
+class TestBoxPlotChartConfigSchema:
+    """BoxPlotChartConfig schema validation."""
+
+    def test_basic_box_plot_config(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+        )
+        assert config.whisker_type == "tukey"
+        assert config.dimensions is None
+        assert [c.name for c in config.distribute_across] == ["day_of_week"]
+
+    def test_box_plot_missing_metrics(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                distribute_across=[{"name": "day"}],
+            )
+
+    def test_box_plot_empty_distribute_across(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[],
+            )
+
+    def test_box_plot_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                bogus=1,
+            )
+
+    def test_box_plot_percentile_requires_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+            )
+
+    def test_box_plot_percentile_bounds_must_be_ordered(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+                percentile_low=90,
+                percentile_high=10,
+            )
+
+    def test_chart_config_union_dispatches_box_plot(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {
+                "chart_type": "box_plot",
+                "metrics": [{"name": "fare", "aggregate": "AVG"}],
+                "distribute_across": [{"name": "day"}],
+            }
+        )
+        assert isinstance(config, BoxPlotChartConfig)
+
+
+class TestMapBoxPlotConfig:
+    """form_data mapping must match the frontend BoxPlot buildQuery."""
+
+    def test_basic_box_plot_form_data(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+            dimensions=[{"name": "payment_type"}],
+        )
+        form_data = map_box_plot_config(config)
+        assert form_data["viz_type"] == "box_plot"
+        assert form_data["columns"] == ["day_of_week"]
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["whiskerOptions"] == "Tukey"
+        assert len(form_data["metrics"]) == 1
+
+    def test_box_plot_whisker_min_max(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="min_max",
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == (
+            "Min/max (no outliers)"
+        )
+
+    def test_box_plot_whisker_percentiles(self) -> None:
+        """Percentiles serialize to the exact string the frontend
+        boxplotOperator PERCENTILE_REGEX parses: '<low>/<high> percentiles'."""
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="percentile",
+            percentile_low=10,
+            percentile_high=90,
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == "10/90 percentiles"
+
+
+class TestPluginRegistry:
+    """Both plugins must be registered and resolve their viz types."""
+
+    def test_histogram_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")
+        assert plugin is not None
+        assert plugin.resolve_viz_type(None) == "histogram_v2"
+
+    def test_box_plot_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("box_plot")
+        assert plugin is not None
+        assert plugin.resolve_viz_type(None) == "box_plot"
+
+    def test_display_names_resolve(self) -> None:
+        from superset.mcp_service.chart.registry import display_name_for_viz_type
+
+        assert display_name_for_viz_type("histogram_v2") == "Histogram"
+        assert display_name_for_viz_type("box_plot") == "Box Plot"
+
+    def test_histogram_pre_validate_missing_column(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")
+        assert plugin is not None
+        error = plugin.pre_validate({"chart_type": "histogram"})

Review Comment:
   **Suggestion:** Add an explicit type annotation for this pre-validation result variable to comply with the type-hint rule. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The variable is newly introduced without a type annotation and stores a validation result, which is a relevant variable that can be annotated under the rule.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=cf7c396ec26b4cc99e9164deafadeb97&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=cf7c396ec26b4cc99e9164deafadeb97&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:** tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py
   **Line:** 275:275
   **Comment:**
   	*Custom Rule: Add an explicit type annotation for this pre-validation result variable to comply with the type-hint rule.
   
   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=283c9c5b6cbd519ba3e8946ca648aedf865b8a4995ed7a96c59562018ffabbb4&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=283c9c5b6cbd519ba3e8946ca648aedf865b8a4995ed7a96c59562018ffabbb4&reaction=dislike'>πŸ‘Ž</a>



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["viz_type"] == "histogram_v2"
+        assert form_data["column"] == "trip_duration"
+        assert form_data["groupby"] == []
+        assert form_data["bins"] == 5
+        assert form_data["normalize"] is False
+        assert form_data["cumulative"] is False
+
+    def test_histogram_form_data_with_options_and_filters(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["bins"] == 20
+        assert form_data["normalize"] is True
+        assert form_data["cumulative"] is True
+        assert form_data["adhoc_filters"], "filters must map to adhoc_filters"
+
+
+class TestBoxPlotChartConfigSchema:
+    """BoxPlotChartConfig schema validation."""
+
+    def test_basic_box_plot_config(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+        )
+        assert config.whisker_type == "tukey"
+        assert config.dimensions is None
+        assert [c.name for c in config.distribute_across] == ["day_of_week"]
+
+    def test_box_plot_missing_metrics(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                distribute_across=[{"name": "day"}],
+            )
+
+    def test_box_plot_empty_distribute_across(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[],
+            )
+
+    def test_box_plot_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                bogus=1,
+            )
+
+    def test_box_plot_percentile_requires_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+            )
+
+    def test_box_plot_percentile_bounds_must_be_ordered(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+                percentile_low=90,
+                percentile_high=10,
+            )
+
+    def test_chart_config_union_dispatches_box_plot(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {
+                "chart_type": "box_plot",
+                "metrics": [{"name": "fare", "aggregate": "AVG"}],
+                "distribute_across": [{"name": "day"}],
+            }
+        )
+        assert isinstance(config, BoxPlotChartConfig)
+
+
+class TestMapBoxPlotConfig:
+    """form_data mapping must match the frontend BoxPlot buildQuery."""
+
+    def test_basic_box_plot_form_data(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+            dimensions=[{"name": "payment_type"}],
+        )
+        form_data = map_box_plot_config(config)
+        assert form_data["viz_type"] == "box_plot"
+        assert form_data["columns"] == ["day_of_week"]
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["whiskerOptions"] == "Tukey"
+        assert len(form_data["metrics"]) == 1
+
+    def test_box_plot_whisker_min_max(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="min_max",
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == (
+            "Min/max (no outliers)"
+        )
+
+    def test_box_plot_whisker_percentiles(self) -> None:
+        """Percentiles serialize to the exact string the frontend
+        boxplotOperator PERCENTILE_REGEX parses: '<low>/<high> percentiles'."""
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="percentile",
+            percentile_low=10,
+            percentile_high=90,
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == "10/90 percentiles"
+
+
+class TestPluginRegistry:
+    """Both plugins must be registered and resolve their viz types."""
+
+    def test_histogram_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")

Review Comment:
   **Suggestion:** Add a type hint to this plugin lookup variable so the new test code follows the required typing standard. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This new local variable is unannotated even though its type can be expressed, so it fits the custom rule for missing type hints on relevant variables.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=bf1e157a03234a43b13ba245e74dcbd5&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=bf1e157a03234a43b13ba245e74dcbd5&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:** tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py
   **Line:** 253:253
   **Comment:**
   	*Custom Rule: Add a type hint to this plugin lookup variable so the new test code follows the required typing standard.
   
   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=690b19971cb7b8cb5e8c689f485d18359adbbf11d86f6af576d2eee2b9f0f371&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=690b19971cb7b8cb5e8c689f485d18359adbbf11d86f6af576d2eee2b9f0f371&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575440924


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "histogram_v2"
+
+    def normalize_column_refs(self, config: Any, dataset_context: Any) -> Any:
+        config_dict = config.model_dump()
+
+        column = config_dict.get("column")

Review Comment:
   Confirmed and fixed in 24eb424629 β€” post_map_validate now mirrors the Explore UI's numeric-only restriction: a non-numeric column returns a structured error naming the dataset's numeric columns and pointing category-frequency intents at xy/bar. Three tests (rejection with suggestions, numeric pass, unknown-column deferral to existence validation).



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565567848


##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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]] = {

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag bare string class attributes such as chart_type and display_name in BaseChartPlugin-derived chart plugins when they follow the established plugin convention and mypy passes.
   
   **Applied to:**
     - `superset/mcp_service/chart/plugins/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575644735


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",
+    "min_max": "Min/max (no outliers)",
+}
+
+
+def map_box_plot_config(config: "BoxPlotChartConfig") -> Dict[str, Any]:
+    """Map box plot config to Superset form_data (viz_type box_plot).
+
+    Matches the frontend BoxPlot buildQuery contract: ``columns`` are the
+    distribute-across values (one box per value), ``groupby`` the series
+    dimensions, and ``whiskerOptions`` one of the strings the
+    boxplotOperator post-processor parses.
+    """
+    if config.whisker_type == "percentile":
+        whisker_options = (
+            f"{config.percentile_low}/{config.percentile_high} percentiles"
+        )
+    else:
+        whisker_options = _WHISKER_TYPE_TO_OPTION[config.whisker_type]
+
+    form_data: Dict[str, Any] = {
+        "viz_type": "box_plot",
+        "columns": [c.name for c in config.distribute_across],
+        "groupby": [d.name for d in (config.dimensions or [])],
+        "metrics": [create_metric_object(m) for m in config.metrics],

Review Comment:
   **Suggestion:** `map_box_plot_config` maps `distribute_across` to `columns` and leaves `groupby` driven only by optional `dimensions`, but the box-plot post-processing logic groups by `groupby` (not `columns`). This means a valid request with only required fields (`metrics` + `distribute_across`) produces an empty `groupby` and collapses into a single box, so the required split field does not actually control box separation. Map the split dimension into `groupby` (and combine additional dimensions intentionally) so box grouping matches the schema contract. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Box plot MCP charts collapse into single global box.
   - ❌ Distribute_across column never controls box separation.
   - ⚠️ Agents misinterpret distributions when following schema examples.
   - ⚠️ get_chart_type_schema box_plot examples produce incorrect grouping.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Call the MCP generate_chart tool defined in
   superset/mcp_service/chart/tool/generate_chart.py:19-64 with a box plot config using only
   the required fields described in its docstring at lines 62-64, for example:
   
      - chart_type: "box_plot"
   
      - metrics: [{"name": "fare_amount", "aggregate": "AVG"}]
   
      - distribute_across: [{"name": "day_of_week"}]
   
      This matches the first box_plot example in
      superset/mcp_service/chart/tool/get_chart_type_schema.py:6-21 where only metrics and
      distribute_across are provided.
   
   
   
   2. The request config is validated against BoxPlotChartConfig in
   superset/mcp_service/chart/schemas.py:63-80, which requires metrics and distribute_across
   and makes dimensions optional. BoxPlotChartPlugin.pre_validate in
   superset/mcp_service/chart/plugins/box_plot.py:44-76 enforces the presence of "metrics"
   and "distribute_across" only, so the minimal config passes when dimensions is omitted.
   
   
   
   3. BoxPlotChartPlugin.to_form_data at superset/mcp_service/chart/plugins/box_plot.py:90-93
   calls map_box_plot_config(config) in superset/mcp_service/chart/chart_utils.py:76-102. For
   the minimal config, map_box_plot_config builds form_data with:
   
      - "columns": [c.name for c in config.distribute_across] (line 93)
   
      - "groupby": [d.name for d in (config.dimensions or [])] (line 94)
   
      Since config.dimensions is None, groupby becomes an empty list while columns contains
      ["day_of_week"].
   
   
   
   4. The Superset frontend Box Plot buildQuery in
   superset-frontend/plugins/plugin-chart-echarts/src/BoxPlot/buildQuery.ts:13-37 consumes
   this form_data. It constructs the query object with:
   
      - columns: distributed columns plus ensureIsArray(formData.groupby) (lines 13-35)
   
      - series_columns: formData.groupby (line 36)
   
      - post_processing: [boxplotOperator(formData, baseQueryObject)] (line 37)
   
      The boxplotOperator implementation in
      superset-frontend/packages/superset-ui-chart-controls/src/operators/boxplotOperator.ts:11-44
      reads groupby from formData and passes ensureIsArray(groupby).map(getColumnLabel) into
      the backend boxplot post processor. Because groupby is empty, options.groupby is [], so
      superset/utils/pandas_postprocessing/boxplot.py:29-59 aggregates each metric over the
      entire dataset with groupby=[], producing a single box per metric instead of separate
      boxes per distribute_across value on the x axis. This contradicts the contract
      documented in BoxPlotChartConfig at superset/mcp_service/chart/schemas.py:75-79 and the
      generate_chart docstring at 62-64 that promise one box per distribute_across value.
   ```
   </details>
   
   [![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=795717e795f24f50ba699fcb9694169f&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=795717e795f24f50ba699fcb9694169f&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/chart_utils.py
   **Line:** 943:944
   **Comment:**
   	*Api Mismatch: `map_box_plot_config` maps `distribute_across` to `columns` and leaves `groupby` driven only by optional `dimensions`, but the box-plot post-processing logic groups by `groupby` (not `columns`). This means a valid request with only required fields (`metrics` + `distribute_across`) produces an empty `groupby` and collapses into a single box, so the required split field does not actually control box separation. Map the split dimension into `groupby` (and combine additional dimensions intentionally) so box grouping matches the schema contract.
   
   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=81f874efce139e6b83fd4c1745611e75679d4304b626f5b0e7574c2ccf33d92d&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=81f874efce139e6b83fd4c1745611e75679d4304b626f5b0e7574c2ccf33d92d&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575707234


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

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Avoid flagging missing type annotations for bare class-level chart_type/display_name constants and other inferable locals in BaseChartPlugin-based chart plugins; this is the shared convention for in-tree plugins and mypy passes.
   
   **Applied to:**
     - `superset/mcp_service/chart/plugins/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



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

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag bare class-level chart_type/display_name constants or other inferable locals in chart plugin classes; this convention is intentional and mypy already passes.
   
   **Applied to:**
     - `superset/mcp_service/chart/plugins/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



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

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag bare class-level chart_type/display_name constants or inferable locals in BaseChartPlugin-derived chart plugins; this convention is intentional and mypy passes.
   
   **Applied to:**
     - `superset/mcp_service/chart/plugins/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565567257


##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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]] = {

Review Comment:
   Declining, standing rationale: chart_type/display_name are bare class attributes on all seven in-tree plugins following BaseChartPlugin's own pattern; annotating only these two would diverge from the established plugin convention, and mypy passes. Identical future findings will be declined the same way.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
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>
   
   [![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=bcabd75ee1e041688a09c4d5979bdba3&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=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>
   
   [![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=ccc8d82e96e74d14812c0db02f8087b9&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=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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575690629


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

Review Comment:
   **Suggestion:** Add an explicit type annotation to this class-level chart type constant. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This new class attribute is a clearly annotatable Python variable, and it currently lacks an explicit type hint, matching the rule that newly added Python code should include type hints where applicable.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=9057fe136a2a437e8584b556aef8fe0b&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=9057fe136a2a437e8584b556aef8fe0b&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:** 4:4
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this class-level chart type constant.
   
   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=8bf8a4bf9ecdf11f4ba4d7cbd87ea9b99048c187447109df934479cac108cb03&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=8bf8a4bf9ecdf11f4ba4d7cbd87ea9b99048c187447109df934479cac108cb03&reaction=dislike'>πŸ‘Ž</a>



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

Review Comment:
   **Suggestion:** Add an explicit type annotation to this class-level display label constant. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This is another newly introduced class-level variable that can be annotated but is not, so the suggestion accurately identifies a real type-hint omission under the custom rule.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=dbb1e09169df4e4d842371f512386338&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=dbb1e09169df4e4d842371f512386338&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:** 5:5
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this class-level display label constant.
   
   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=60ceb4eaacbf6049b040250e70803fc45263911ce3e6dde75968049865c9904e&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=60ceb4eaacbf6049b040250e70803fc45263911ce3e6dde75968049865c9904e&reaction=dislike'>πŸ‘Ž</a>



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

Review Comment:
   **Suggestion:** Add a concrete type annotation for this list so its intended element type is explicit. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The local list is newly introduced in a typed function and its element type is evident from use, so omitting an explicit annotation is a real type-hint omission covered by the rule.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=011c836d7a7e4ce1b93bd86379f60c46&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=011c836d7a7e4ce1b93bd86379f60c46&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:** 14:14
   **Comment:**
   	*Custom Rule: Add a concrete type annotation for this list so its intended element type is explicit.
   
   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=ee76d9a16f6170833a318a46100970425b7d213c0b4ffa446979b36d24823023&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=ee76d9a16f6170833a318a46100970425b7d213c0b4ffa446979b36d24823023&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575455410


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,186 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        numeric_types = ["INTEGER", "FLOAT", "DOUBLE", "DECIMAL", "NUMERIC"]
+        if col_info.get("is_numeric", False) or (
+            col_info.get("type", "").upper() in numeric_types
+        ):
+            return None

Review Comment:
   **Suggestion:** The numeric-type fallback check is too narrow and will reject valid numeric columns when `is_numeric` is false but the backend reports types like `BIGINT`, `SMALLINT`, `INT`, `REAL`, or `DOUBLE PRECISION`. Expand the allowed SQL type set (or use a broader numeric-type predicate) so valid numeric histogram columns are not incorrectly blocked. [incorrect condition logic]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Histograms rejected for BIGINT or similar numeric types.
   - ⚠️ LLM agents blocked from plotting distributions on affected columns.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Prepare a dataset whose ORM column has a SQL type like `BIGINT` or `DOUBLE PRECISION`
   and where the ORM `is_numeric` flag is False; `build_dataset_context_from_orm()`
   (superset/mcp_service/chart/validation/dataset_validator.py:51-59) will record this in
   `DatasetContext.available_columns` as `{"name": <column_name>, "type": "BIGINT",
   "is_temporal": False, "is_numeric": False}`.
   
   2. Call the MCP `generate_chart()` tool
   (superset/mcp_service/chart/tool/generate_chart.py:39-144) with `config.chart_type` set to
   `"histogram"` and `config.column` referencing that numeric-looking column by name, plus
   any valid `bins`/`groupby` values (the schema for `HistogramChartConfig` is at
   superset/mcp_service/chart/schemas.py:50-73).
   
   3. During validation, `ValidationPipeline.validate_request_with_warnings()` (imported in
   superset/mcp_service/chart/validation/__init__.py:20 and invoked at
   generate_chart.py:181-183) obtains a `DatasetContext` via
   `DatasetValidator._get_dataset_context()`
   (superset/mcp_service/chart/validation/dataset_validator.py:249-259), then passes the
   typed `HistogramChartConfig` and `dataset_id` into
   `HistogramChartPlugin.post_map_validate()`
   (superset/mcp_service/chart/plugins/histogram.py:87-149).
   
   4. In `post_map_validate()`, the plugin looks up the column metadata (lines 106-113) and
   evaluates the numeric guard at lines 118-122: `numeric_types = ["INTEGER", "FLOAT",
   "DOUBLE", "DECIMAL", "NUMERIC"]; if col_info.get("is_numeric", False) or
   (col_info.get("type", "").upper() in numeric_types): return None`. Because `is_numeric` is
   False and `type.upper()` is `"BIGINT"` (not in the allowlist), the check fails and the
   function returns a `ChartGenerationError` `"NON_NUMERIC_HISTOGRAM_COLUMN"` (lines
   130-149), incorrectly rejecting a valid numeric histogram column purely due to the narrow
   type allowlist.
   ```
   </details>
   
   [![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=97ee024776aa427bbf65450ba5ba4afb&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=97ee024776aa427bbf65450ba5ba4afb&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/histogram.py
   **Line:** 118:122
   **Comment:**
   	*Incorrect Condition Logic: The numeric-type fallback check is too narrow and will reject valid numeric columns when `is_numeric` is false but the backend reports types like `BIGINT`, `SMALLINT`, `INT`, `REAL`, or `DOUBLE PRECISION`. Expand the allowed SQL type set (or use a broader numeric-type predicate) so valid numeric histogram columns are not incorrectly blocked.
   
   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=9d5ebbd306c5e4d9c907c23373827c6413d13b5b5195f45d90081870e6b980d0&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=9d5ebbd306c5e4d9c907c23373827c6413d13b5b5195f45d90081870e6b980d0&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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 forming one box per value)"
+            )
+
+        if missing_fields:
+            return ChartGenerationError(
+                error_type="missing_box_plot_fields",
+                message=(
+                    f"Box plot missing required fields: {', '.join(missing_fields)}"
+                ),
+                details=(
+                    "Box plots show the distribution of one or more metrics "
+                    "across the values of the distribute_across columns"
+                ),
+                suggestions=[
+                    "Add 'metrics': [{'name': 'value_column', 'aggregate': 'AVG'}]",
+                    "Add 'distribute_across': [{'name': 'category_column'}]",
+                    "Example: {'chart_type': 'box_plot', 'metrics': "
+                    "[{'name': 'fare', 'aggregate': 'AVG'}], "
+                    "'distribute_across': [{'name': 'day_of_week'}]}",
+                ],
+                error_code="MISSING_BOX_PLOT_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, BoxPlotChartConfig):
+            return []
+        refs: list[ColumnRef] = []
+        refs.extend(config.metrics)
+        refs.extend(config.distribute_across)
+        refs.extend(config.dimensions or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_box_plot_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        metric_names = ", ".join(m.label or m.name for m in config.metrics)
+        across = ", ".join(c.label or c.name for c in config.distribute_across)
+        what = f"{metric_names} distribution by {across}"
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "box_plot"
+
+    def normalize_column_refs(self, config: Any, dataset_context: Any) -> Any:
+        config_dict = config.model_dump()
+
+        for metric in config_dict.get("metrics") or []:
+            if metric.get("sql_expression"):
+                continue
+            if metric.get("saved_metric"):
+                metric["name"] = DatasetValidator.get_canonical_metric_name(
+                    metric["name"], dataset_context
+                )
+            else:
+                metric["name"] = DatasetValidator.get_canonical_column_name(
+                    metric["name"], dataset_context
+                )
+        for key in ("distribute_across", "dimensions"):
+            for col in config_dict.get(key) or []:
+                if not col.get("sql_expression") and not col.get("saved_metric"):
+                    col["name"] = DatasetValidator.get_canonical_column_name(
+                        col["name"], dataset_context
+                    )
+        DatasetValidator.normalize_filters(config_dict, dataset_context)
+        return BoxPlotChartConfig.model_validate(config_dict)
+
+    def schema_error_hint(self) -> ChartGenerationError | None:
+        return ChartGenerationError(
+            error_type="box_plot_validation_error",
+            message="Box plot configuration validation failed",
+            details=(
+                "The box plot configuration is missing required fields or "
+                "has invalid structure"
+            ),
+            suggestions=[
+                "Ensure 'metrics' is a non-empty list with 'name' and 'aggregate'",

Review Comment:
   **Suggestion:** This validation hint incorrectly states that every metric must include `aggregate`, but box-plot metrics can also be valid saved metrics (`saved_metric=True`) or SQL metrics (`sql_expression` with `label`). Update the hint text so it matches the actual accepted schema and does not mislead callers into sending the wrong payload shape. [comment mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Box plot validation hint misstates required metric fields.
   - ⚠️ Saved metrics guidance conflicts with ColumnRef schema description.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Invoke the MCP `generate_chart()` tool
   (superset/mcp_service/chart/tool/generate_chart.py:39-144) with a request whose
   `config.chart_type` is `"box_plot"` but whose `config` either omits `metrics` or has an
   invalid shape so that Pydantic cannot parse the `ChartConfig` discriminated union defined
   at superset/mcp_service/chart/schemas.py:167-186.
   
   2. During validation, `ValidationPipeline.validate_request_with_warnings()` (imported in
   superset/mcp_service/chart/validation/__init__.py:20 and called at
   generate_chart.py:181-183) delegates to `SchemaValidator`, which per the
   `schema_error_hint` docstring in superset/mcp_service/chart/plugin.py:17-23 uses the
   plugin-specific `BoxPlotChartPlugin.schema_error_hint()`
   (superset/mcp_service/chart/plugins/box_plot.py:127-145) when union parsing fails for a
   known `chart_type`.
   
   3. The error returned from `BoxPlotChartPlugin.schema_error_hint()` contains the
   suggestion string at superset/mcp_service/chart/plugins/box_plot.py:136: `Ensure 'metrics'
   is a non-empty list with 'name' and 'aggregate'`, which states that every metric must
   provide an `aggregate`.
   
   4. However, the actual box-plot schema `BoxPlotChartConfig.metrics` at
   superset/mcp_service/chart/schemas.py:93-104 explicitly allows both ad-hoc metrics with
   `aggregate` and saved metrics using `saved_metric=True`, as well as SQL metrics using
   `sql_expression`+`label` per the general metric guidance in `generate_chart()`'s docstring
   (superset/mcp_service/chart/tool/generate_chart.py:114-132). This contradiction can
   mislead callers (especially LLM agents) into sending only `name`+`aggregate` even for
   saved or SQL metrics, triggering additional saved-metric validation errors via
   `DatasetValidator._build_saved_metric_hint_error()`
   (superset/mcp_service/chart/validation/dataset_validator.py:219-245).
   ```
   </details>
   
   [![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=392991e656d1481689c72c35e8ed4d92&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=392991e656d1481689c72c35e8ed4d92&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:** 136:136
   **Comment:**
   	*Comment Mismatch: This validation hint incorrectly states that every metric must include `aggregate`, but box-plot metrics can also be valid saved metrics (`saved_metric=True`) or SQL metrics (`sql_expression` with `label`). Update the hint text so it matches the actual accepted schema and does not mislead callers into sending the wrong payload shape.
   
   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=61cec564be75e6504441b46b638d998aeef650d2e19bed77d9ec1aab699388e4&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=61cec564be75e6504441b46b638d998aeef650d2e19bed77d9ec1aab699388e4&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565457021


##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,108 @@ 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}]")
+        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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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(100, description="Max boxes", ge=1, le=10000)
+    number_format: str = Field("SMART_NUMBER", max_length=50)
+    date_format: str = Field("smart_date", max_length=50)
+
+    @model_validator(mode="after")
+    def validate_percentiles_and_dimensions(self) -> "BoxPlotChartConfig":
+        if self.whisker_type == "percentile":
+            if self.percentile_low is None or self.percentile_high is None:
+                raise ValueError(
+                    "whisker_type='percentile' requires both percentile_low "
+                    "and percentile_high"
+                )
+            if self.percentile_low >= self.percentile_high:
+                raise ValueError("percentile_low must be less than percentile_high")
+        elif self.percentile_low is not None or self.percentile_high is not None:
+            raise ValueError(
+                "percentile_low/percentile_high only apply when "
+                "whisker_type='percentile'"
+            )
+        for i, col in enumerate(self.distribute_across):
+            _reject_sql_expression_on_dimension(col, f"distribute_across[{i}]")
+        for i, col in enumerate(self.dimensions or []):
+            _reject_sql_expression_on_dimension(col, f"dimensions[{i}]")

Review Comment:
   **Suggestion:** `dimensions` in box plot config is also a dimension-only field but currently permits `saved_metric=True`. Those values are mapped into `form_data["groupby"]`, where saved metrics are not valid and can trigger runtime query errors. Add a validation check to reject `saved_metric=True` in `dimensions` entries. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Box plot dimension metrics break query group-by generation.
   - ⚠️ Box plot MCP results fail for misconfigured series dimensions.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Use the MCP `generate_chart` tool in
   `superset/mcp_service/chart/tool/generate_chart.py:89-101` to submit a box-plot request
   whose `config` includes `{"chart_type": "box_plot", "metrics": [{"name": "fare",
   "aggregate": "AVG"}], "distribute_across": [{"name": "day_of_week"}], "dimensions":
   [{"name": "fare_metric", "saved_metric": true}]}` as permitted by the box-plot docstring
   at lines 141-143.
   
   2. The `ChartConfig` union in `superset/mcp_service/chart/schemas.py:93-112` dispatches
   this config to `BoxPlotChartConfig`; the `validate_percentiles_and_dimensions` validator
   at lines 71-90 iterates over `self.dimensions` and calls
   `_reject_sql_expression_on_dimension(col, f"dimensions[{i}]")` (lines 88-89) but does not
   check `col.saved_metric`, so a `dimensions` entry with `saved_metric=True` passes schema
   validation.
   
   3. `generate_chart` calls `map_config_to_form_data` in
   `superset/mcp_service/chart/chart_utils.py:368-415`, which uses the `BoxPlotChartPlugin`
   (registered in `plugins/__init__.py:50-52`) and its `to_form_data` implementation in
   `superset/mcp_service/chart/plugins/box_plot.py:89-92` to convert the config via
   `map_box_plot_config`.
   
   4. `map_box_plot_config` in `chart_utils.py:925-951` builds `form_data` with `"groupby":
   [d.name for d in (config.dimensions or [])]`, so the saved metric name from the
   `dimensions` field is emitted into `form_data["groupby"]` even though group-by fields in
   Superset’s box-plot query path are dimension columns; when `_compile_chart` runs in
   `generate_chart.py:443-449` or `generate_chart.py:585-589`, the query builder treats these
   names as columns, and if they do not match real columns the compile step fails and
   `generate_chart` returns a `CHART_COMPILE_FAILED` style error instead of a valid box plot.
   ```
   </details>
   
   [![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=fba29f039637410d96ce24482780b49d&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=fba29f039637410d96ce24482780b49d&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:** 1946:1947
   **Comment:**
   	*Api Mismatch: `dimensions` in box plot config is also a dimension-only field but currently permits `saved_metric=True`. Those values are mapped into `form_data["groupby"]`, where saved metrics are not valid and can trigger runtime query errors. Add a validation check to reject `saved_metric=True` in `dimensions` entries.
   
   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=385778d3fd428612b8e2a0084eb3a06b3ff596ca761d2aaa208cc797f1b329da&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=385778d3fd428612b8e2a0084eb3a06b3ff596ca761d2aaa208cc797f1b329da&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,108 @@ 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}]")

Review Comment:
   **Suggestion:** `groupby` entries in histogram config currently only reject `sql_expression` but still allow `saved_metric=True`. That lets saved metrics flow into `form_data["groupby"]` as if they were dimension columns, which will break query generation at runtime because group-by fields must be real columns. Add a schema-level check rejecting `saved_metric=True` for each `groupby` item. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ generate_chart histogram requests with metric groupby fail compile check.
   - ⚠️ Histogram MCP previews fail for misconfigured groupby dimensions.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Call the MCP `generate_chart` tool defined in
   `superset/mcp_service/chart/tool/generate_chart.py:89-101` with a `GenerateChartRequest`
   whose `config` payload includes `{"chart_type": "histogram", "column": {"name":
   "trip_duration"}, "groupby": [{"name": "fare_avg", "saved_metric": true}]}` as allowed by
   the docstring’s histogram section at lines 137-140.
   
   2. The request config is validated via the `ChartConfig` discriminated union in
   `superset/mcp_service/chart/schemas.py:93-112`, which dispatches to
   `HistogramChartConfig`; its `reject_metric_style_column` validator at lines 15-26 only
   rejects `sql_expression` and `saved_metric` on `self.column` and calls
   `_reject_sql_expression_on_dimension` for each `groupby` entry, but never checks
   `col.saved_metric`, so a `groupby` entry with `saved_metric=True` is accepted.
   
   3. `generate_chart` then calls `map_config_to_form_data` in
   `superset/mcp_service/chart/chart_utils.py:368-415`, which looks up the
   `HistogramChartPlugin` registered in
   `superset/mcp_service/chart/plugins/__init__.py:50-52`;
   `HistogramChartPlugin.to_form_data` in `plugins/histogram.py:75-78` delegates to
   `map_histogram_config`.
   
   4. `map_histogram_config` in `chart_utils.py:896-913` builds `form_data` with `groupby:
   [g.name for g in (config.groupby or [])]`, so the saved metric name (for example
   `"fare_avg"`) is emitted into `form_data["groupby"]` as if it were a physical column; when
   `_compile_chart` is invoked in `generate_chart.py:443-449` or `generate_chart.py:585-589`,
   Superset’s query builder treats these group-by entries as real dataset columns, leading to
   a compile failure (e.g. column-not-found) and a `CHART_COMPILE_FAILED` error instead of a
   valid histogram.
   ```
   </details>
   
   [![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=7f77d9eabef643f7836150c24054a7b5&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=7f77d9eabef643f7836150c24054a7b5&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:** 1882:1883
   **Comment:**
   	*Api Mismatch: `groupby` entries in histogram config currently only reject `sql_expression` but still allow `saved_metric=True`. That lets saved metrics flow into `form_data["groupby"]` as if they were dimension columns, which will break query generation at runtime because group-by fields must be real columns. Add a schema-level check rejecting `saved_metric=True` for each `groupby` item.
   
   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=16d858d1e4a902de3162967a72eec796e8e48b540e8baed08639288e99d92484&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=16d858d1e4a902de3162967a72eec796e8e48b540e8baed08639288e99d92484&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,108 @@ 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}]")
+        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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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(100, description="Max boxes", ge=1, le=10000)
+    number_format: str = Field("SMART_NUMBER", max_length=50)
+    date_format: str = Field("smart_date", max_length=50)
+
+    @model_validator(mode="after")
+    def validate_percentiles_and_dimensions(self) -> "BoxPlotChartConfig":
+        if self.whisker_type == "percentile":
+            if self.percentile_low is None or self.percentile_high is None:
+                raise ValueError(
+                    "whisker_type='percentile' requires both percentile_low "
+                    "and percentile_high"
+                )
+            if self.percentile_low >= self.percentile_high:
+                raise ValueError("percentile_low must be less than percentile_high")
+        elif self.percentile_low is not None or self.percentile_high is not None:
+            raise ValueError(
+                "percentile_low/percentile_high only apply when "
+                "whisker_type='percentile'"
+            )
+        for i, col in enumerate(self.distribute_across):
+            _reject_sql_expression_on_dimension(col, f"distribute_across[{i}]")

Review Comment:
   **Suggestion:** `distribute_across` in box plot config is a dimension axis but the validator only blocks `sql_expression`, not `saved_metric=True`. This allows saved metrics to be emitted in `form_data["columns"]`, which are expected to be real dataset columns and can fail during query compilation. Reject `saved_metric=True` for `distribute_across` items at schema validation time. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Box plot distribute_across metrics cause compile_error in generate_chart.
   - ⚠️ Box plot previews fail for misconfigured distribute_across dimensions.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Call the MCP `generate_chart` tool in
   `superset/mcp_service/chart/tool/generate_chart.py:89-101` with a `GenerateChartRequest`
   whose `config` contains a box-plot definition such as `{"chart_type": "box_plot",
   "metrics": [{"name": "fare", "aggregate": "AVG"}], "distribute_across": [{"name":
   "fare_metric", "saved_metric": true}]}` as described in the box-plot section of the
   docstring at lines 141-143.
   
   2. The request is validated by the `ChartConfig` union in
   `superset/mcp_service/chart/schemas.py:93-112`, which dispatches to `BoxPlotChartConfig`;
   its `validate_percentiles_and_dimensions` validator at lines 71-90 only calls
   `_reject_sql_expression_on_dimension` for each `distribute_across` entry (line 86) and
   does not check `col.saved_metric`, so a `distribute_across` item with `saved_metric=True`
   passes schema validation.
   
   3. `generate_chart` then invokes `map_config_to_form_data` in
   `superset/mcp_service/chart/chart_utils.py:368-415`, which retrieves the
   `BoxPlotChartPlugin` from the registry (registered in
   `superset/mcp_service/chart/plugins/__init__.py:50-52`) and calls
   `BoxPlotChartPlugin.to_form_data` in `plugins/box_plot.py:89-92`, delegating to
   `map_box_plot_config`.
   
   4. `map_box_plot_config` in `chart_utils.py:925-951` builds `form_data` with `"columns":
   [c.name for c in config.distribute_across]` and `"metrics": [create_metric_object(m) for m
   in config.metrics]`; because the `distribute_across` entry carries `saved_metric=True`,
   its name (e.g. `"fare_metric"`) is emitted into `form_data["columns"]` even though
   Superset’s box-plot query builder expects physical dataset columns there, so when
   `_compile_chart` is executed in `generate_chart.py:443-449` or
   `generate_chart.py:585-589`, the query compile fails due to an invalid or missing column
   and `generate_chart` returns a compile-error response instead of a working box plot.
   ```
   </details>
   
   [![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=41f9cd09354447c29ca0a6781f04a9d1&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=41f9cd09354447c29ca0a6781f04a9d1&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:** 1944:1945
   **Comment:**
   	*Api Mismatch: `distribute_across` in box plot config is a dimension axis but the validator only blocks `sql_expression`, not `saved_metric=True`. This allows saved metrics to be emitted in `form_data["columns"]`, which are expected to be real dataset columns and can fail during query compilation. Reject `saved_metric=True` for `distribute_across` items at schema validation time.
   
   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=6a27dbc783f5611fe50e4ec7010ad4a519272962de43bbe64a960eed033dc8b8&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=6a27dbc783f5611fe50e4ec7010ad4a519272962de43bbe64a960eed033dc8b8&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565569213


##########
superset/mcp_service/chart/tool/get_chart_type_schema.py:
##########
@@ -48,6 +50,8 @@
     "mixed_timeseries": TypeAdapter(MixedTimeseriesChartConfig),
     "handlebars": TypeAdapter(HandlebarsChartConfig),
     "big_number": TypeAdapter(BigNumberChartConfig),
+    "histogram": TypeAdapter(HistogramChartConfig),
+    "box_plot": TypeAdapter(BoxPlotChartConfig),

Review Comment:
   **Suggestion:** You added new supported chart types but did not update the tool’s declared valid-type contract text, so callers relying on the function documentation will incorrectly treat these chart types as unsupported. Update the public contract text in the function docstring to include the new chart types so discovery and invocation guidance stays consistent with runtime behavior. [docstring mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ MCP get_chart_type_schema tool advertises incomplete chart_type set.
   - ⚠️ Agents may avoid valid histogram and box_plot schemas.
   - ⚠️ Documentation-driven workflows get inconsistent discovery versus runtime behavior.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Open `superset/core/mcp/core_mcp_injection.py` and observe in `create_tool_decorator`
   that the MCP `@tool` decorator derives the tool description from the function docstring:
   `tool_description = description or func.__doc__ or f"Tool: {base_tool_name}"` (lines
   35–36), so whatever is written in the docstring is surfaced to MCP clients as the tool
   contract text.
   
   2. Open `superset/mcp_service/chart/tool/get_chart_type_schema.py` and confirm that
   `_CHART_TYPE_ADAPTERS` includes `"histogram"` and `"box_plot"` (lines 45–55), and that
   `VALID_CHART_TYPES = sorted(_CHART_TYPE_ADAPTERS.keys())` (line 57), meaning the runtime
   logic fully supports these two new chart types and they are returned as valid in error
   messages and responses.
   
   3. In the same file, scroll to `get_chart_type_schema` (lines 15–29) and inspect its
   docstring: the β€œValid chart_type values” sentence lists only `xy, table, pie, pivot_table,
   mixed_timeseries, handlebars, big_number` (lines 24–25), omitting the newly added
   `histogram` and `box_plot` types that are present in `_CHART_TYPE_ADAPTERS` and
   `VALID_CHART_TYPES`.
   
   4. Start the MCP service (which imports `get_chart_type_schema` in
   `superset/mcp_service/app.py` around lines 4–18) and inspect the tool manifest or let an
   MCP client enumerate tools; the tool description for `get_chart_type_schema` will present
   the stale list from the docstring, so an agent or human relying on that text will
   reasonably conclude that `histogram` and `box_plot` are unsupported even though calls with
   `chart_type="histogram"` or `chart_type="box_plot"` succeed at runtime.
   ```
   </details>
   
   [![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=1f3f6a62b2444eea8de228c513efd6b8&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=1f3f6a62b2444eea8de228c513efd6b8&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/tool/get_chart_type_schema.py
   **Line:** 53:54
   **Comment:**
   	*Docstring Mismatch: You added new supported chart types but did not update the tool’s declared valid-type contract text, so callers relying on the function documentation will incorrectly treat these chart types as unsupported. Update the public contract text in the function docstring to include the new chart types so discovery and invocation guidance stays consistent with runtime behavior.
   
   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=8e70efa67dbd2070837cfd8ccd884171b45dbaa1adfb5275208806a96b9342d6&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=8e70efa67dbd2070837cfd8ccd884171b45dbaa1adfb5275208806a96b9342d6&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/app.py:
##########
@@ -384,6 +384,11 @@ def get_default_instructions(
   Requires handlebars_template with Handlebars HTML template string.
   Supports query_mode="aggregate" (with metrics/groupby) or "raw" (with columns).
   Data available as {{{{data}}}} array; helpers: dateFormat, formatNumber, stringify.
+- chart_type="histogram": Histogram of a numeric column's distribution
+  (column required; optional bins, groupby, normalize, cumulative)
+- chart_type="box_plot": Box plot comparing statistical spread
+  (metrics + distribute_across required; optional dimensions,
+   whisker_type: tukey | min_max | percentile)

Review Comment:
   **Suggestion:** You added two new creatable chart types here, but the same instructions block still states later that only 7 chart types are supported by `generate_chart`. This creates contradictory runtime guidance for MCP clients and can cause them to mis-handle valid `histogram`/`box_plot` flows. Update the downstream count/list text in this same instructions payload to stay consistent with these additions. [comment mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ MCP instructions misstate generate_chart supported chart types.
   - ⚠️ Clients may mishandle histogram/box_plot based on guidance.
   - ⚠️ Agents receive inconsistent chart_type_display_name documentation.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Deploy the MCP service with this PR and call `get_default_instructions()` from
   `superset/mcp_service/app.py` (function containing the instructions block around lines
   374–399 in the diff).
   
   2. Inspect the "Chart Types You Can CREATE with generate_chart/generate_explore_link"
   section, which now lists `chart_type="histogram"` and `chart_type="box_plot"` at lines
   387–391 as creatable chart types.
   
   3. Continue reading the "Chart Types in Existing Charts (viewable via
   list_charts/get_chart_info)" section immediately below; at line 399 the text states: "This
   field is populated only for the 7 chart types supported by generate_chart (xy, pie, table,
   pivot_table, big_number, mixed_timeseries, handlebars)."
   
   4. Observe that the instructions payload simultaneously describes histogram/box_plot as
   supported creatable chart types while still claiming that only 7 chart types are supported
   by `generate_chart`, creating contradictory guidance for MCP clients consuming this
   instructions string.
   ```
   </details>
   
   [![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=04abc17a29db430f8b4aa006dbd4ae9a&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=04abc17a29db430f8b4aa006dbd4ae9a&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/app.py
   **Line:** 387:391
   **Comment:**
   	*Comment Mismatch: You added two new creatable chart types here, but the same instructions block still states later that only 7 chart types are supported by `generate_chart`. This creates contradictory runtime guidance for MCP clients and can cause them to mis-handle valid `histogram`/`box_plot` flows. Update the downstream count/list text in this same instructions payload to stay consistent with these additions.
   
   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=e6b0c8629ba3a561e10541d7eef85b6909ae89702cab60711799b989b519e544&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=e6b0c8629ba3a561e10541d7eef85b6909ae89702cab60711799b989b519e544&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575705511


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

Review Comment:
   Declining, standing rationale: bare class-level chart_type/display_name constants and inferable locals follow BaseChartPlugin's convention shared by all in-tree plugins; mypy passes. Identical future findings will be declined the same way.



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

Review Comment:
   Declining, standing rationale: bare class-level chart_type/display_name constants and inferable locals follow BaseChartPlugin's convention shared by all in-tree plugins; mypy passes. Identical future findings will be declined the same way.



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

Review Comment:
   Declining, standing rationale: bare class-level chart_type/display_name constants and inferable locals follow BaseChartPlugin's convention shared by all in-tree plugins; mypy passes. Identical future findings will be declined the same way.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565584560


##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag inferable local variables or constants in Python code for missing type annotations; follow the existing codebase convention where these remain unannotated if mypy passes.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag inferable-type constants or locals for missing type annotations in chart plugin code; this project intentionally leaves them unannotated.
   
   **Applied to:**
     - `superset/mcp_service/chart/plugins/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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."""
+

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not require explicit type annotations for class-level constants or other inferable locals in chart plugin files; follow the existing in-tree pattern unless a type is not obvious or mypy requires it.
   
   **Applied to:**
     - `superset/mcp_service/chart/plugins/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag unannotated local variables when their type is clearly inferable and the codebase's type checks already pass; this is an accepted pattern in Python code throughout the repository.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag missing type annotations for locals or constants when their types are inferable; this pattern is accepted across the codebase.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["viz_type"] == "histogram_v2"
+        assert form_data["column"] == "trip_duration"
+        assert form_data["groupby"] == []
+        assert form_data["bins"] == 5
+        assert form_data["normalize"] is False
+        assert form_data["cumulative"] is False
+
+    def test_histogram_form_data_with_options_and_filters(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["bins"] == 20
+        assert form_data["normalize"] is True
+        assert form_data["cumulative"] is True
+        assert form_data["adhoc_filters"], "filters must map to adhoc_filters"
+
+
+class TestBoxPlotChartConfigSchema:
+    """BoxPlotChartConfig schema validation."""
+
+    def test_basic_box_plot_config(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+        )
+        assert config.whisker_type == "tukey"
+        assert config.dimensions is None
+        assert [c.name for c in config.distribute_across] == ["day_of_week"]
+
+    def test_box_plot_missing_metrics(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                distribute_across=[{"name": "day"}],
+            )
+
+    def test_box_plot_empty_distribute_across(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[],
+            )
+
+    def test_box_plot_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                bogus=1,
+            )
+
+    def test_box_plot_percentile_requires_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+            )
+
+    def test_box_plot_percentile_bounds_must_be_ordered(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+                percentile_low=90,
+                percentile_high=10,
+            )
+
+    def test_chart_config_union_dispatches_box_plot(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {
+                "chart_type": "box_plot",
+                "metrics": [{"name": "fare", "aggregate": "AVG"}],
+                "distribute_across": [{"name": "day"}],
+            }
+        )
+        assert isinstance(config, BoxPlotChartConfig)
+
+
+class TestMapBoxPlotConfig:
+    """form_data mapping must match the frontend BoxPlot buildQuery."""
+
+    def test_basic_box_plot_form_data(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+            dimensions=[{"name": "payment_type"}],
+        )
+        form_data = map_box_plot_config(config)
+        assert form_data["viz_type"] == "box_plot"
+        assert form_data["columns"] == ["day_of_week"]
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["whiskerOptions"] == "Tukey"
+        assert len(form_data["metrics"]) == 1
+
+    def test_box_plot_whisker_min_max(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="min_max",
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == (
+            "Min/max (no outliers)"
+        )
+
+    def test_box_plot_whisker_percentiles(self) -> None:
+        """Percentiles serialize to the exact string the frontend
+        boxplotOperator PERCENTILE_REGEX parses: '<low>/<high> percentiles'."""
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="percentile",
+            percentile_low=10,
+            percentile_high=90,
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == "10/90 percentiles"
+
+
+class TestPluginRegistry:
+    """Both plugins must be registered and resolve their viz types."""
+
+    def test_histogram_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")
+        assert plugin is not None
+        assert plugin.resolve_viz_type(None) == "histogram_v2"
+
+    def test_box_plot_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("box_plot")
+        assert plugin is not None
+        assert plugin.resolve_viz_type(None) == "box_plot"
+
+    def test_display_names_resolve(self) -> None:
+        from superset.mcp_service.chart.registry import display_name_for_viz_type
+
+        assert display_name_for_viz_type("histogram_v2") == "Histogram"
+        assert display_name_for_viz_type("box_plot") == "Box Plot"
+
+    def test_histogram_pre_validate_missing_column(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")
+        assert plugin is not None
+        error = plugin.pre_validate({"chart_type": "histogram"})

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag missing type annotations for locals/constants whose types are inferable and already accepted by mypy; this is acceptable in Python test code and chart-plugin code.
   
   **Applied to:**
     - `tests/**`
     - `superset/mcp_service/chart/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["viz_type"] == "histogram_v2"
+        assert form_data["column"] == "trip_duration"
+        assert form_data["groupby"] == []
+        assert form_data["bins"] == 5
+        assert form_data["normalize"] is False
+        assert form_data["cumulative"] is False
+
+    def test_histogram_form_data_with_options_and_filters(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["bins"] == 20
+        assert form_data["normalize"] is True
+        assert form_data["cumulative"] is True
+        assert form_data["adhoc_filters"], "filters must map to adhoc_filters"
+
+
+class TestBoxPlotChartConfigSchema:
+    """BoxPlotChartConfig schema validation."""
+
+    def test_basic_box_plot_config(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+        )
+        assert config.whisker_type == "tukey"
+        assert config.dimensions is None
+        assert [c.name for c in config.distribute_across] == ["day_of_week"]
+
+    def test_box_plot_missing_metrics(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                distribute_across=[{"name": "day"}],
+            )
+
+    def test_box_plot_empty_distribute_across(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[],
+            )
+
+    def test_box_plot_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                bogus=1,
+            )
+
+    def test_box_plot_percentile_requires_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+            )
+
+    def test_box_plot_percentile_bounds_must_be_ordered(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+                percentile_low=90,
+                percentile_high=10,
+            )
+
+    def test_chart_config_union_dispatches_box_plot(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {
+                "chart_type": "box_plot",
+                "metrics": [{"name": "fare", "aggregate": "AVG"}],
+                "distribute_across": [{"name": "day"}],
+            }
+        )
+        assert isinstance(config, BoxPlotChartConfig)
+
+
+class TestMapBoxPlotConfig:
+    """form_data mapping must match the frontend BoxPlot buildQuery."""
+
+    def test_basic_box_plot_form_data(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+            dimensions=[{"name": "payment_type"}],
+        )
+        form_data = map_box_plot_config(config)
+        assert form_data["viz_type"] == "box_plot"
+        assert form_data["columns"] == ["day_of_week"]
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["whiskerOptions"] == "Tukey"
+        assert len(form_data["metrics"]) == 1
+
+    def test_box_plot_whisker_min_max(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="min_max",
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == (
+            "Min/max (no outliers)"
+        )
+
+    def test_box_plot_whisker_percentiles(self) -> None:
+        """Percentiles serialize to the exact string the frontend
+        boxplotOperator PERCENTILE_REGEX parses: '<low>/<high> percentiles'."""
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="percentile",
+            percentile_low=10,
+            percentile_high=90,
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == "10/90 percentiles"
+
+
+class TestPluginRegistry:
+    """Both plugins must be registered and resolve their viz types."""
+
+    def test_histogram_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag missing type hints for locals or constants when their types are inferable; this is an accepted codebase pattern and mypy already passes.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565573524


##########
superset/mcp_service/app.py:
##########
@@ -384,6 +384,11 @@ def get_default_instructions(
   Requires handlebars_template with Handlebars HTML template string.
   Supports query_mode="aggregate" (with metrics/groupby) or "raw" (with columns).
   Data available as {{{{data}}}} array; helpers: dateFormat, formatNumber, stringify.
+- chart_type="histogram": Histogram of a numeric column's distribution
+  (column required; optional bins, groupby, normalize, cumulative)
+- chart_type="box_plot": Box plot comparing statistical spread
+  (metrics + distribute_across required; optional dimensions,
+   whisker_type: tukey | min_max | percentile)

Review Comment:
   Confirmed and fixed in a375284697 β€” both remaining 7-type passages (server instructions and the get_chart_type_schema docstring) now list all 9 types.



##########
superset/mcp_service/chart/tool/get_chart_type_schema.py:
##########
@@ -48,6 +50,8 @@
     "mixed_timeseries": TypeAdapter(MixedTimeseriesChartConfig),
     "handlebars": TypeAdapter(HandlebarsChartConfig),
     "big_number": TypeAdapter(BigNumberChartConfig),
+    "histogram": TypeAdapter(HistogramChartConfig),
+    "box_plot": TypeAdapter(BoxPlotChartConfig),

Review Comment:
   Confirmed and fixed in a375284697 β€” both remaining 7-type passages (server instructions and the get_chart_type_schema docstring) now list all 9 types.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575724731


##########
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:
   Fixed in the latest push β€” pre_validate accepts either distribute_across or its columns alias, with a test.



##########
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:
   Fixed in the latest push β€” whiskerOptions is always consumed; an explicit whisker_type takes precedence, so sending both keys no longer trips the unknown-field check. With a test.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575758151


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,197 @@
+# 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.
+
+"""Histogram chart type plugin."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any, ClassVar
+
+from superset.mcp_service.chart.chart_utils import (
+    _summarize_filters,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        def _is_numeric(col: dict[str, Any]) -> bool:
+            if col.get("is_numeric", False):
+                return True
+            # Backends report many spellings (BIGINT, SMALLINT, REAL, NUMBER,
+            # DOUBLE PRECISION); match numeric tokens at word boundaries so
+            # INTERVAL/POINT (which merely contain "INT") stay non-numeric.
+            type_upper = str(col.get("type", "")).upper()
+            return bool(
+                re.search(
+                    r"\b(?:TINY|SMALL|MEDIUM|BIG)?INT(?:EGER)?\b"
+                    r"|\bFLOAT\b|\bDOUBLE\b|\bDECIMAL\b"
+                    r"|\bNUMERIC\b|\bREAL\b|\bNUMBER\b",
+                    type_upper,
+                )
+            )
+
+        if _is_numeric(col_info):
+            return None
+
+        numeric_columns = sorted(
+            col["name"] for col in dataset_context.available_columns if _is_numeric(col)
+        )
+        return ChartGenerationError(
+            error_type="non_numeric_histogram_column",
+            message=(
+                f"Histograms bin numeric values; column "
+                f"'{config.column.name}' has type "
+                f"{col_info.get('type', 'UNKNOWN')}."
+            ),
+            details=(
+                "The Explore UI restricts the histogram column to numeric "
+                "columns; non-numeric columns fail or produce meaningless "
+                "bins at query time."
+            ),
+            suggestions=(
+                [f"Numeric columns in this dataset: {', '.join(numeric_columns)}"]
+                if numeric_columns
+                else ["Use get_dataset_info to inspect the dataset's columns"]
+            )
+            + ["For category frequencies, use chart_type='xy' with kind='bar'"],
+            error_code="NON_NUMERIC_HISTOGRAM_COLUMN",
+        )
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "histogram_v2"

Review Comment:
   **Suggestion:** The plugin resolves histogram charts to `histogram_v2`, but downstream preview/type-mapping code in the MCP chart-preview path still classifies only `histogram`. This causes histogram charts created through this plugin to be misclassified and rendered through fallback preview logic. Keep the viz-type contract aligned across tools by updating downstream viz-type mappings to include `histogram_v2` wherever histogram is recognized. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Histogram Vega-Lite preview renders scatter, misrepresenting distribution.
   - ⚠️ LLM-driven chart previews mislead users about histogram charts.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Create or identify a chart whose Superset viz_type is "histogram_v2" (e.g., via MCP
   generate_chart using HistogramChartConfig, which maps to form_data with viz_type
   "histogram_v2" in superset/mcp_service/chart/chart_utils.py:45-47, or an existing Explore
   histogram chart).
   
   2. Call the MCP tool get_chart_preview
   (superset/mcp_service/chart/tool/get_chart_preview.py:143-145) with a
   GetChartPreviewRequest (superset/mcp_service/chart/schemas.py:2498-2534) specifying
   identifier equal to that chart’s ID/UUID and format="vega_lite".
   
   3. Inside VegaLitePreviewStrategy.generate()
   (superset/mcp_service/chart/tool/get_chart_preview.py:151-217), the helper
   _create_vega_lite_spec() reads viz_type = getattr(self.chart, "viz_type", "table") or
   "table" (line 249), which for this chart is "histogram_v2", then calls
   _get_chart_spec_for_type(viz_type, fields, field_types) (lines 10-12).
   
   4. In _get_chart_spec_for_type()
   (superset/mcp_service/chart/tool/get_chart_preview.py:10-50), chart_type_mapping defines
   the histogram family as "histogram": ["histogram"] and does not include "histogram_v2"
   (lines 32-33). Because chart.viz_type is "histogram_v2", no mapping matches, the loop
   falls through to the fallback path, logging "Unknown chart type 'histogram_v2', using
   scatter plot fallback" and returning _scatter_chart_spec(fields, field_types) (lines
   41-50). The resulting ChartPreview (lines 82-92) therefore exposes chart_type
   "histogram_v2" but its Vega-Lite specification renders a scatter plot instead of a
   histogram.
   ```
   </details>
   
   [![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=8122a69df34c4f2997f6f067569b3af4&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=8122a69df34c4f2997f6f067569b3af4&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/histogram.py
   **Line:** 162:163
   **Comment:**
   	*Api Mismatch: The plugin resolves histogram charts to `histogram_v2`, but downstream preview/type-mapping code in the MCP chart-preview path still classifies only `histogram`. This causes histogram charts created through this plugin to be misclassified and rendered through fallback preview logic. Keep the viz-type contract aligned across tools by updating downstream viz-type mappings to include `histogram_v2` wherever histogram is recognized.
   
   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=1454998747fe7275f506edbe1f7acf43c0eae47f599897707a54fb852b1d16b1&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=1454998747fe7275f506edbe1f7acf43c0eae47f599897707a54fb852b1d16b1&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575646541


##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,123 @@ 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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )

Review Comment:
   **Suggestion:** `box_plot` currently rejects Superset-native payload keys (`columns`/`groupby`) because the schema only accepts `distribute_across`/`dimensions` and the pre-validation path checks only those names. This breaks compatibility when callers send existing form_data vocabulary (which the chart request normalizer is intended to accept). Add input aliases (and matching pre-validate support) so `columns` maps to `distribute_across` and `groupby` maps to `dimensions`. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ generate_chart rejects box_plot configs using columns/groupby.
   - ⚠️ Superset form_data compatibility broken for box_plot inputs.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Call the MCP `generate_chart` tool (entrypoint at
   `superset/mcp_service/chart/tool/generate_chart.py:240`) with a request body containing
   `dataset_id` and `config`, where `config` uses Superset box-plot form_data keys:
   `"chart_type": "box_plot"`, `"metrics": [...]`, `"columns": [{"name": "day_of_week"}]`,
   and `"groupby": [{"name": "region"}]`, matching the frontend contract documented in
   `map_box_plot_config()` at `superset/mcp_service/chart/chart_utils.py:21-27`.
   
   2. The tool passes this raw `request_data` into
   `ValidationPipeline.validate_request_with_warnings()` at
   `superset/mcp_service/chart/validation/pipeline.py:90-103`, which in turn calls
   `SchemaValidator.validate_request()` at
   `superset/mcp_service/chart/validation/schema_validator.py:5-27`.
   
   3. During pre-validation, `_pre_validate_chart_type()` at `schema_validator.py:38-52`
   fetches `BoxPlotChartPlugin` and invokes its `pre_validate()` method at
   `superset/mcp_service/chart/plugins/box_plot.py:44-75`, which only checks for the presence
   of `"metrics"` and `"distribute_across"` keys; because the request uses `"columns"`
   instead of `"distribute_across"`, `pre_validate()` adds the missing `'distribute_across'`
   field to `missing_fields` and returns a `ChartGenerationError` with error code
   `MISSING_BOX_PLOT_FIELDS`.
   
   4. Even if plugin pre-validation were bypassed or updated, the Pydantic layer still
   rejects the same request: `GenerateChartRequest` at
   `superset/mcp_service/chart/schemas.py:2151-2227` validates its `config` field as
   `ChartConfig`, whose `box_plot` variant is `BoxPlotChartConfig` at `schemas.py:1892-76`;
   `UnknownFieldCheckMixin.check_unknown_fields()` at `schemas.py:22-28` computes known keys
   from model fields (including validation aliases via `_get_known_fields()` at
   `schemas.py:11-26`), and because `BoxPlotChartConfig` declares only `distribute_across`
   and `dimensions` (lines 1904-1925) with no `validation_alias` for `"columns"` or
   `"groupby"`, `_check_unknown_fields()` flags `columns`/`groupby` as unknown and raises a
   `ValueError`, causing valid Superset-style payloads to fail schema validation.
   ```
   </details>
   
   [![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=86b776d64bd64d378491d19a7ab0ba80&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=86b776d64bd64d378491d19a7ab0ba80&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:** 1904:1913
   **Comment:**
   	*Api Mismatch: `box_plot` currently rejects Superset-native payload keys (`columns`/`groupby`) because the schema only accepts `distribute_across`/`dimensions` and the pre-validation path checks only those names. This breaks compatibility when callers send existing form_data vocabulary (which the chart request normalizer is intended to accept). Add input aliases (and matching pre-validate support) so `columns` maps to `distribute_across` and `groupby` maps to `dimensions`.
   
   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=134c2808a8c3918c091d32b3a6f7b3e5a095e729c4aa12844b2a68838d9d0f01&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=134c2808a8c3918c091d32b3a6f7b3e5a095e729c4aa12844b2a68838d9d0f01&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,123 @@ 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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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
+    )

Review Comment:
   **Suggestion:** The schema does not accept frontend `whiskerOptions` input, so requests based on existing Superset box-plot form_data fail with unknown-field errors even though this plugin emits/consumes that contract downstream. Add an input compatibility path that accepts `whiskerOptions` and converts it to `whisker_type` (+ percentiles when needed) before validation. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Box-plot configs with whiskerOptions fail validation.
   - ⚠️ Native Superset box_plot form_data cannot be reused.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Construct a `generate_chart` MCP request targeting a box plot where the caller reuses
   Superset-native form_data produced by existing charts (or by `map_box_plot_config()`),
   with `config` containing `"chart_type": "box_plot"`, `"metrics": [...]`,
   `"distribute_across": [...]`, and `"whiskerOptions": "Tukey"` (or the `<low>/<high>
   percentiles` string), instead of the new schema fields `whisker_type`, `percentile_low`,
   and `percentile_high`; this mirrors the frontend contract documented in
   `map_box_plot_config()` at `superset/mcp_service/chart/chart_utils.py:6-27`.
   
   2. The request flows through `ValidationPipeline.validate_request_with_warnings()` at
   `superset/mcp_service/chart/validation/pipeline.py:90-103`, which calls
   `SchemaValidator.validate_request()` at
   `superset/mcp_service/chart/validation/schema_validator.py:5-27`;
   `_pre_validate_chart_type()` invokes `BoxPlotChartPlugin.pre_validate()` at
   `superset/mcp_service/chart/plugins/box_plot.py:44-75`, which checks only for `"metrics"`
   and `"distribute_across"`, so this step passes because those keys are present and
   `whiskerOptions` is ignored.
   
   3. After pre-validation succeeds, `GenerateChartRequest` at
   `superset/mcp_service/chart/schemas.py:2151-2227` validates `config` as `ChartConfig`,
   selecting `BoxPlotChartConfig` at `schemas.py:1892-76` based on `chart_type="box_plot"`;
   `UnknownFieldCheckMixin.check_unknown_fields()` at `schemas.py:22-28` computes known keys
   from `BoxPlotChartConfig`’s declared fields (`metrics`, `distribute_across`, `dimensions`,
   `whisker_type`, `percentile_low`, `percentile_high`, etc.) via `_get_known_fields()` at
   `schemas.py:11-26.
   
   4. Because `BoxPlotChartConfig`’ whisker configuration only accepts `whisker_type` plus
   optional `percentile_low`/`percentile_high` (lines 1914-1924) and defines no
   `validation_alias` or pre-normalization for `whiskerOptions`, the top-level key
   `"whiskerOptions"` is treated as unknown, `_check_unknown_fields()` raises a `ValueError`
   with an unknown-field message, and the box-plot request fails schema validation instead of
   being translated into `whisker_type` and percentiles as expected for compatibility with
   existing Superset form_data.
   ```
   </details>
   
   [![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=4bf81db688b145f1bc7bbf4448ed9a52&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=4bf81db688b145f1bc7bbf4448ed9a52&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:** 1914:1924
   **Comment:**
   	*Api Mismatch: The schema does not accept frontend `whiskerOptions` input, so requests based on existing Superset box-plot form_data fail with unknown-field errors even though this plugin emits/consumes that contract downstream. Add an input compatibility path that accepts `whiskerOptions` and converts it to `whisker_type` (+ percentiles when needed) before validation.
   
   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=7ae400abe9f0f8b507cda14df2f022affbb106cad77963e93e57fb03dd307c62&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=7ae400abe9f0f8b507cda14df2f022affbb106cad77963e93e57fb03dd307c62&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575623310


##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,371 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+from superset.mcp_service.common.error_schemas import ChartGenerationError

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag Python imports as unused when they are referenced only in string type annotations; Ruff recognizes these as used in typing contexts.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565446720


##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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"

Review Comment:
   **Suggestion:** Add explicit class-level type annotations for these string constants (for example as `ClassVar[str]`) to satisfy the type-hint requirement. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   These class-level string constants are unannotated even though they can be typed as ClassVar[str], which matches the Python type-hint requirement.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=fa1cebd29cfb49328783228b29881b93&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=fa1cebd29cfb49328783228b29881b93&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:** 38:39
   **Comment:**
   	*Custom Rule: Add explicit class-level type annotations for these string constants (for example as `ClassVar[str]`) to satisfy the type-hint 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=26b7d4c9934fb33ba2e2c3c8c05b2dd2b931cbd0a7fe73046be654de44d772eb&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=26b7d4c9934fb33ba2e2c3c8c05b2dd2b931cbd0a7fe73046be654de44d772eb&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",
+    "min_max": "Min/max (no outliers)",
+}

Review Comment:
   **Suggestion:** Add an explicit type annotation for this new module-level mapping constant to satisfy the required type-hint rule for relevant variables. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This is a new module-level mapping constant in Python and it is unannotated. The custom rule requires type hints for relevant variables that can be annotated, so the suggestion matches a real violation.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=b7bf702b7ef446a7805930c0c19710f9&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=b7bf702b7ef446a7805930c0c19710f9&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/chart_utils.py
   **Line:** 919:922
   **Comment:**
   	*Custom Rule: Add an explicit type annotation for this new module-level mapping constant to satisfy the required type-hint rule for relevant variables.
   
   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=d3374c1f6b3ee09864538968265a0eab0828792b70649d7c0bb445caa50ac346&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=d3374c1f6b3ee09864538968265a0eab0828792b70649d7c0bb445caa50ac346&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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 = []

Review Comment:
   **Suggestion:** Add a concrete type annotation to this list initialization so its intended element type is explicit. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The local variable is an empty list that can be concretely annotated as list[str], so leaving it untyped violates the type-hint requirement.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=efeb79a87e894a669ad803cd08a95b6a&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=efeb79a87e894a669ad803cd08a95b6a&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:** 48:48
   **Comment:**
   	*Custom Rule: Add a concrete type annotation to this list initialization so its intended element type is explicit.
   
   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=4cadf1557bb0142d162c7c8d777ad9f3a1d98eea0cf4753472e04c003ee4778f&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=4cadf1557bb0142d162c7c8d777ad9f3a1d98eea0cf4753472e04c003ee4778f&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565583722


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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."""
+

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["viz_type"] == "histogram_v2"
+        assert form_data["column"] == "trip_duration"
+        assert form_data["groupby"] == []
+        assert form_data["bins"] == 5
+        assert form_data["normalize"] is False
+        assert form_data["cumulative"] is False
+
+    def test_histogram_form_data_with_options_and_filters(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["bins"] == 20
+        assert form_data["normalize"] is True
+        assert form_data["cumulative"] is True
+        assert form_data["adhoc_filters"], "filters must map to adhoc_filters"
+
+
+class TestBoxPlotChartConfigSchema:
+    """BoxPlotChartConfig schema validation."""
+
+    def test_basic_box_plot_config(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+        )
+        assert config.whisker_type == "tukey"
+        assert config.dimensions is None
+        assert [c.name for c in config.distribute_across] == ["day_of_week"]
+
+    def test_box_plot_missing_metrics(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                distribute_across=[{"name": "day"}],
+            )
+
+    def test_box_plot_empty_distribute_across(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[],
+            )
+
+    def test_box_plot_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                bogus=1,
+            )
+
+    def test_box_plot_percentile_requires_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+            )
+
+    def test_box_plot_percentile_bounds_must_be_ordered(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+                percentile_low=90,
+                percentile_high=10,
+            )
+
+    def test_chart_config_union_dispatches_box_plot(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {
+                "chart_type": "box_plot",
+                "metrics": [{"name": "fare", "aggregate": "AVG"}],
+                "distribute_across": [{"name": "day"}],
+            }
+        )
+        assert isinstance(config, BoxPlotChartConfig)
+
+
+class TestMapBoxPlotConfig:
+    """form_data mapping must match the frontend BoxPlot buildQuery."""
+
+    def test_basic_box_plot_form_data(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+            dimensions=[{"name": "payment_type"}],
+        )
+        form_data = map_box_plot_config(config)
+        assert form_data["viz_type"] == "box_plot"
+        assert form_data["columns"] == ["day_of_week"]
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["whiskerOptions"] == "Tukey"
+        assert len(form_data["metrics"]) == 1
+
+    def test_box_plot_whisker_min_max(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="min_max",
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == (
+            "Min/max (no outliers)"
+        )
+
+    def test_box_plot_whisker_percentiles(self) -> None:
+        """Percentiles serialize to the exact string the frontend
+        boxplotOperator PERCENTILE_REGEX parses: '<low>/<high> percentiles'."""
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="percentile",
+            percentile_low=10,
+            percentile_high=90,
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == "10/90 percentiles"
+
+
+class TestPluginRegistry:
+    """Both plugins must be registered and resolve their viz types."""
+
+    def test_histogram_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,315 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+
+
+class TestHistogramChartConfigSchema:
+    """HistogramChartConfig schema validation."""
+
+    def test_basic_histogram_config(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "trip_duration"},
+        )
+        assert config.column.name == "trip_duration"
+        assert config.bins == 5  # frontend controlPanel default
+        assert config.normalize is False
+        assert config.cumulative is False
+        assert config.groupby is None
+
+    def test_histogram_config_with_all_options(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            row_limit=500,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        assert config.bins == 20
+        assert config.normalize is True
+        assert config.cumulative is True
+        assert [g.name for g in config.groupby or []] == ["payment_type"]
+
+    def test_histogram_missing_column(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram")
+
+    def test_histogram_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "x"},
+                nonsense_field=True,
+            )
+
+    def test_histogram_bins_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(chart_type="histogram", column={"name": "x"}, bins=0)
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram", column={"name": "x"}, bins=1001
+            )
+
+    def test_histogram_column_rejects_saved_metric(self) -> None:
+        """The binned column is a physical column, not a metric."""
+        with pytest.raises(ValidationError):
+            HistogramChartConfig(
+                chart_type="histogram",
+                column={"name": "count", "saved_metric": True},
+            )
+
+    def test_chart_config_union_dispatches_histogram(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {"chart_type": "histogram", "column": {"name": "duration"}}
+        )
+        assert isinstance(config, HistogramChartConfig)
+
+
+class TestMapHistogramConfig:
+    """form_data mapping must match the frontend Histogram buildQuery."""
+
+    def test_basic_histogram_form_data(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram", column={"name": "trip_duration"}
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["viz_type"] == "histogram_v2"
+        assert form_data["column"] == "trip_duration"
+        assert form_data["groupby"] == []
+        assert form_data["bins"] == 5
+        assert form_data["normalize"] is False
+        assert form_data["cumulative"] is False
+
+    def test_histogram_form_data_with_options_and_filters(self) -> None:
+        config = HistogramChartConfig(
+            chart_type="histogram",
+            column={"name": "fare_amount"},
+            groupby=[{"name": "payment_type"}],
+            bins=20,
+            normalize=True,
+            cumulative=True,
+            filters=[{"column": "year", "op": "=", "value": 2026}],
+        )
+        form_data = map_histogram_config(config)
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["bins"] == 20
+        assert form_data["normalize"] is True
+        assert form_data["cumulative"] is True
+        assert form_data["adhoc_filters"], "filters must map to adhoc_filters"
+
+
+class TestBoxPlotChartConfigSchema:
+    """BoxPlotChartConfig schema validation."""
+
+    def test_basic_box_plot_config(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+        )
+        assert config.whisker_type == "tukey"
+        assert config.dimensions is None
+        assert [c.name for c in config.distribute_across] == ["day_of_week"]
+
+    def test_box_plot_missing_metrics(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                distribute_across=[{"name": "day"}],
+            )
+
+    def test_box_plot_empty_distribute_across(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[],
+            )
+
+    def test_box_plot_rejects_extra_fields(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                bogus=1,
+            )
+
+    def test_box_plot_percentile_requires_bounds(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+            )
+
+    def test_box_plot_percentile_bounds_must_be_ordered(self) -> None:
+        with pytest.raises(ValidationError):
+            BoxPlotChartConfig(
+                chart_type="box_plot",
+                metrics=[{"name": "x", "aggregate": "AVG"}],
+                distribute_across=[{"name": "day"}],
+                whisker_type="percentile",
+                percentile_low=90,
+                percentile_high=10,
+            )
+
+    def test_chart_config_union_dispatches_box_plot(self) -> None:
+        config = TypeAdapter(ChartConfig).validate_python(
+            {
+                "chart_type": "box_plot",
+                "metrics": [{"name": "fare", "aggregate": "AVG"}],
+                "distribute_across": [{"name": "day"}],
+            }
+        )
+        assert isinstance(config, BoxPlotChartConfig)
+
+
+class TestMapBoxPlotConfig:
+    """form_data mapping must match the frontend BoxPlot buildQuery."""
+
+    def test_basic_box_plot_form_data(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "fare_amount", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day_of_week"}],
+            dimensions=[{"name": "payment_type"}],
+        )
+        form_data = map_box_plot_config(config)
+        assert form_data["viz_type"] == "box_plot"
+        assert form_data["columns"] == ["day_of_week"]
+        assert form_data["groupby"] == ["payment_type"]
+        assert form_data["whiskerOptions"] == "Tukey"
+        assert len(form_data["metrics"]) == 1
+
+    def test_box_plot_whisker_min_max(self) -> None:
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="min_max",
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == (
+            "Min/max (no outliers)"
+        )
+
+    def test_box_plot_whisker_percentiles(self) -> None:
+        """Percentiles serialize to the exact string the frontend
+        boxplotOperator PERCENTILE_REGEX parses: '<low>/<high> percentiles'."""
+        config = BoxPlotChartConfig(
+            chart_type="box_plot",
+            metrics=[{"name": "x", "aggregate": "AVG"}],
+            distribute_across=[{"name": "day"}],
+            whisker_type="percentile",
+            percentile_low=10,
+            percentile_high=90,
+        )
+        assert map_box_plot_config(config)["whiskerOptions"] == "10/90 percentiles"
+
+
+class TestPluginRegistry:
+    """Both plugins must be registered and resolve their viz types."""
+
+    def test_histogram_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")
+        assert plugin is not None
+        assert plugin.resolve_viz_type(None) == "histogram_v2"
+
+    def test_box_plot_plugin_registered(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("box_plot")
+        assert plugin is not None
+        assert plugin.resolve_viz_type(None) == "box_plot"
+
+    def test_display_names_resolve(self) -> None:
+        from superset.mcp_service.chart.registry import display_name_for_viz_type
+
+        assert display_name_for_viz_type("histogram_v2") == "Histogram"
+        assert display_name_for_viz_type("box_plot") == "Box Plot"
+
+    def test_histogram_pre_validate_missing_column(self) -> None:
+        from superset.mcp_service.chart import registry
+
+        plugin = registry.get("histogram")
+        assert plugin is not None
+        error = plugin.pre_validate({"chart_type": "histogram"})

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (same pattern in all in-tree chart plugins and test suites); mypy passes. Identical future findings will be declined the same way.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575581099


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,199 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        def _is_numeric(col: dict[str, Any]) -> bool:
+            if col.get("is_numeric", False):
+                return True
+            # Backends report many spellings (BIGINT, SMALLINT, REAL,
+            # NUMBER, DOUBLE PRECISION); substring-match the common stems.
+            type_upper = str(col.get("type", "")).upper()
+            return any(
+                stem in type_upper
+                for stem in (
+                    "INT",
+                    "FLOAT",
+                    "DOUBLE",
+                    "DECIMAL",
+                    "NUMERIC",
+                    "REAL",
+                    "NUMBER",
+                )
+            )

Review Comment:
   **Suggestion:** The numeric type fallback check is too broad: matching `"INT"` as a substring will incorrectly treat non-numeric types like `INTERVAL` (and other type names containing `INT`) as numeric, so invalid histogram columns can pass validation and fail later at query time. Tighten this check to exact numeric type families (or explicit tokenized matching) and exclude known non-numeric types. [incorrect condition logic]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Histogram tool accepts non-numeric bin columns silently.
   - ⚠️ Agents may build histograms on invalid column types.
   - ⚠️ Downstream queries can fail or produce meaningless bins.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Define a Superset dataset whose ORM column has type rendered as "INTERVAL" and
   is_numeric=False, so build_dataset_context_from_orm() in
   superset/mcp_service/chart/validation/dataset_validator.py:42-60 produces an
   available_columns entry {"name": "event_duration", "type": "INTERVAL", "is_numeric":
   False}.
   
   2. From an MCP client, call the generate_chart tool in
   superset/mcp_service/chart/tool/generate_chart.py:98-120 with
   request.config.chart_type="histogram" and config.column={"name": "event_duration"}
   pointing at that INTERVAL column and a valid dataset_id.
   
   3. The request passes ValidationPipeline.validate_request_with_warnings() in
   superset/mcp_service/chart/validation/pipeline.py:90-145, which builds a DatasetContext
   via ValidationPipeline._get_dataset_context() (pipeline.py:165-172) and
   DatasetValidator._get_dataset_context() (dataset_validator.py:249-259); column existence
   validation succeeds because "event_duration" is present.
   
   4. When the chart is compiled, chart_utils.map_config_to_form_data() in
   superset/mcp_service/chart/chart_utils.py:368-56 looks up HistogramChartPlugin and calls
   HistogramChartPlugin.post_map_validate() in
   superset/mcp_service/chart/plugins/histogram.py:87-162; inside _is_numeric() at lines
   118-135, col_info["is_numeric"] is False, type_upper becomes "INTERVAL", and the substring
   check finds "INT" in "INTERVAL", causing _is_numeric() to incorrectly return True so the
   non-numeric INTERVAL column is accepted as numeric and no ChartGenerationError is raised,
   allowing an invalid histogram bin column through validation and into query execution.
   ```
   </details>
   
   [![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=4c068a98ce3b4a589b12e7f143d9c0de&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=4c068a98ce3b4a589b12e7f143d9c0de&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/histogram.py
   **Line:** 124:135
   **Comment:**
   	*Incorrect Condition Logic: The numeric type fallback check is too broad: matching `"INT"` as a substring will incorrectly treat non-numeric types like `INTERVAL` (and other type names containing `INT`) as numeric, so invalid histogram columns can pass validation and fail later at query time. Tighten this check to exact numeric type families (or explicit tokenized matching) and exclude known non-numeric types.
   
   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=d04f909518ae2d0566b49696cc2d469daa3f927dbfb2ef35f3647244f084d70f&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=d04f909518ae2d0566b49696cc2d469daa3f927dbfb2ef35f3647244f084d70f&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575564706


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag module-level constants, locals, or lambdas with inferable types for missing type annotations when the codebase already follows this pattern and mypy passes; only flag cases where the type is ambiguous or a type hint is required for correctness.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575621619


##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,123 @@ 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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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(100, description="Max boxes", ge=1, le=10000)

Review Comment:
   Confirmed and fixed in 3f5f5123ad β€” row_limit now defaults to 10000 (le=50000), matching the frontend's shared control.



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,371 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+from superset.mcp_service.common.error_schemas import ChartGenerationError

Review Comment:
   Declining as inaccurate: ruff parses string annotations in typing contexts, so the import is recognized as used β€” `ruff check` passes on this file locally and in the repo's pre-commit config (no F401). CI's pre-commit run on the current HEAD confirms.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #41860:
URL: https://github.com/apache/superset/pull/41860#issuecomment-4965416581

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #41860:
URL: https://github.com/apache/superset/pull/41860#issuecomment-4969937188

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575684758


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",
+    "min_max": "Min/max (no outliers)",
+}
+
+
+def map_box_plot_config(config: "BoxPlotChartConfig") -> Dict[str, Any]:
+    """Map box plot config to Superset form_data (viz_type box_plot).
+
+    Matches the frontend BoxPlot buildQuery contract: ``columns`` are the
+    distribute-across values (one box per value), ``groupby`` the series
+    dimensions, and ``whiskerOptions`` one of the strings the
+    boxplotOperator post-processor parses.
+    """
+    if config.whisker_type == "percentile":
+        whisker_options = (
+            f"{config.percentile_low}/{config.percentile_high} percentiles"
+        )
+    else:
+        whisker_options = _WHISKER_TYPE_TO_OPTION[config.whisker_type]
+
+    form_data: Dict[str, Any] = {
+        "viz_type": "box_plot",
+        "columns": [c.name for c in config.distribute_across],
+        "groupby": [d.name for d in (config.dimensions or [])],
+        "metrics": [create_metric_object(m) for m in config.metrics],

Review Comment:
   Confirmed against the pandas boxplot post-processor (its docstring: groupby = 'the categories to group by (x-axis)') and fixed in the latest push. The form_data key mapping was correct but the schema's semantic contract was inverted: descriptions/examples/docstrings/name-generation now state that dimensions (form_data groupby) splits boxes while distribute_across (form_data columns) is the sample axis; the schema example includes dimensions so the minimal path no longer produces a collapsed single box. Regression test asserts the semantics explicitly.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,123 @@ 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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )

Review Comment:
   Fixed in the latest push β€” 'columns' and 'groupby' are accepted as validation aliases for distribute_across/dimensions, per the codebase's translate-don't-refuse convention, with a test.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,123 @@ 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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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
+    )

Review Comment:
   Fixed in the latest push β€” whiskerOptions display strings (Tukey, Min/max (no outliers), '<low>/<high> percentiles') are translated by a before-validator into whisker_type/percentiles; unsupported strings get a clear error pointing at whisker_type. Tests cover translation and rejection.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575613477


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,199 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        def _is_numeric(col: dict[str, Any]) -> bool:
+            if col.get("is_numeric", False):
+                return True
+            # Backends report many spellings (BIGINT, SMALLINT, REAL,
+            # NUMBER, DOUBLE PRECISION); substring-match the common stems.
+            type_upper = str(col.get("type", "")).upper()
+            return any(
+                stem in type_upper
+                for stem in (
+                    "INT",
+                    "FLOAT",
+                    "DOUBLE",
+                    "DECIMAL",
+                    "NUMERIC",
+                    "REAL",
+                    "NUMBER",
+                )
+            )

Review Comment:
   Confirmed and fixed in the latest push β€” numeric tokens now match at word boundaries (\b(?:TINY|SMALL|MEDIUM|BIG)?INT(?:EGER)?\b etc.), so INTERVAL and POINT stay non-numeric; tests cover both directions.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3578270540


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,197 @@
+# 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.
+
+"""Histogram chart type plugin."""
+
+from __future__ import annotations
+
+import re
+from collections.abc import Mapping
+from typing import Any, ClassVar
+
+from superset.mcp_service.chart.chart_utils import (
+    _summarize_filters,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        def _is_numeric(col: dict[str, Any]) -> bool:
+            if col.get("is_numeric", False):
+                return True
+            # Backends report many spellings (BIGINT, SMALLINT, REAL, NUMBER,
+            # DOUBLE PRECISION); match numeric tokens at word boundaries so
+            # INTERVAL/POINT (which merely contain "INT") stay non-numeric.
+            type_upper = str(col.get("type", "")).upper()
+            return bool(
+                re.search(
+                    r"\b(?:TINY|SMALL|MEDIUM|BIG)?INT(?:EGER)?\b"
+                    r"|\bFLOAT\b|\bDOUBLE\b|\bDECIMAL\b"
+                    r"|\bNUMERIC\b|\bREAL\b|\bNUMBER\b",
+                    type_upper,
+                )
+            )
+
+        if _is_numeric(col_info):
+            return None
+
+        numeric_columns = sorted(
+            col["name"] for col in dataset_context.available_columns if _is_numeric(col)
+        )
+        return ChartGenerationError(
+            error_type="non_numeric_histogram_column",
+            message=(
+                f"Histograms bin numeric values; column "
+                f"'{config.column.name}' has type "
+                f"{col_info.get('type', 'UNKNOWN')}."
+            ),
+            details=(
+                "The Explore UI restricts the histogram column to numeric "
+                "columns; non-numeric columns fail or produce meaningless "
+                "bins at query time."
+            ),
+            suggestions=(
+                [f"Numeric columns in this dataset: {', '.join(numeric_columns)}"]
+                if numeric_columns
+                else ["Use get_dataset_info to inspect the dataset's columns"]
+            )
+            + ["For category frequencies, use chart_type='xy' with kind='bar'"],
+            error_code="NON_NUMERIC_HISTOGRAM_COLUMN",
+        )
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "histogram_v2"

Review Comment:
   Confirmed and fixed in the latest push β€” histogram_v2 added to the preview classification map, matching the dual-spelling treatment heatmap already has.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565575153


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+

Review Comment:
   **Suggestion:** Add an explicit type annotation for this class constant so it complies with the project rule requiring type hints for annotatable variables. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   The class attribute is a new annotatable Python variable, and it is currently defined without a type hint. This matches the rule requiring type hints for relevant variables in new or modified Python code.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=2f1a4715c8854b45a6c0c94300e129b6&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=2f1a4715c8854b45a6c0c94300e129b6&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/histogram.py
   **Line:** 37:37
   **Comment:**
   	*Custom Rule: Add an explicit type annotation for this class constant so it complies with the project rule requiring type hints for annotatable variables.
   
   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=3d27225a58e9a262bcc6c62ce4f4697dd5c47a3afbb3174c6ea6f522e0656823&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=3d27225a58e9a262bcc6c62ce4f4697dd5c47a3afbb3174c6ea6f522e0656823&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "bito-code-review[bot] (via GitHub)" <gi...@apache.org>.
bito-code-review[bot] commented on PR #41860:
URL: https://github.com/apache/superset/pull/41860#issuecomment-4950051387

   AI Code Review is in progress (usually takes 3 to 15 minutes unless it's a very large PR).
   
   <details>
   <summary>
   Bito Usage Guide
   </summary>
   
   **Commands**
         
   Type the following command in the pull request comment and save the comment.
         
   - `/review` - Manually triggers a full AI review.
   
   - `/pause` - Pauses automatic reviews on this pull request.
   - `/resume` - Resumes automatic reviews.
   - `/resolve` - Marks all Bito-posted review comments as resolved.
   - `/abort` - Cancels all in-progress reviews.
   
         
   Refer to the <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/available-commands" target="_blank" rel="noopener noreferrer">documentation</a> for additional commands.
         
   **Configuration**
   
   This repository uses `Superset` You can customize the agent settings <a href="https://alpha.bito.ai/home/ai-agents/code-review-agent" target="_blank" rel="noopener noreferrer">here</a> or contact your Bito workspace admin at evan@preset.io.
         
   **Documentation & Help**
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/getting-started/install-run-using-bito-cloud/create-or-customize-an-agent-instance" target="_blank" rel="noopener noreferrer">Customize agent settings</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/implementing-custom-code-review-rules" target="_blank" rel="noopener noreferrer">Review rules</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent" target="_blank" rel="noopener noreferrer">General documentation</a>
   - <a href="https://docs.bito.ai/bito-dev-agents/ai-code-review-agent/faqs" target="_blank" rel="noopener noreferrer">FAQ</a>
   
   </details>
   


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565560393


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (the same patterns appear in all seven in-tree chart plugins and throughout mcp_service); mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (the same patterns appear in all seven in-tree chart plugins and throughout mcp_service); mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",
+    "min_max": "Min/max (no outliers)",
+}

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (the same patterns appear in all seven in-tree chart plugins and throughout mcp_service); mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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"

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (the same patterns appear in all seven in-tree chart plugins and throughout mcp_service); mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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 = []

Review Comment:
   Declining, standing rationale: constants and locals with inferable types are unannotated across this codebase (the same patterns appear in all seven in-tree chart plugins and throughout mcp_service); mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,108 @@ 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}]")

Review Comment:
   Fixed in c1cb5c2085 β€” dimension-position fields now reject saved_metric=True alongside sql_expression, with tests.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,108 @@ 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}]")
+        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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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(100, description="Max boxes", ge=1, le=10000)
+    number_format: str = Field("SMART_NUMBER", max_length=50)
+    date_format: str = Field("smart_date", max_length=50)
+
+    @model_validator(mode="after")
+    def validate_percentiles_and_dimensions(self) -> "BoxPlotChartConfig":
+        if self.whisker_type == "percentile":
+            if self.percentile_low is None or self.percentile_high is None:
+                raise ValueError(
+                    "whisker_type='percentile' requires both percentile_low "
+                    "and percentile_high"
+                )
+            if self.percentile_low >= self.percentile_high:
+                raise ValueError("percentile_low must be less than percentile_high")
+        elif self.percentile_low is not None or self.percentile_high is not None:
+            raise ValueError(
+                "percentile_low/percentile_high only apply when "
+                "whisker_type='percentile'"
+            )
+        for i, col in enumerate(self.distribute_across):
+            _reject_sql_expression_on_dimension(col, f"distribute_across[{i}]")

Review Comment:
   Fixed in c1cb5c2085 β€” dimension-position fields now reject saved_metric=True alongside sql_expression, with tests.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,108 @@ 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}]")
+        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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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(100, description="Max boxes", ge=1, le=10000)
+    number_format: str = Field("SMART_NUMBER", max_length=50)
+    date_format: str = Field("smart_date", max_length=50)
+
+    @model_validator(mode="after")
+    def validate_percentiles_and_dimensions(self) -> "BoxPlotChartConfig":
+        if self.whisker_type == "percentile":
+            if self.percentile_low is None or self.percentile_high is None:
+                raise ValueError(
+                    "whisker_type='percentile' requires both percentile_low "
+                    "and percentile_high"
+                )
+            if self.percentile_low >= self.percentile_high:
+                raise ValueError("percentile_low must be less than percentile_high")
+        elif self.percentile_low is not None or self.percentile_high is not None:
+            raise ValueError(
+                "percentile_low/percentile_high only apply when "
+                "whisker_type='percentile'"
+            )
+        for i, col in enumerate(self.distribute_across):
+            _reject_sql_expression_on_dimension(col, f"distribute_across[{i}]")
+        for i, col in enumerate(self.dimensions or []):
+            _reject_sql_expression_on_dimension(col, f"dimensions[{i}]")

Review Comment:
   Fixed in c1cb5c2085 β€” dimension-position fields now reject saved_metric=True alongside sql_expression, with tests.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3565560983


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",
+    "min_max": "Min/max (no outliers)",
+}

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag unannotated constants or locals in Python when the type is clearly inferable; this codebase intentionally leaves such variables unannotated and mypy passes.
   
   **Applied to:**
     - `**/*.py`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag unannotated constants, locals, or class-level values with clearly inferable types in superset/mcp_service; this codebase intentionally leaves such values untyped when mypy already passes.
   
   **Applied to:**
     - `superset/mcp_service/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag missing type annotations for constants, locals, or simple class attributes when the type is clearly inferable and the code passes mypy in mcp_service.
   
   **Applied to:**
     - `superset/mcp_service/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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"

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag unannotated constants or locals with inferable types in mcp_service; mypy already passes and this codebase intentionally leaves these unannotated.
   
   **Applied to:**
     - `superset/mcp_service/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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 = []

Review Comment:
   βœ… **Customized review instruction saved!**
   
   **Instruction:**
   > Do not flag unannotated constants or local variables when their types are clearly inferable; this is an accepted pattern in mcp_service and chart plugin code.
   
   **Applied to:**
     - `superset/mcp_service/**`
   
   ---
   πŸ’‘ *To manage or update this instruction, visit: [CodeAnt AI Settings](https://app.codeant.ai/org/settings/learnings)*



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "aminghadersohi (via GitHub)" <gi...@apache.org>.
aminghadersohi commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3573269884


##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,122 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "histogram_v2"
+
+    def normalize_column_refs(self, config: Any, dataset_context: Any) -> Any:
+        config_dict = config.model_dump()
+
+        column = config_dict.get("column")

Review Comment:
   MEDIUM: the histogram's binned `column` has no numeric-dtype check. Superset's own Explore UI restricts this control to `columnsByType(datasource, GenericDataType.Numeric)` (Histogram/controlPanel.tsx), but `DatasetValidator._validate_aggregations`'s numeric check only fires for `aggregate`-bearing refs β€” `column` here carries none. An LLM picking a categorical/string column passes all pre-validation and only fails (or silently produces a nonsense chart) at frontend post-processing time. `dataset_context` already carries `is_numeric` per column (used elsewhere in dataset_validator.py) β€” worth checking it here and returning a `ChartGenerationError` hint, consistent with this PR's own goal of LLM-friendly fix suggestions.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575439645


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",

Review Comment:
   **Suggestion:** Add an explicit type annotation to this newly introduced module-level mapping constant so it complies with the project’s type-hint requirement. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This is a newly introduced module-level constant without a type annotation, and the stated rule requires type hints on relevant variables that can be annotated. A dict[str, str] annotation would satisfy the rule.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=bdb7237abf9b4275b97069609654854c&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=bdb7237abf9b4275b97069609654854c&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/chart_utils.py
   **Line:** 917:920
   **Comment:**
   	*Custom Rule: Add an explicit type annotation to this newly introduced module-level mapping constant so it complies with the project’s type-hint 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=bd0463b85fac7eb70a9745768930c4389cc427c639591687a106bb5bcd8f2e2e&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=bd0463b85fac7eb70a9745768930c4389cc427c639591687a106bb5bcd8f2e2e&reaction=dislike'>πŸ‘Ž</a>



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,186 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        numeric_types = ["INTEGER", "FLOAT", "DOUBLE", "DECIMAL", "NUMERIC"]

Review Comment:
   **Suggestion:** Add a concrete local variable type annotation for this collection of SQL type names. [custom_rule]
   
   **Severity Level:** Minor 🧹
   <details>
   <summary><b>Why it matters? ⭐ </b></summary>
   
   This local variable is introduced without a type annotation even though its type is obvious and stable, so it violates the Python type-hint requirement.
   </details>
   <details>
   <summary><b>Rule source πŸ“– </b></summary>
   
   .cursor/rules/dev-standard.mdc (line 28)
   </details>
   
   [![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=3cc57a9783f142f09936ed0f49cfb868&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=3cc57a9783f142f09936ed0f49cfb868&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/histogram.py
   **Line:** 118:118
   **Comment:**
   	*Custom Rule: Add a concrete local variable type annotation for this collection of SQL type names.
   
   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=f8def7048408ed1b8f6131536f953c82fc970f0060f8eca4068c0cc08ffad61e&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=f8def7048408ed1b8f6131536f953c82fc970f0060f8eca4068c0cc08ffad61e&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "netlify[bot] (via GitHub)" <gi...@apache.org>.
netlify[bot] commented on PR #41860:
URL: https://github.com/apache/superset/pull/41860#issuecomment-4964535925

   ### <span aria-hidden="true">βœ…</span> Deploy Preview for *superset-docs-preview* ready!
   
   
   |  Name | Link |
   |:-:|------------------------|
   |<span aria-hidden="true">πŸ”¨</span> Latest commit | 24eb4246290fad2ddb53c53fc92291773cbf9870 |
   |<span aria-hidden="true">πŸ”</span> Latest deploy log | https://app.netlify.com/projects/superset-docs-preview/deploys/6a559108948fb000083af3b6 |
   |<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-41860--superset-docs-preview.netlify.app](https://deploy-preview-41860--superset-docs-preview.netlify.app) |
   |<span aria-hidden="true">πŸ“±</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTQxODYwLS1zdXBlcnNldC1kb2NzLXByZXZpZXcubmV0bGlmeS5hcHAifQ.Ci-j2cUQvpl5IRyFQ8vCU_pAgEMkFcS2rDjwWo_2Up0)<br /><br />_Use your smartphone camera to open QR code link._</details> |
   |<span aria-hidden="true">πŸ€–</span> Make changes | [Run an agent on this branch](https://app.netlify.com/projects/superset-docs-preview/agent-runs#DYpBCoAgEAD_suekwgrtBz1jTa0gdVGDIvp7O6dhmBcop0AVZoAGTMa47uzeYW3DSmI_Sk1bxiBMuulMVdB5bUcsfFvH4Vks_xOOo-47pQflTccoiV6aCb4f) |
   ---
   <!-- [superset-docs-preview Preview](https://deploy-preview-41860--superset-docs-preview.netlify.app) -->
   _To edit notification comments on pull requests, go to your [Netlify project configuration](https://app.netlify.com/projects/superset-docs-preview/configuration/notifications#deploy-notifications)._


-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "gkneighb (via GitHub)" <gi...@apache.org>.
gkneighb commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575563008


##########
superset/mcp_service/chart/chart_utils.py:
##########
@@ -891,6 +893,64 @@ def map_pie_config(config: PieChartConfig) -> Dict[str, Any]:
     return form_data
 
 
+def map_histogram_config(config: "HistogramChartConfig") -> Dict[str, Any]:
+    """Map histogram config to Superset form_data (viz_type histogram_v2).
+
+    Matches the frontend Histogram buildQuery contract: a single ``column``
+    string to bin, ``groupby`` name list for series, plus bins/normalize/
+    cumulative passed straight through to the histogram post-processing
+    operator.
+    """
+    form_data: Dict[str, Any] = {
+        "viz_type": "histogram_v2",
+        "column": config.column.name,
+        "groupby": [g.name for g in (config.groupby or [])],
+        "bins": config.bins,
+        "normalize": config.normalize,
+        "cumulative": config.cumulative,
+        "row_limit": config.row_limit,
+    }
+    _add_adhoc_filters(form_data, config.filters)
+    return form_data
+
+
+# The exact strings the frontend boxplotOperator understands; the percentile
+# variant must match its PERCENTILE_REGEX: "<low>/<high> percentiles".
+_WHISKER_TYPE_TO_OPTION = {
+    "tukey": "Tukey",

Review Comment:
   Declining, standing rationale: constants, locals, and test lambdas with inferable types are unannotated across this codebase; mypy passes. Identical future findings will be declined the same way.



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,186 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        numeric_types = ["INTEGER", "FLOAT", "DOUBLE", "DECIMAL", "NUMERIC"]

Review Comment:
   Superseded in 39ec6d334b β€” the list was replaced by a stem-matching helper (see the sibling thread); the local is inside a small typed closure now.



##########
superset/mcp_service/chart/plugins/box_plot.py:
##########
@@ -0,0 +1,145 @@
+# 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 forming one box per value)"
+            )
+
+        if missing_fields:
+            return ChartGenerationError(
+                error_type="missing_box_plot_fields",
+                message=(
+                    f"Box plot missing required fields: {', '.join(missing_fields)}"
+                ),
+                details=(
+                    "Box plots show the distribution of one or more metrics "
+                    "across the values of the distribute_across columns"
+                ),
+                suggestions=[
+                    "Add 'metrics': [{'name': 'value_column', 'aggregate': 'AVG'}]",
+                    "Add 'distribute_across': [{'name': 'category_column'}]",
+                    "Example: {'chart_type': 'box_plot', 'metrics': "
+                    "[{'name': 'fare', 'aggregate': 'AVG'}], "
+                    "'distribute_across': [{'name': 'day_of_week'}]}",
+                ],
+                error_code="MISSING_BOX_PLOT_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, BoxPlotChartConfig):
+            return []
+        refs: list[ColumnRef] = []
+        refs.extend(config.metrics)
+        refs.extend(config.distribute_across)
+        refs.extend(config.dimensions or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_box_plot_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        metric_names = ", ".join(m.label or m.name for m in config.metrics)
+        across = ", ".join(c.label or c.name for c in config.distribute_across)
+        what = f"{metric_names} distribution by {across}"
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "box_plot"
+
+    def normalize_column_refs(self, config: Any, dataset_context: Any) -> Any:
+        config_dict = config.model_dump()
+
+        for metric in config_dict.get("metrics") or []:
+            if metric.get("sql_expression"):
+                continue
+            if metric.get("saved_metric"):
+                metric["name"] = DatasetValidator.get_canonical_metric_name(
+                    metric["name"], dataset_context
+                )
+            else:
+                metric["name"] = DatasetValidator.get_canonical_column_name(
+                    metric["name"], dataset_context
+                )
+        for key in ("distribute_across", "dimensions"):
+            for col in config_dict.get(key) or []:
+                if not col.get("sql_expression") and not col.get("saved_metric"):
+                    col["name"] = DatasetValidator.get_canonical_column_name(
+                        col["name"], dataset_context
+                    )
+        DatasetValidator.normalize_filters(config_dict, dataset_context)
+        return BoxPlotChartConfig.model_validate(config_dict)
+
+    def schema_error_hint(self) -> ChartGenerationError | None:
+        return ChartGenerationError(
+            error_type="box_plot_validation_error",
+            message="Box plot configuration validation failed",
+            details=(
+                "The box plot configuration is missing required fields or "
+                "has invalid structure"
+            ),
+            suggestions=[
+                "Ensure 'metrics' is a non-empty list with 'name' and 'aggregate'",

Review Comment:
   Fixed in 39ec6d334b β€” suggestions now state aggregate or saved_metric=True.



##########
superset/mcp_service/chart/plugins/histogram.py:
##########
@@ -0,0 +1,186 @@
+# 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.
+
+"""Histogram 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_histogram_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HistogramChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HistogramChartPlugin(BaseChartPlugin):
+    """Plugin for histogram chart type."""
+
+    chart_type = "histogram"
+    display_name = "Histogram"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "histogram_v2": "Histogram",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "column" not in config:
+            return ChartGenerationError(
+                error_type="missing_histogram_fields",
+                message="Histogram missing required field: 'column'",
+                details=(
+                    "Histograms bin the values of a single numeric column "
+                    "into frequency buckets"
+                ),
+                suggestions=[
+                    "Add 'column' field: {'name': 'numeric_column'}",
+                    "Example: {'chart_type': 'histogram', "
+                    "'column': {'name': 'trip_duration'}, 'bins': 10}",
+                ],
+                error_code="MISSING_HISTOGRAM_FIELDS",
+            )
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HistogramChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.column]
+        refs.extend(config.groupby or [])
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_histogram_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> str:
+        what = f"Distribution of {config.column.label or config.column.name}"
+        if config.groupby:
+            what += " by " + ", ".join(g.label or g.name for g in config.groupby)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def post_map_validate(
+        self,
+        config: Any,
+        form_data: dict[str, Any],
+        dataset_id: int | str | None = None,
+    ) -> ChartGenerationError | None:
+        """Require a numeric binned column, mirroring the Explore UI.
+
+        The frontend control panel restricts the histogram column to
+        ``GenericDataType.Numeric``; without this check an LLM picking a
+        text column passes pre-validation and only fails at query time.
+        """
+        if not isinstance(config, HistogramChartConfig) or dataset_id is None:
+            return None
+
+        dataset_context = DatasetValidator._get_dataset_context(dataset_id)
+        if dataset_context is None:
+            return None
+
+        col_info = next(
+            (
+                col
+                for col in dataset_context.available_columns
+                if col["name"].lower() == (config.column.name or "").lower()
+            ),
+            None,
+        )
+        if col_info is None:
+            # Column existence is validated separately; don't double-report.
+            return None
+
+        numeric_types = ["INTEGER", "FLOAT", "DOUBLE", "DECIMAL", "NUMERIC"]
+        if col_info.get("is_numeric", False) or (
+            col_info.get("type", "").upper() in numeric_types
+        ):
+            return None

Review Comment:
   Confirmed and fixed in 39ec6d334b β€” the fallback now stem-matches (INT/FLOAT/DOUBLE/DECIMAL/NUMERIC/REAL/NUMBER), covering BIGINT, SMALLINT, REAL, NUMBER, and DOUBLE PRECISION spellings.



-- 
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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


Re: [PR] feat(mcp): histogram and box plot chart type plugins [superset]

Posted by "codeant-ai-for-open-source[bot] (via GitHub)" <gi...@apache.org>.
codeant-ai-for-open-source[bot] commented on code in PR #41860:
URL: https://github.com/apache/superset/pull/41860#discussion_r3575591076


##########
superset/mcp_service/chart/schemas.py:
##########
@@ -1846,6 +1846,123 @@ 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,
+        description="Columns whose values form the boxes along the x-axis "
+        "(one box per value)",
+    )
+    dimensions: List[ColumnRef] | None = Field(
+        None,
+        description="Optional series dimensions (one colored box group per value)",
+    )
+    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(100, description="Max boxes", ge=1, le=10000)

Review Comment:
   **Suggestion:** The default `row_limit` for box plots is set to `100`, but the frontend box-plot control uses the shared row-limit default of `10000`. This mismatch will silently truncate query rows for MCP-generated box plots, which can materially distort quartiles/outlier detection compared with the same chart built in Explore. Align the default with the frontend/shared control default to avoid biased statistical results. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ MCP box-plot charts truncate data to 100 boxes.
   - ❌ Quartile and outlier statistics differ from Explore box plots.
   - ⚠️ Agents misinterpret distributions due to silently truncated series.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Start the MCP service and ensure the `generate_chart` tool is available; its chart
   config discriminator is defined in `superset/mcp_service/chart/schemas.py:1966-1975` as
   `ChartConfig = Annotated[..., Field(discriminator="chart_type")]`, which includes
   `BoxPlotChartConfig`.
   
   2. Call the MCP `generate_chart` tool with a request that resolves to
   `chart_type="box_plot"` (via either explicit `chart_type` or `viz_type="box_plot"` mapped
   in the plugin under `superset/mcp_service/chart/plugins/box_plot.py:1-200`), providing
   required `metrics` and `distribute_across` fields but omitting `row_limit`.
   
   3. The request normalization and Pydantic validation pipeline constructs a
   `BoxPlotChartConfig` instance (`superset/mcp_service/chart/schemas.py:1892-1933`); because
   `row_limit` is not provided in the request, it takes the default `100` from `row_limit:
   int = Field(100, description="Max boxes", ge=1, le=10000)` at line 1930.
   
   4. The box-plot plugin then maps this config into Superset-style `form_data` (in
   `superset/mcp_service/chart/plugins/box_plot.py`, around the form-data builder function),
   setting `row_limit=100`; the resulting query for the MCP-generated box plot is limited to
   100 category values, whereas the Explore frontend box-plot control (in the main Superset
   UI, e.g. `superset-frontend/src/explore/controlPanels/BoxPlot.tsx`) uses the shared
   default `row_limit=10000`. Comparing the MCP-generated chart to an Explore-created box
   plot on the same dataset shows truncated series and materially different quartiles/outlier
   detection because 9,900 potential rows are silently dropped in the MCP path.
   ```
   </details>
   
   [![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=531a56495865468e8060f11f6b46c677&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=531a56495865468e8060f11f6b46c677&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:** 1930:1930
   **Comment:**
   	*Logic Error: The default `row_limit` for box plots is set to `100`, but the frontend box-plot control uses the shared row-limit default of `10000`. This mismatch will silently truncate query rows for MCP-generated box plots, which can materially distort quartiles/outlier detection compared with the same chart built in Explore. Align the default with the frontend/shared control default to avoid biased statistical results.
   
   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=43c427473961cd1da570c954e5092e0091ec613212aaf1b239e13eb105fd5d46&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=43c427473961cd1da570c954e5092e0091ec613212aaf1b239e13eb105fd5d46&reaction=dislike'>πŸ‘Ž</a>



##########
tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py:
##########
@@ -0,0 +1,371 @@
+# 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.
+
+"""Tests for the histogram and box plot chart type plugins.
+
+Schema validation, form_data mapping (matching the frontend buildQuery
+contracts for viz_type ``histogram_v2`` and ``box_plot``), and registry
+integration.
+"""
+
+import pytest
+from pydantic import TypeAdapter, ValidationError
+
+from superset.mcp_service.chart.chart_utils import (
+    map_box_plot_config,
+    map_histogram_config,
+)
+from superset.mcp_service.chart.schemas import (
+    BoxPlotChartConfig,
+    ChartConfig,
+    HistogramChartConfig,
+)
+from superset.mcp_service.common.error_schemas import ChartGenerationError

Review Comment:
   **Suggestion:** The import is unused because the only reference is inside a quoted forward annotation, so Ruff/Pyflakes will raise `F401` and fail CI. Remove the import or switch the annotation to a real (non-string) type reference with postponed evaluation so the symbol is actually used. [code quality]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Linting stage fails due to unused import F401.
   - ⚠️ PRs blocked until import removed or annotation reference updated.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction βœ… </b></summary>
   
   ```mdx
   1. Open `tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py` and note the
   import of `ChartGenerationError` at line 37: `from
   superset.mcp_service.common.error_schemas import ChartGenerationError`.
   
   2. In the same file, observe that the only mention of `ChartGenerationError` is in a
   quoted return annotation at line 333 inside
   `TestHistogramNumericColumnValidation._validate`: `) -> "ChartGenerationError | None":`,
   so the name never appears as a real Python symbol reference.
   
   3. Run the linter on this file, for example `ruff
   tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py`; Ruff/Pyflakes do not
   treat strings as symbol usage, so they report `F401: 'ChartGenerationError' imported but
   unused` for the import at line 37.
   
   4. Because the repository’s CI runs linting over the test suite, this F401 causes the lint
   job (and thus the overall CI for the PR) to fail until the import is removed or the
   annotation is changed to a real type reference (e.g., `ChartGenerationError | None`).
   ```
   </details>
   
   [![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=4ec486404bc64fefaeeef57b84175b17&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=4ec486404bc64fefaeeef57b84175b17&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:** tests/unit_tests/mcp_service/chart/test_histogram_boxplot_charts.py
   **Line:** 37:37
   **Comment:**
   	*Code Quality: The import is unused because the only reference is inside a quoted forward annotation, so Ruff/Pyflakes will raise `F401` and fail CI. Remove the import or switch the annotation to a real (non-string) type reference with postponed evaluation so the symbol is actually used.
   
   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=e474057bb5b7d24295546ee60a75253bdbcc9058d1d8347a3062a3196b0a483e&reaction=like'>πŸ‘</a> | <a href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41860&comment_hash=e474057bb5b7d24295546ee60a75253bdbcc9058d1d8347a3062a3196b0a483e&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: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org