Skip to content

feat(mcp): add pie, pivot table, and mixed timeseries chart creation support#38375

Merged
Antonio-RiveroMartnez merged 2 commits into
apache:masterfrom
aminghadersohi:amin/mcp-chart-types-pie-pivot-mixed
Mar 6, 2026
Merged

feat(mcp): add pie, pivot table, and mixed timeseries chart creation support#38375
Antonio-RiveroMartnez merged 2 commits into
apache:masterfrom
aminghadersohi:amin/mcp-chart-types-pie-pivot-mixed

Conversation

@aminghadersohi

@aminghadersohi aminghadersohi commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

User description

SUMMARY

Add three new chart types to the MCP service chart creation pipeline, expanding the set of charts that can be programmatically created via the Model Context Protocol:

  • Pie/Donut charts (chart_type="pie"): Category-based proportional visualization with configurable labels, donut mode, slice limits, and number formatting
  • Pivot Table V2 (chart_type="pivot_table"): Interactive cross-tabulation with row/column grouping, multiple aggregation functions, totals, and value formatting
  • Mixed Timeseries (chart_type="mixed_timeseries"): Dual-series charts combining two chart types (e.g., line + bar) on the same time axis with independent Y-axes and groupby support

Previously, only XY charts (line, bar, area, scatter) and tables could be created via MCP. These three chart types were viewable but not creatable, requiring users to fall back to the Superset UI.

Key changes:

  • New Pydantic schemas: PieChartConfig, PivotTableChartConfig, MixedTimeseriesChartConfig with full field validation
  • New form_data mapping functions: map_pie_config, map_pivot_table_config, map_mixed_timeseries_config
  • Updated ChartConfig discriminated union to include new types
  • Updated SchemaValidator with pre-validation for all new chart types
  • Updated MCP instructions to list new creatable chart types

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A - Backend-only changes to MCP service

TESTING INSTRUCTIONS

  1. Run the new unit tests:

    pytest tests/unit_tests/mcp_service/chart/test_new_chart_types.py -v

    Expected: 58 tests pass

  2. Run the full MCP test suite to verify no regressions:

    pytest tests/unit_tests/mcp_service/ -x -q

    Expected: All tests pass

  3. Test chart creation via MCP client:

    • Create a pie chart: generate_chart(dataset_id=X, config={"chart_type": "pie", "dimension": {"name": "category"}, "metric": {"name": "revenue", "aggregate": "SUM"}})
    • Create a pivot table: generate_chart(dataset_id=X, config={"chart_type": "pivot_table", "rows": [{"name": "product"}], "metrics": [{"name": "sales", "aggregate": "SUM"}]})
    • Create a mixed timeseries: generate_chart(dataset_id=X, config={"chart_type": "mixed_timeseries", "x": {"name": "date"}, "y": [{"name": "revenue", "aggregate": "SUM"}], "y_secondary": [{"name": "orders", "aggregate": "COUNT"}]})

ADDITIONAL INFORMATION

  • Introduces new feature or API
  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration
  • Removes existing feature or API

MCP Testing Results (localhost, 2026-03-04)

Tested via localhost MCP tools on branch amin/mcp-chart-types-pie-pivot-mixed:

Pie Chart

  • generate_chart with chart_type="pie" config using dataset 7 (vehicle_sales)
  • Config: groupby=["product_line"], metric={"name": "quantity", "aggregate": "SUM"}
  • Result: Chart created successfully with viz_type: "pie"

Pivot Table

  • generate_chart with chart_type="pivot_table" config using dataset 7
  • Config: groupby=["product_line"], metrics=[{"name": "quantity", "aggregate": "SUM"}], columns=["status"]
  • Result: Chart created successfully with viz_type: "pivot_table_v2"

Mixed Timeseries

  • generate_chart with chart_type="mixed_timeseries" config using dataset 7
  • Config: x={"name": "order_date"}, y=[{"name": "quantity", "aggregate": "SUM"}], y_secondary=[{"name": "sales", "aggregate": "SUM"}], time_grain="P1M"
  • Note: y field (not y_primary) is the correct field name for primary metrics
  • Result: Chart created successfully with viz_type: "mixed_timeseries"

