Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion superset/mcp_service/chart/chart_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -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):
Expand Down
10 changes: 10 additions & 0 deletions superset/mcp_service/chart/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions tests/unit_tests/mcp_service/chart/test_chart_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,47 @@ def test_table_chart_row_limit_validation(self) -> None:
)


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

Expand Down
32 changes: 32 additions & 0 deletions tests/unit_tests/mcp_service/chart/test_chart_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,38 @@ def test_map_xy_config_default_row_limit(self, mock_is_temporal) -> None:

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"""
Expand Down
Loading