mirror of
https://github.com/apache/superset.git
synced 2026-09-02 21:41:31 +00:00
chrome.title.text (three levels deep: EchartsControls -> chrome -> title -> text) rendered as a bare "Chrome" header with nothing inside it — verified live and reproduced with a probe schema. JsonForms' generated control panel only expands one level of nested-object properties (the same depth DataBinding/Customization already rely on); a second level of nesting produces an empty group. Flattens EchartsChrome to direct scalar fields (titleText, legendShow, legendPosition, tooltipTrigger, xAxisName/Rotate/Format, yAxisName/Rotate/ Format) at the same one-level depth as the working fields, and updates the frontend merge module, widgetLabel's title precedence, and all tests to match. Adds a regression test asserting the flat shape actually renders its fields in the panel, not just that the schema declares them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
167 lines
6.0 KiB
Python
167 lines
6.0 KiB
Python
# 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.
|
|
"""Tests for the `echarts` widget's `chrome` structured layer — title,
|
|
legend, tooltip, and axis labels, each independently optional and applying
|
|
regardless of `chartType` (see `EchartsChrome` in `superset/widgets/controls.py`).
|
|
`EchartsChrome`'s fields are deliberately flat (`titleText`, `legendShow`,
|
|
`xAxisName`, ...) rather than nested into `title`/`legend`/`xAxis` sub-objects
|
|
— JsonForms only renders one level of nested-object properties, so a
|
|
two-level-deep `chrome.title.text` would render as an empty group with
|
|
nothing inside it (verified against the actual control panel). The actual
|
|
merge onto `echartsOptions` happens on the frontend
|
|
(`echartsStructuredChrome.ts`); this covers the backend's half — the schema
|
|
shape served, and strict validation of the control values it accepts."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from superset.utils import json
|
|
from superset.widgets.registry import registry
|
|
|
|
|
|
def _echarts_schema(control_values=None):
|
|
widget = registry.get("echarts")
|
|
assert widget is not None
|
|
return widget.get_control_schema(control_values, None)
|
|
|
|
|
|
def test_chrome_schema_is_flat_one_level_under_chrome() -> None:
|
|
schema = _echarts_schema()
|
|
chrome_ref = schema["properties"]["chrome"]
|
|
assert chrome_ref["$ref"] == "#/$defs/EchartsChrome"
|
|
chrome_def = schema["$defs"]["EchartsChrome"]
|
|
assert set(chrome_def["properties"]) == {
|
|
"titleText",
|
|
"legendShow",
|
|
"legendPosition",
|
|
"tooltipTrigger",
|
|
"xAxisName",
|
|
"xAxisRotate",
|
|
"xAxisFormat",
|
|
"yAxisName",
|
|
"yAxisRotate",
|
|
"yAxisFormat",
|
|
}
|
|
# Flat, not nested: every field's own schema is a plain scalar (string,
|
|
# boolean, integer, or a nullable enum) — never another $ref to a
|
|
# sub-object.
|
|
for name, prop in chrome_def["properties"].items():
|
|
assert "$ref" not in prop, f"{name} must not be a nested object"
|
|
|
|
|
|
def test_legend_position_and_tooltip_trigger_offer_their_option_lists() -> None:
|
|
schema = _echarts_schema()
|
|
chrome_def = schema["$defs"]["EchartsChrome"]
|
|
assert chrome_def["properties"]["legendPosition"]["x-options"] == [
|
|
"top",
|
|
"bottom",
|
|
"left",
|
|
"right",
|
|
]
|
|
assert chrome_def["properties"]["tooltipTrigger"]["x-options"] == [
|
|
"item",
|
|
"axis",
|
|
]
|
|
|
|
|
|
def test_axis_rotate_is_bounded() -> None:
|
|
schema = _echarts_schema()
|
|
chrome_def = schema["$defs"]["EchartsChrome"]
|
|
assert chrome_def["properties"]["xAxisRotate"]["minimum"] == -90
|
|
assert chrome_def["properties"]["xAxisRotate"]["maximum"] == 90
|
|
assert chrome_def["properties"]["yAxisRotate"]["minimum"] == -90
|
|
assert chrome_def["properties"]["yAxisRotate"]["maximum"] == 90
|
|
|
|
|
|
def test_valid_chrome_control_values_pass_strict_validation() -> None:
|
|
widget = registry.get("echarts")
|
|
assert widget is not None
|
|
errors = widget.validate_control_values(
|
|
{
|
|
"dataBinding": {"datasetId": 1, "metrics": ["count"]},
|
|
"chrome": {
|
|
"titleText": "Sales",
|
|
"legendShow": False,
|
|
"legendPosition": "right",
|
|
"tooltipTrigger": "axis",
|
|
"xAxisName": "Product",
|
|
"xAxisRotate": 45,
|
|
"xAxisFormat": "{value} kg",
|
|
"yAxisName": "Sales",
|
|
},
|
|
}
|
|
)
|
|
assert errors == []
|
|
|
|
|
|
def test_out_of_range_rotate_is_rejected() -> None:
|
|
widget = registry.get("echarts")
|
|
assert widget is not None
|
|
errors = widget.validate_control_values(
|
|
{
|
|
"dataBinding": {"datasetId": 1, "metrics": ["count"]},
|
|
"chrome": {"xAxisRotate": 200},
|
|
}
|
|
)
|
|
locs = [tuple(error["loc"]) for error in errors]
|
|
assert ("chrome", "xAxisRotate") in locs
|
|
|
|
|
|
def test_invalid_legend_position_is_rejected() -> None:
|
|
widget = registry.get("echarts")
|
|
assert widget is not None
|
|
errors = widget.validate_control_values(
|
|
{
|
|
"dataBinding": {"datasetId": 1, "metrics": ["count"]},
|
|
"chrome": {"legendPosition": "center"},
|
|
}
|
|
)
|
|
assert errors != []
|
|
|
|
|
|
def test_a_widget_without_any_chrome_still_validates_cleanly() -> None:
|
|
# Pre-existing raw-only widgets carry no `chrome` at all.
|
|
widget = registry.get("echarts")
|
|
assert widget is not None
|
|
errors = widget.validate_control_values(
|
|
{"dataBinding": {"datasetId": 1, "metrics": ["count"]}}
|
|
)
|
|
assert errors == []
|
|
|
|
|
|
def test_control_schema_rest_endpoint_exposes_chrome(client, full_api_access) -> None:
|
|
resp = client.get("/api/v1/widgets/type/echarts/control-schema")
|
|
assert resp.status_code == 200
|
|
schema = resp.get_json()["result"]
|
|
assert "chrome" in schema["properties"]
|
|
|
|
|
|
def test_validate_rest_endpoint_accepts_chrome_fields(client, full_api_access) -> None:
|
|
resp = client.post(
|
|
"/api/v1/widgets/type/echarts/validate",
|
|
data=json.dumps(
|
|
{
|
|
"control_values": {
|
|
"dataBinding": {"datasetId": 1, "metrics": ["count"]},
|
|
"chrome": {"titleText": "Sales", "legendShow": False},
|
|
}
|
|
}
|
|
),
|
|
content_type="application/json",
|
|
)
|
|
assert resp.status_code == 200
|
|
assert resp.get_json()["result"]["errors"] == []
|