All three new chart types work correctly via MCP.


CodeAnt-AI Description

Add pie, pivot table, and mixed timeseries chart creation support to MCP

What Changed

  • MCP can now create pie/donut charts, interactive pivot tables (pivot_table_v2), and mixed timeseries (dual-series line/bar/area/scatter) from a single request; form data mapping supports labels, donut mode, slice limits, row/column groupings, metrics, dual Y-axes, axis titles/formatting, and filters.
  • Request validation now recognizes "pie", "pivot_table", and "mixed_timeseries" and returns clear, actionable errors when required fields are missing; malformed non-string chart_type values are handled gracefully instead of crashing.
  • Chart names and capability/semantic resolution include the new types so generated charts get meaningful default titles and correct viz_type classification.
  • 58 unit tests added covering schemas, form_data mapping, name generation, and pre-validation for the three new chart types.

Impact

✅ Create pie/donut charts via API
✅ Create interactive pivot tables via API
✅ Fewer server crashes on malformed chart_type

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@bito-code-review

bito-code-review Bot commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #5a6b25

Actionable Suggestions - 0
Additional Suggestions - 1
  • tests/unit_tests/mcp_service/chart/test_new_chart_types.py - 1
    • Inaccurate module docstring · Line 18-23
      The module docstring claims comprehensive testing for mixed_timeseries, chart name generation, and schema validator pre-validation across all three chart types, but the implementation only includes schema validation and form_data mapping tests for pie and pivot_table. This mismatch could mislead developers. Update the docstring to match the actual test coverage.
Review Details
  • Files reviewed - 5 · Commit Range: 98251f7..98251f7
    • superset/mcp_service/app.py
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/validation/schema_validator.py
    • tests/unit_tests/mcp_service/chart/test_new_chart_types.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

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 documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@dosubot dosubot Bot added change:backend Requires changing the backend viz:charts:pie Related to the Pie chart viz:charts:pivot Related to the Pivot Table charts viz:charts:timeseries Related to Timeseries labels Mar 4, 2026
Comment on lines +142 to +174
@staticmethod
def _pre_validate_chart_type(
chart_type: str,
config: Dict[str, Any],
) -> Tuple[bool, ChartGenerationError | None]:
"""Validate chart type and dispatch to type-specific pre-validation."""
chart_type_validators = {
"xy": SchemaValidator._pre_validate_xy_config,
"table": SchemaValidator._pre_validate_table_config,
"pie": SchemaValidator._pre_validate_pie_config,
"pivot_table": SchemaValidator._pre_validate_pivot_table_config,
"mixed_timeseries": SchemaValidator._pre_validate_mixed_timeseries_config,
}

if chart_type not in chart_type_validators:
valid_types = ", ".join(chart_type_validators.keys())
return False, ChartGenerationError(
error_type="invalid_chart_type",
message=f"Invalid chart_type: '{chart_type}'",
details=f"Chart type '{chart_type}' is not supported. Must be 'xy' or "
f"'table'",
details=f"Chart type '{chart_type}' is not supported. "
f"Must be one of: {valid_types}",
suggestions=[
"Use 'chart_type': 'xy' for line, bar, area, or scatter charts",
"Use 'chart_type': 'table' for tabular data display",
"Use 'chart_type': 'pie' for pie or donut charts",
"Use 'chart_type': 'pivot_table' for interactive pivot tables",
"Use 'chart_type': 'mixed_timeseries' for dual-series time charts",
"Check spelling and ensure lowercase",
],
error_code="INVALID_CHART_TYPE",
)

# Pre-validate structure based on chart type
if chart_type == "xy":
return SchemaValidator._pre_validate_xy_config(config)
elif chart_type == "table":
return SchemaValidator._pre_validate_table_config(config)

