mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat(mcp): add time_grain parameter to XY chart generation (#37182)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -290,6 +290,10 @@ def map_xy_config(config: XYChartConfig) -> Dict[str, Any]:
|
|||||||
"metrics": metrics,
|
"metrics": metrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add time grain if specified (for temporal x-axis columns)
|
||||||
|
if config.time_grain:
|
||||||
|
form_data["time_grain_sqla"] = config.time_grain
|
||||||
|
|
||||||
# CRITICAL FIX: For time series charts, handle groupby carefully to avoid duplicates
|
# CRITICAL FIX: For time series charts, handle groupby carefully to avoid duplicates
|
||||||
# The x_axis field already tells Superset which column to use for time grouping
|
# The x_axis field already tells Superset which column to use for time grouping
|
||||||
groupby_columns = []
|
groupby_columns = []
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from pydantic import (
|
|||||||
PositiveInt,
|
PositiveInt,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from superset.constants import TimeGrain
|
||||||
from superset.daos.base import ColumnOperator, ColumnOperatorEnum
|
from superset.daos.base import ColumnOperator, ColumnOperatorEnum
|
||||||
from superset.mcp_service.common.cache_schemas import (
|
from superset.mcp_service.common.cache_schemas import (
|
||||||
CacheStatus,
|
CacheStatus,
|
||||||
@@ -678,6 +679,15 @@ class XYChartConfig(BaseModel):
|
|||||||
kind: Literal["line", "bar", "area", "scatter"] = Field(
|
kind: Literal["line", "bar", "area", "scatter"] = Field(
|
||||||
"line", description="Chart visualization type"
|
"line", description="Chart visualization type"
|
||||||
)
|
)
|
||||||
|
time_grain: TimeGrain | None = Field(
|
||||||
|
None,
|
||||||
|
description=(
|
||||||
|
"Time granularity for the x-axis when it's a temporal column. "
|
||||||
|
"Common values: PT1S (second), PT1M (minute), PT1H (hour), "
|
||||||
|
"P1D (day), P1W (week), P1M (month), P3M (quarter), P1Y (year). "
|
||||||
|
"If not specified, Superset will use its default behavior."
|
||||||
|
),
|
||||||
|
)
|
||||||
group_by: ColumnRef | None = Field(None, description="Column to group by")
|
group_by: ColumnRef | None = Field(None, description="Column to group by")
|
||||||
x_axis: AxisConfig | None = Field(None, description="X-axis configuration")
|
x_axis: AxisConfig | None = Field(None, description="X-axis configuration")
|
||||||
y_axis: AxisConfig | None = Field(None, description="Y-axis configuration")
|
y_axis: AxisConfig | None = Field(None, description="Y-axis configuration")
|
||||||
|
|||||||
@@ -275,6 +275,82 @@ class TestMapXYConfig:
|
|||||||
assert result["show_legend"] is False
|
assert result["show_legend"] is False
|
||||||
assert result["legend_orientation"] == "top"
|
assert result["legend_orientation"] == "top"
|
||||||
|
|
||||||
|
def test_map_xy_config_with_time_grain_month(self) -> None:
|
||||||
|
"""Test XY config mapping with monthly time grain"""
|
||||||
|
config = XYChartConfig(
|
||||||
|
chart_type="xy",
|
||||||
|
x=ColumnRef(name="order_date"),
|
||||||
|
y=[ColumnRef(name="revenue", aggregate="SUM")],
|
||||||
|
kind="bar",
|
||||||
|
time_grain="P1M",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = map_xy_config(config)
|
||||||
|
|
||||||
|
assert result["viz_type"] == "echarts_timeseries_bar"
|
||||||
|
assert result["x_axis"] == "order_date"
|
||||||
|
assert result["time_grain_sqla"] == "P1M"
|
||||||
|
|
||||||
|
def test_map_xy_config_with_time_grain_day(self) -> None:
|
||||||
|
"""Test XY config mapping with daily time grain"""
|
||||||
|
config = XYChartConfig(
|
||||||
|
chart_type="xy",
|
||||||
|
x=ColumnRef(name="created_at"),
|
||||||
|
y=[ColumnRef(name="count", aggregate="COUNT")],
|
||||||
|
kind="line",
|
||||||
|
time_grain="P1D",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = map_xy_config(config)
|
||||||
|
|
||||||
|
assert result["viz_type"] == "echarts_timeseries_line"
|
||||||
|
assert result["x_axis"] == "created_at"
|
||||||
|
assert result["time_grain_sqla"] == "P1D"
|
||||||
|
|
||||||
|
def test_map_xy_config_with_time_grain_hour(self) -> None:
|
||||||
|
"""Test XY config mapping with hourly time grain"""
|
||||||
|
config = XYChartConfig(
|
||||||
|
chart_type="xy",
|
||||||
|
x=ColumnRef(name="timestamp"),
|
||||||
|
y=[ColumnRef(name="requests", aggregate="SUM")],
|
||||||
|
kind="area",
|
||||||
|
time_grain="PT1H",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = map_xy_config(config)
|
||||||
|
|
||||||
|
assert result["time_grain_sqla"] == "PT1H"
|
||||||
|
|
||||||
|
def test_map_xy_config_without_time_grain(self) -> None:
|
||||||
|
"""Test XY config mapping without time grain (should not set time_grain_sqla)"""
|
||||||
|
config = XYChartConfig(
|
||||||
|
chart_type="xy",
|
||||||
|
x=ColumnRef(name="date"),
|
||||||
|
y=[ColumnRef(name="revenue")],
|
||||||
|
kind="line",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = map_xy_config(config)
|
||||||
|
|
||||||
|
assert "time_grain_sqla" not in result
|
||||||
|
|
||||||
|
def test_map_xy_config_with_time_grain_and_groupby(self) -> None:
|
||||||
|
"""Test XY config mapping with time grain and group by"""
|
||||||
|
config = XYChartConfig(
|
||||||
|
chart_type="xy",
|
||||||
|
x=ColumnRef(name="order_date"),
|
||||||
|
y=[ColumnRef(name="revenue", aggregate="SUM")],
|
||||||
|
kind="line",
|
||||||
|
time_grain="P1W",
|
||||||
|
group_by=ColumnRef(name="category"),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = map_xy_config(config)
|
||||||
|
|
||||||
|
assert result["time_grain_sqla"] == "P1W"
|
||||||
|
assert result["groupby"] == ["category"]
|
||||||
|
assert result["x_axis"] == "order_date"
|
||||||
|
|
||||||
|
|
||||||
class TestMapConfigToFormData:
|
class TestMapConfigToFormData:
|
||||||
"""Test map_config_to_form_data function"""
|
"""Test map_config_to_form_data function"""
|
||||||
|
|||||||
Reference in New Issue
Block a user