fix: add TrendLine to kpi and MaxValue to gauge queryState roles

Confirmed from Sales_Report.Report Desktop export: kpi visuals have a
third queryState key TrendLine (date/axis column for the sparkline), and
gauge visuals have a second queryState key MaxValue (a measure role).

- kpi.json template: add TrendLine projection slot
- gauge.json template: add MaxValue projection slot
- VISUAL_DATA_ROLES: updated kpi and gauge role lists
- ROLE_ALIASES: added trend_line/trend aliases for kpi, max/max_value/target for gauge
- MEASURE_ROLES: added MaxValue (TrendLine binds to Column, not Measure)
- tests: 6 new tests covering template structure and alias resolution
This commit is contained in:
MinaSaad1 2026-04-01 21:52:50 +02:00
parent 628a5fb758
commit 9f2a7b044e
4 changed files with 75 additions and 5 deletions

View file

@ -53,8 +53,8 @@ VISUAL_DATA_ROLES: dict[str, list[str]] = {
"tableEx": ["Values"],
"pivotTable": ["Rows", "Values", "Columns"],
"slicer": ["Values"],
"kpi": ["Indicator", "Goal"],
"gauge": ["Y"],
"kpi": ["Indicator", "Goal", "TrendLine"],
"gauge": ["Y", "MaxValue"],
"donutChart": ["Category", "Y", "Legend"],
# v3.1.0 additions
"columnChart": ["Category", "Y", "Legend"],
@ -92,6 +92,8 @@ MEASURE_ROLES: frozenset[str] = frozenset({
"ColumnY", "LineY", "X", "Size",
# v3.4.0 additions
"Data",
# v3.8.0 additions
"MaxValue",
})
# User-friendly role aliases to PBIR role names
@ -103,8 +105,19 @@ ROLE_ALIASES: dict[str, dict[str, str]] = {
"tableEx": {"value": "Values", "column": "Values"},
"pivotTable": {"row": "Rows", "value": "Values", "column": "Columns"},
"slicer": {"value": "Values", "field": "Values"},
"kpi": {"indicator": "Indicator", "value": "Indicator", "goal": "Goal"},
"gauge": {"value": "Y"},
"kpi": {
"indicator": "Indicator",
"value": "Indicator",
"goal": "Goal",
"trend_line": "TrendLine",
"trend": "TrendLine",
},
"gauge": {
"value": "Y",
"max": "MaxValue",
"max_value": "MaxValue",
"target": "MaxValue",
},
"donutChart": {"category": "Category", "value": "Y", "legend": "Legend"},
# v3.1.0 additions
"columnChart": {"category": "Category", "value": "Y", "legend": "Legend"},

View file

@ -15,6 +15,9 @@
"queryState": {
"Y": {
"projections": []
},
"MaxValue": {
"projections": []
}
}
},

View file

@ -18,6 +18,9 @@
},
"Goal": {
"projections": []
},
"TrendLine": {
"projections": []
}
}
},

View file

@ -959,4 +959,55 @@ def test_multi_row_card_template_uses_values_role(report_with_page: Path) -> Non
qs = data["visual"]["query"]["queryState"]
assert "Values" in qs
assert isinstance(qs["Values"], dict)
assert "Fields" not in qs
# ---------------------------------------------------------------------------
# v3.8.0 -- kpi TrendLine + gauge MaxValue role fixes
# ---------------------------------------------------------------------------
def test_kpi_template_has_trend_line_role(report_with_page: Path) -> None:
"""kpi template must include TrendLine queryState key (confirmed from Desktop)."""
r = visual_add(report_with_page, "test_page", "kpi", x=0, y=0)
vfile = (
report_with_page / "pages" / "test_page" / "visuals" / r["name"] / "visual.json"
)
data = json.loads(vfile.read_text())
qs = data["visual"]["query"]["queryState"]
assert "TrendLine" in qs
assert isinstance(qs["TrendLine"], dict)
assert "Indicator" in qs
assert "Goal" in qs
def test_gauge_template_has_max_value_role(report_with_page: Path) -> None:
"""gauge template must include MaxValue queryState key (confirmed from Desktop)."""
r = visual_add(report_with_page, "test_page", "gauge", x=0, y=0)
vfile = (
report_with_page / "pages" / "test_page" / "visuals" / r["name"] / "visual.json"
)
data = json.loads(vfile.read_text())
qs = data["visual"]["query"]["queryState"]
assert "MaxValue" in qs
assert isinstance(qs["MaxValue"], dict)
assert "Y" in qs
@pytest.mark.parametrize("alias,expected_role", [
("trend_line", "TrendLine"),
("trend", "TrendLine"),
("goal", "Goal"),
])
def test_kpi_role_aliases(alias: str, expected_role: str) -> None:
from pbi_cli.core.visual_backend import ROLE_ALIASES
assert ROLE_ALIASES["kpi"][alias] == expected_role
@pytest.mark.parametrize("alias,expected_role", [
("max", "MaxValue"),
("max_value", "MaxValue"),
("target", "MaxValue"),
])
def test_gauge_role_aliases(alias: str, expected_role: str) -> None:
from pbi_cli.core.visual_backend import ROLE_ALIASES
assert ROLE_ALIASES["gauge"][alias] == expected_role