return True, None
return chart_type_validators[chart_type](config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: If chart_type is not a string (for example, a list or dict due to malformed client input), checking membership in chart_type_validators will raise a TypeError instead of returning a structured ChartGenerationError, causing the validator to crash on bad input instead of failing gracefully. To avoid this, explicitly validate that chart_type is a string before using it as a dict key and treat non-string values as an invalid chart type. [type error]

Severity Level: Major ⚠️
- ❌ Malformed MCP chart requests can crash validation flow.
- ⚠️ MCP chart generation may return HTTP 500 on bad input.
- ⚠️ Clients receive no structured `ChartGenerationError` response.
- ⚠️ Harder to debug invalid chart_type issues for integrators.
Suggested change
@staticmethod
def _pre_validate_chart_type(
chart_type: str,
config: Dict[str, Any],
) -> Tuple[bool, ChartGenerationError | None]:
"""Validate chart type and dispatch to type-specific pre-validation."""
chart_type_validators = {
"xy": SchemaValidator._pre_validate_xy_config,
"table": SchemaValidator._pre_validate_table_config,
"pie": SchemaValidator._pre_validate_pie_config,
"pivot_table": SchemaValidator._pre_validate_pivot_table_config,
"mixed_timeseries": SchemaValidator._pre_validate_mixed_timeseries_config,
}
if chart_type not in chart_type_validators:
valid_types = ", ".join(chart_type_validators.keys())
return False, ChartGenerationError(
error_type="invalid_chart_type",
message=f"Invalid chart_type: '{chart_type}'",
details=f"Chart type '{chart_type}' is not supported. Must be 'xy' or "
f"'table'",
details=f"Chart type '{chart_type}' is not supported. "
f"Must be one of: {valid_types}",
suggestions=[
"Use 'chart_type': 'xy' for line, bar, area, or scatter charts",
"Use 'chart_type': 'table' for tabular data display",
"Use 'chart_type': 'pie' for pie or donut charts",
"Use 'chart_type': 'pivot_table' for interactive pivot tables",
"Use 'chart_type': 'mixed_timeseries' for dual-series time charts",
"Check spelling and ensure lowercase",
],
error_code="INVALID_CHART_TYPE",
)
# Pre-validate structure based on chart type
if chart_type == "xy":
return SchemaValidator._pre_validate_xy_config(config)
elif chart_type == "table":
return SchemaValidator._pre_validate_table_config(config)
return True, None
return chart_type_validators[chart_type](config)
@staticmethod
def _pre_validate_chart_type(
chart_type: str,
config: Dict[str, Any],
) -> Tuple[bool, ChartGenerationError | None]:
"""Validate chart type and dispatch to type-specific pre-validation."""
chart_type_validators = {
"xy": SchemaValidator._pre_validate_xy_config,
"table": SchemaValidator._pre_validate_table_config,
"pie": SchemaValidator._pre_validate_pie_config,
"pivot_table": SchemaValidator._pre_validate_pivot_table_config,
"mixed_timeseries": SchemaValidator._pre_validate_mixed_timeseries_config,
}
# Ensure chart_type is a string before using it as a dict key
if not isinstance(chart_type, str):
valid_types = ", ".join(chart_type_validators.keys())
return False, ChartGenerationError(
error_type="invalid_chart_type",
message=f"Invalid chart_type type: {type(chart_type).__name__}",
details="The 'chart_type' field must be a string. "
f"Must be one of: {valid_types}",
suggestions=[
"Set 'chart_type' to a string value such as 'xy', 'table', "
"'pie', 'pivot_table', or 'mixed_timeseries'",
"Check that your JSON encoder is not serializing chart_type "
"as an object or array",
],
error_code="INVALID_CHART_TYPE",
)
if chart_type not in chart_type_validators:
valid_types = ", ".join(chart_type_validators.keys())
return False, ChartGenerationError(
error_type="invalid_chart_type",
message=f"Invalid chart_type: '{chart_type}'",
details=f"Chart type '{chart_type}' is not supported. "
f"Must be one of: {valid_types}",
suggestions=[
"Use 'chart_type': 'xy' for line, bar, area, or scatter charts",
"Use 'chart_type': 'table' for tabular data display",
"Use 'chart_type': 'pie' for pie or donut charts",
"Use 'chart_type': 'pivot_table' for interactive pivot tables",
"Use 'chart_type': 'mixed_timeseries' for dual-series time charts",
"Check spelling and ensure lowercase",
],
error_code="INVALID_CHART_TYPE",
)
return chart_type_validators[chart_type](config)
Steps of Reproduction ✅
1. In any Python context (tests, MCP service), call `SchemaValidator.validate_request()`
from `superset/mcp_service/chart/validation/schema_validator.py` with a payload like:

   `{"dataset_id": 1, "config": {"chart_type": ["xy"]}}`.

2. Inside `validate_request()` (same file, lines ~36–58), `_pre_validate()` is invoked,
which extracts `config` and then `chart_type = config.get("chart_type")`.

3. Since `chart_type` is a non-empty list (`["xy"]`), the truthiness check `if not
chart_type:` in `_pre_validate()` (same file, lines ~84–101) is skipped, and it calls
`SchemaValidator._pre_validate_chart_type(chart_type, config)` at line 140.

4. In `_pre_validate_chart_type()` at
`superset/mcp_service/chart/validation/schema_validator.py:143`, Python evaluates `if
chart_type not in chart_type_validators:` where `chart_type_validators` is a dict. Because
`chart_type` is a list (an unhashable type), this membership test raises `TypeError:
unhashable type: 'list'`, causing `validate_request()` to raise an uncaught exception
instead of returning a structured `ChartGenerationError` with
`error_code="INVALID_CHART_TYPE"`.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/mcp_service/chart/validation/schema_validator.py
**Line:** 142:174
**Comment:**
	*Type Error: If `chart_type` is not a string (for example, a list or dict due to malformed client input), checking membership in `chart_type_validators` will raise a `TypeError` instead of returning a structured `ChartGenerationError`, causing the validator to crash on bad input instead of failing gracefully. To avoid this, explicitly validate that `chart_type` is a string before using it as a dict key and treat non-string values as an invalid chart type.

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.
👍 | 👎

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - fixed in 3fbc4ce. Added isinstance(chart_type, str) guard before the dict lookup so non-string values (list, dict, int, etc.) return a structured ChartGenerationError instead of raising TypeError. Also added parametrized tests covering list, dict, int, and bool inputs.

@netlify

netlify Bot commented Mar 4, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit cc2f466
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/69a9e5ef447bac0008fa4a2d
😎 Deploy Preview https://deploy-preview-38375--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Mar 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 43.30709% with 72 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.96%. Comparing base (5f0efd2) to head (cc2f466).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/chart/chart_utils.py 10.00% 72 Missing ⚠️

❌ Your project status has failed because the head coverage (99.24%) is below the target coverage (100.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #38375      +/-   ##
==========================================
+ Coverage   64.78%   64.96%   +0.18%     
==========================================
  Files        1815     2515     +700     
  Lines       71917   126848   +54931     
  Branches    22915    29248    +6333     
==========================================
+ Hits        46591    82409   +35818     
- Misses      25326    43010   +17684     
- Partials        0     1429    +1429     
Flag Coverage Δ
hive 41.66% <43.30%> (?)
mysql 63.38% <43.30%> (?)
postgres 63.45% <43.30%> (?)
presto 41.67% <43.30%> (?)
python 65.13% <43.30%> (?)
sqlite 63.07% <43.30%> (?)
unit 100.00% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aminghadersohi

Copy link
Copy Markdown
Contributor Author

Re: Bito suggestion about inaccurate module docstring in test_new_chart_types.py (Line 18-23):

The docstring is actually accurate - the test file contains all the test classes mentioned: TestMixedTimeseriesChartConfigSchema, TestMapMixedTimeseriesConfig, TestGenerateChartNameNewTypes, and TestSchemaValidatorNewTypes. All three chart types are covered for schema validation, form_data mapping, chart name generation, and pre-validation (62 tests total).

@bito-code-review

bito-code-review Bot commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #dd1dcd

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 98251f7..3fbc4ce
    • superset/mcp_service/chart/validation/schema_validator.py
    • tests/unit_tests/mcp_service/chart/test_new_chart_types.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

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 documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@aminghadersohi aminghadersohi force-pushed the amin/mcp-chart-types-pie-pivot-mixed branch from 3fbc4ce to 5366006 Compare March 4, 2026 21:03
@bito-code-review

bito-code-review Bot commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #f45be1

Actionable Suggestions - 0
Review Details
  • Files reviewed - 5 · Commit Range: c5a21c3..5366006
    • superset/mcp_service/app.py
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/validation/schema_validator.py
    • tests/unit_tests/mcp_service/chart/test_new_chart_types.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

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 documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@aminghadersohi aminghadersohi force-pushed the amin/mcp-chart-types-pie-pivot-mixed branch from 5366006 to 32d7dd3 Compare March 5, 2026 08:19
@bito-code-review

bito-code-review Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #5875b0

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/mcp_service/chart/schemas.py - 1
    • Potential incorrect donut rendering · Line 542-547
      The inner_radius field defaults to 30 and is always present, but it's only meaningful for donut charts. If donut=False, a non-zero inner_radius might cause the chart to render incorrectly as a donut, changing observable behavior.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/mcp_service/chart/schemas.py - 1
Review Details
  • Files reviewed - 5 · Commit Range: 4bc2816..32d7dd3
    • superset/mcp_service/app.py
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/validation/schema_validator.py
    • tests/unit_tests/mcp_service/chart/test_new_chart_types.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

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 documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

…support

Add three new chart types to the MCP service chart creation pipeline:

- Pie/Donut charts (chart_type="pie"): Category-based proportional
  visualization with configurable labels, donut mode, and slice limits
- Pivot Table V2 (chart_type="pivot_table"): Interactive cross-tabulation
  with row/column grouping, aggregation functions, and totals
- Mixed Timeseries (chart_type="mixed_timeseries"): Dual-series charts
  combining two chart types (e.g., line + bar) on the same time axis
  with independent Y-axes

Changes:
- Add PieChartConfig, PivotTableChartConfig, MixedTimeseriesChartConfig
  Pydantic schemas with full validation
- Add map_pie_config, map_pivot_table_config, map_mixed_timeseries_config
  form_data mapping functions
- Update ChartConfig discriminated union to include new types
- Update SchemaValidator with pre-validation for new chart types
- Update MCP instructions to list new creatable chart types
- Add 58 unit tests covering schemas, form_data mapping, chart name
  generation, and schema validator pre-validation
Add isinstance(chart_type, str) check before dict lookup in
_pre_validate_chart_type to prevent TypeError on malformed input
(e.g., chart_type passed as list or dict). Returns structured
ChartGenerationError instead of crashing.
@aminghadersohi aminghadersohi force-pushed the amin/mcp-chart-types-pie-pivot-mixed branch from 32d7dd3 to cc2f466 Compare March 5, 2026 20:22
@codeant-ai-for-open-source codeant-ai-for-open-source Bot added the size:XXL This PR changes 1000+ lines, ignoring generated files label Mar 5, 2026

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Agent Run #bc324c

Actionable Suggestions - 1
  • tests/unit_tests/mcp_service/chart/test_new_chart_types.py - 1
Additional Suggestions - 1
  • tests/unit_tests/mcp_service/chart/test_new_chart_types.py - 1
    • Unused Imports · Line 30-45
      Several imports are declared but not used in the test code, which can trigger linter warnings and clutter the codebase.
Review Details
  • Files reviewed - 5 · Commit Range: 41f0a76..cc2f466
    • superset/mcp_service/app.py
    • superset/mcp_service/chart/chart_utils.py
    • superset/mcp_service/chart/schemas.py
    • superset/mcp_service/chart/validation/schema_validator.py
    • tests/unit_tests/mcp_service/chart/test_new_chart_types.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

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 documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines +1 to +360
# 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.

"""
Unit tests for new MCP chart types: pie, pivot_table, mixed_timeseries.

Tests cover schema validation, form_data mapping, chart name generation,
and schema validator pre-validation for all three new chart types.
"""

from unittest.mock import patch

import pytest
from pydantic import ValidationError

from superset.mcp_service.chart.chart_utils import (
generate_chart_name,
map_config_to_form_data,
map_mixed_timeseries_config,
map_pie_config,
map_pivot_table_config,
)
from superset.mcp_service.chart.schemas import (
AxisConfig,
ColumnRef,
FilterConfig,
MixedTimeseriesChartConfig,
PieChartConfig,
PivotTableChartConfig,
)
from superset.mcp_service.chart.validation.schema_validator import SchemaValidator

# ============================================================
# Pie Chart Schema Tests
# ============================================================


class TestPieChartConfigSchema:
"""Test PieChartConfig Pydantic schema validation."""

def test_basic_pie_config(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM"),
)
assert config.chart_type == "pie"
assert config.dimension.name == "product"
assert config.metric.aggregate == "SUM"
assert config.donut is False
assert config.show_labels is True
assert config.label_type == "key_value_percent"

def test_donut_chart_config(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="category"),
metric=ColumnRef(name="count", aggregate="COUNT"),
donut=True,
inner_radius=40,
outer_radius=80,
)
assert config.donut is True
assert config.inner_radius == 40
assert config.outer_radius == 80

def test_pie_config_with_all_options(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="region"),
metric=ColumnRef(name="sales", aggregate="SUM", label="Total Sales"),
donut=True,
show_labels=False,
label_type="percent",
sort_by_metric=False,
show_legend=False,
row_limit=50,
number_format="$,.2f",
show_total=True,
labels_outside=False,
outer_radius=90,
inner_radius=50,
filters=[FilterConfig(column="status", op="=", value="active")],
)
assert config.show_labels is False
assert config.label_type == "percent"
assert config.row_limit == 50
assert config.show_total is True
assert config.filters is not None
assert len(config.filters) == 1

