Compare commits

...

2 Commits

Author SHA1 Message Date
Amin Ghadersohi
22d1d3a878 refactor(mcp): extract _add_xy_limits helper and move series_limit tests
Extract row/series limit assignment into a private helper to keep
map_xy_config within the McCabe complexity threshold, removing the
noqa: C901 suppression. Move series_limit schema tests out of
TestRowLimit and into a dedicated TestSeriesLimit class.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 01:13:45 +00:00
Amin Ghadersohi
188fea221d feat(mcp): add series_limit to generate_chart XY config
Add series_limit parameter to XYChartConfig so callers can control
how many distinct series (group_by values) the chart renders.  When
set, the value is forwarded as form_data["series_limit"], which
Superset's echarts timeseries plugins interpret to cap the number of
rendered series lines/bars.
2026-05-21 01:13:45 +00:00
4 changed files with 90 additions and 1 deletions

View File

@@ -648,6 +648,12 @@ def _resolve_default_x_axis(
return config.model_copy(update={"x": ColumnRef(name=dataset.main_dttm_col)})
def _add_xy_limits(form_data: Dict[str, Any], config: XYChartConfig) -> None:
form_data["row_limit"] = config.row_limit
if config.series_limit is not None:
form_data["series_limit"] = config.series_limit
def map_xy_config(
config: XYChartConfig, dataset_id: int | str | None = None
) -> Dict[str, Any]:
@@ -712,7 +718,7 @@ def map_xy_config(
if x_is_temporal:
_ensure_temporal_adhoc_filter(form_data, config.x.name)
form_data["row_limit"] = config.row_limit
_add_xy_limits(form_data, config)
# Add stacking configuration
if getattr(config, "stacked", False):

View File

@@ -1304,6 +1304,16 @@ class XYChartConfig(UnknownFieldCheckMixin):
"Do NOT use adhoc_filters or raw SQL expressions.",
)
row_limit: int = Field(10000, description="Max data points", ge=1, le=50000)
series_limit: int | None = Field(
None,
description=(
"Max number of series to show when group_by is set. "
"Limits the distinct values rendered as separate lines/bars. "
"Only applies when group_by is specified."
),
ge=1,
le=10000,
)
@field_validator("group_by", mode="before")
@classmethod

View File

@@ -485,6 +485,47 @@ class TestRowLimit:
)
class TestSeriesLimit:
"""Test series_limit field on XYChartConfig."""
def test_xy_chart_series_limit_default_none(self) -> None:
"""Test that XYChartConfig series_limit defaults to None."""
config = XYChartConfig(
chart_type="xy",
x=ColumnRef(name="date"),
y=[ColumnRef(name="revenue", aggregate="SUM")],
)
assert config.series_limit is None
def test_xy_chart_series_limit_custom(self) -> None:
"""Test that XYChartConfig accepts a custom series_limit."""
config = XYChartConfig(
chart_type="xy",
x=ColumnRef(name="date"),
y=[ColumnRef(name="revenue", aggregate="SUM")],
group_by=[ColumnRef(name="region")],
series_limit=5,
)
assert config.series_limit == 5
def test_xy_chart_series_limit_validation(self) -> None:
"""Test that XYChartConfig rejects invalid series_limit values."""
with pytest.raises(ValidationError):
XYChartConfig(
chart_type="xy",
x=ColumnRef(name="date"),
y=[ColumnRef(name="revenue", aggregate="SUM")],
series_limit=0,
)
with pytest.raises(ValidationError):
XYChartConfig(
chart_type="xy",
x=ColumnRef(name="date"),
y=[ColumnRef(name="revenue", aggregate="SUM")],
series_limit=10001,
)
class TestTableChartConfigExtraFields:
"""Test TableChartConfig rejects unknown fields."""

View File

@@ -804,6 +804,38 @@ class TestMapXYConfig:
assert result["row_limit"] == 10000
@patch("superset.mcp_service.chart.chart_utils.is_column_truly_temporal")
def test_map_xy_config_series_limit(self, mock_is_temporal) -> None:
"""Test that series_limit is mapped to form_data when set."""
mock_is_temporal.return_value = True
config = XYChartConfig(
chart_type="xy",
x=ColumnRef(name="date"),
y=[ColumnRef(name="revenue", aggregate="SUM")],
kind="line",
group_by=[ColumnRef(name="region")],
series_limit=10,
)
result = map_xy_config(config)
assert result["series_limit"] == 10
@patch("superset.mcp_service.chart.chart_utils.is_column_truly_temporal")
def test_map_xy_config_no_series_limit_by_default(self, mock_is_temporal) -> None:
"""Test that series_limit is omitted from form_data when not set."""
mock_is_temporal.return_value = True
config = XYChartConfig(
chart_type="xy",
x=ColumnRef(name="date"),
y=[ColumnRef(name="revenue", aggregate="SUM")],
kind="line",
)
result = map_xy_config(config)
assert "series_limit" not in result
@patch("superset.mcp_service.chart.chart_utils.is_column_truly_temporal")
def test_map_xy_config_saved_metric(self, mock_is_temporal: Any) -> None:
"""Test XY config with saved metric emits string in metrics list"""