def test_pie_config_rejects_extra_fields(self) -> None:
with pytest.raises(ValidationError):
PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM"),
unknown_field="bad",
)

def test_pie_config_missing_dimension(self) -> None:
with pytest.raises(ValidationError):
PieChartConfig(
chart_type="pie",
metric=ColumnRef(name="revenue", aggregate="SUM"),
)

def test_pie_config_missing_metric(self) -> None:
with pytest.raises(ValidationError):
PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
)

def test_pie_config_row_limit_bounds(self) -> None:
with pytest.raises(ValidationError):
PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM"),
row_limit=0,
)

def test_pie_config_valid_label_types(self) -> None:
for label_type in [
"key",
"value",
"percent",
"key_value",
"key_percent",
"key_value_percent",
"value_percent",
]:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM"),
label_type=label_type,
)
assert config.label_type == label_type


# ============================================================
# Pie Chart Form Data Mapping Tests
# ============================================================


class TestMapPieConfig:
"""Test map_pie_config form_data generation."""

def test_basic_pie_form_data(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM"),
)
result = map_pie_config(config)

assert result["viz_type"] == "pie"
assert result["groupby"] == ["product"]
assert result["metric"]["aggregate"] == "SUM"
assert result["metric"]["column"]["column_name"] == "revenue"
assert result["show_labels"] is True
assert result["show_legend"] is True
assert result["label_type"] == "key_value_percent"
assert result["sort_by_metric"] is True
assert result["row_limit"] == 100
assert result["donut"] is False
assert result["color_scheme"] == "supersetColors"

def test_donut_form_data(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="category"),
metric=ColumnRef(name="count", aggregate="COUNT"),
donut=True,
inner_radius=40,
outer_radius=80,
)
result = map_pie_config(config)

assert result["donut"] is True
assert result["innerRadius"] == 40
assert result["outerRadius"] == 80

def test_pie_form_data_with_filters(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM"),
filters=[FilterConfig(column="region", op="=", value="US")],
)
result = map_pie_config(config)

assert "adhoc_filters" in result
assert len(result["adhoc_filters"]) == 1
assert result["adhoc_filters"][0]["subject"] == "region"
assert result["adhoc_filters"][0]["operator"] == "=="
assert result["adhoc_filters"][0]["comparator"] == "US"

def test_pie_form_data_custom_options(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="status"),
metric=ColumnRef(name="count", aggregate="COUNT"),
show_labels=False,
label_type="percent",
show_legend=False,
number_format="$,.2f",
show_total=True,
labels_outside=False,
)
result = map_pie_config(config)

assert result["show_labels"] is False
assert result["label_type"] == "percent"
assert result["show_legend"] is False
assert result["number_format"] == "$,.2f"
assert result["show_total"] is True
assert result["labels_outside"] is False

def test_pie_form_data_custom_metric_label(self) -> None:
config = PieChartConfig(
chart_type="pie",
dimension=ColumnRef(name="product"),
metric=ColumnRef(name="revenue", aggregate="SUM", label="Total Revenue"),
)
result = map_pie_config(config)

assert result["metric"]["label"] == "Total Revenue"
assert result["metric"]["hasCustomLabel"] is True


# ============================================================
# Pivot Table Schema Tests
# ============================================================


class TestPivotTableChartConfigSchema:
"""Test PivotTableChartConfig Pydantic schema validation."""

def test_basic_pivot_table_config(self) -> None:
config = PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product")],
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
)
assert config.chart_type == "pivot_table"
assert len(config.rows) == 1
assert len(config.metrics) == 1
assert config.aggregate_function == "Sum"
assert config.show_row_totals is True
assert config.show_column_totals is True

def test_pivot_table_with_columns(self) -> None:
config = PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product")],
columns=[ColumnRef(name="region")],
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
)
assert config.columns is not None
assert len(config.columns) == 1
assert config.columns[0].name == "region"

def test_pivot_table_with_all_options(self) -> None:
config = PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product"), ColumnRef(name="category")],
columns=[ColumnRef(name="region")],
metrics=[
ColumnRef(name="revenue", aggregate="SUM"),
ColumnRef(name="orders", aggregate="COUNT"),
],
aggregate_function="Average",
show_row_totals=False,
show_column_totals=False,
transpose=True,
combine_metric=True,
row_limit=5000,
value_format="$,.2f",
filters=[FilterConfig(column="year", op="=", value=2024)],
)
assert config.aggregate_function == "Average"
assert config.show_row_totals is False
assert config.transpose is True
assert config.combine_metric is True
assert config.row_limit == 5000

def test_pivot_table_missing_rows(self) -> None:
with pytest.raises(ValidationError):
PivotTableChartConfig(
chart_type="pivot_table",
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
)

def test_pivot_table_missing_metrics(self) -> None:
with pytest.raises(ValidationError):
PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product")],
)

def test_pivot_table_empty_rows(self) -> None:
with pytest.raises(ValidationError):
PivotTableChartConfig(
chart_type="pivot_table",
rows=[],
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
)

def test_pivot_table_rejects_extra_fields(self) -> None:
with pytest.raises(ValidationError):
PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product")],
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
unknown_field="bad",
)

def test_pivot_table_valid_aggregate_functions(self) -> None:
for agg in ["Sum", "Average", "Median", "Count", "Minimum", "Maximum"]:
config = PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product")],
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
aggregate_function=agg,
)
assert config.aggregate_function == agg


# ============================================================
# Pivot Table Form Data Mapping Tests
# ============================================================


class TestMapPivotTableConfig:
"""Test map_pivot_table_config form_data generation."""

def test_basic_pivot_form_data(self) -> None:
config = PivotTableChartConfig(
chart_type="pivot_table",
rows=[ColumnRef(name="product")],
metrics=[ColumnRef(name="revenue", aggregate="SUM")],
)
result = map_pivot_table_config(config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete Test Coverage

The test file's docstring indicates coverage for mixed_timeseries charts, chart name generation, and schema validator pre-validation, but these are not implemented in the tests. This could allow bugs in untested code paths to go undetected.

Code Review Run #bc324c


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

@Antonio-RiveroMartnez Antonio-RiveroMartnez merged commit 84a53ea into apache:master Mar 6, 2026
73 of 75 checks passed
aminghadersohi added a commit to aminghadersohi/superset that referenced this pull request Mar 17, 2026
qfcwell pushed a commit to qfcwell/superset that referenced this pull request May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:backend Requires changing the backend size/XXL size:XXL This PR changes 1000+ lines, ignoring generated files viz:charts:pie Related to the Pie chart viz:charts:pivot Related to the Pivot Table charts viz:charts:timeseries Related to Timeseries

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants