- Зачем: - зелёный результат проверок должен означать фактический стык и точный контракт дашборда. - Что: - проверка startup-history читает manifest, требует live seam и непустой ODS. - добавлен быстрый runtime gate для daily-wave и live-продолжения. - Superset sync ограничен целевым dashboard и сверяет существенные params. - Проверка: - docker target tests; make generator-test; make generated-history-runtime-check.
218 lines
7.2 KiB
Python
218 lines
7.2 KiB
Python
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
|
|
|
|
def load_dashboard_module():
|
|
module_path = Path(__file__).resolve().parents[1] / "superset" / "create_dashboard.py"
|
|
spec = importlib.util.spec_from_file_location("create_dashboard", module_path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def chart_config(module, slice_name):
|
|
return next(chart for chart in module.CHARTS_CONFIG if chart["slice_name"] == slice_name)
|
|
|
|
|
|
def exported_chart(exported_dashboard, slice_name):
|
|
return next(
|
|
chart["__Slice__"]
|
|
for chart in exported_dashboard["charts"]
|
|
if chart["__Slice__"]["slice_name"] == slice_name
|
|
)
|
|
|
|
|
|
def load_exported_dashboard():
|
|
export_path = Path(__file__).resolve().parents[1] / "superset" / "dashboards" / "ecommerce_analytics.zip.json"
|
|
return json.loads(export_path.read_text())
|
|
|
|
|
|
def expected_export_params(chart, actual_params):
|
|
return {
|
|
**chart["params"],
|
|
"datasource": actual_params["datasource"],
|
|
"viz_type": chart["viz_type"],
|
|
}
|
|
|
|
|
|
class FakeDashboard:
|
|
def __init__(self, slug):
|
|
self.slug = slug
|
|
|
|
|
|
class FakeChart:
|
|
def __init__(self, chart_id, slice_name, datasource_id=1, dashboard_slugs=None):
|
|
self.id = chart_id
|
|
self.slice_name = slice_name
|
|
self.datasource_id = datasource_id
|
|
self.dashboards = [
|
|
FakeDashboard(slug)
|
|
for slug in (dashboard_slugs or ["ecommerce-analytics"])
|
|
]
|
|
|
|
|
|
def test_geo_chart_uses_readable_top_countries_bar_config():
|
|
module = load_dashboard_module()
|
|
|
|
geo_chart = chart_config(module, "🌍 Top Countries by Events")
|
|
|
|
assert geo_chart["viz_type"] == "echarts_timeseries_bar"
|
|
assert geo_chart["previous_slice_names"] == ["🌍 Geography Map"]
|
|
|
|
params = geo_chart["params"]
|
|
assert params["x_axis"] == "geo_country"
|
|
assert params["metrics"] == [
|
|
{
|
|
"expressionType": "SQL",
|
|
"sqlExpression": "COUNT(*)",
|
|
"label": "Events, pcs",
|
|
}
|
|
]
|
|
assert params["row_limit"] == 15
|
|
assert params["order_desc"] is True
|
|
assert params["sort_series_type"] == "sum"
|
|
assert params["show_legend"] is True
|
|
assert params["rich_tooltip"] is True
|
|
assert params["y_axis_title"] == "Events, pcs"
|
|
assert params["x_axis_title"] == "Country"
|
|
assert params["y_axis_format"] == ",d"
|
|
|
|
|
|
def test_exported_dashboard_uses_same_readable_geo_chart():
|
|
module = load_dashboard_module()
|
|
exported_dashboard = load_exported_dashboard()
|
|
|
|
expected_chart = chart_config(module, "🌍 Top Countries by Events")
|
|
geo_chart = exported_chart(exported_dashboard, "🌍 Top Countries by Events")
|
|
|
|
assert geo_chart["viz_type"] == "echarts_timeseries_bar"
|
|
params = json.loads(geo_chart["params"])
|
|
assert params == expected_export_params(expected_chart, params)
|
|
assert params["viz_type"] == "echarts_timeseries_bar"
|
|
assert params["x_axis"] == "geo_country"
|
|
assert params["metrics"][0]["label"] == "Events, pcs"
|
|
assert params["x_axis_sort"] == "Events, pcs"
|
|
assert params["x_axis_sort_asc"] is False
|
|
assert params["show_legend"] is True
|
|
assert params["rich_tooltip"] is True
|
|
assert params["y_axis_title"] == "Events, pcs"
|
|
|
|
position_json = json.loads(exported_dashboard["dashboards"][0]["__Dashboard__"]["position_json"])
|
|
assert position_json["CHART-7"]["meta"]["sliceName"] == "🌍 Top Countries by Events"
|
|
|
|
|
|
def test_exported_dashboard_chart_params_match_current_config():
|
|
module = load_dashboard_module()
|
|
exported_dashboard = load_exported_dashboard()
|
|
|
|
expected_charts = {
|
|
chart["slice_name"]: chart
|
|
for chart in module.CHARTS_CONFIG
|
|
}
|
|
|
|
for exported in exported_dashboard["charts"]:
|
|
actual_chart = exported["__Slice__"]
|
|
actual_params = json.loads(actual_chart["params"])
|
|
expected_chart = expected_charts[actual_chart["slice_name"]]
|
|
|
|
assert actual_chart["viz_type"] == expected_chart["viz_type"]
|
|
assert actual_params == expected_export_params(expected_chart, actual_params)
|
|
|
|
|
|
def test_exported_dashboard_chart_names_match_current_config():
|
|
module = load_dashboard_module()
|
|
exported_dashboard = load_exported_dashboard()
|
|
|
|
expected_names = [chart["slice_name"] for chart in module.CHARTS_CONFIG]
|
|
actual_names = [chart["__Slice__"]["slice_name"] for chart in exported_dashboard["charts"]]
|
|
|
|
assert actual_names == expected_names
|
|
|
|
|
|
def test_exported_dashboard_layout_matches_current_rows():
|
|
module = load_dashboard_module()
|
|
exported_dashboard = load_exported_dashboard()
|
|
|
|
position_json = json.loads(exported_dashboard["dashboards"][0]["__Dashboard__"]["position_json"])
|
|
chart_titles_by_id = {
|
|
component_id: component["meta"]["sliceName"]
|
|
for component_id, component in position_json.items()
|
|
if isinstance(component, dict) and component.get("type") == "CHART"
|
|
}
|
|
|
|
actual_rows = []
|
|
for row_id in position_json["GRID_ID"]["children"]:
|
|
row = []
|
|
for chart_component_id in position_json[row_id]["children"]:
|
|
chart_component = position_json[chart_component_id]
|
|
row.append(
|
|
(
|
|
chart_titles_by_id[chart_component_id],
|
|
chart_component["meta"]["width"],
|
|
)
|
|
)
|
|
actual_rows.append(row)
|
|
|
|
assert actual_rows == module.DASHBOARD_ROWS
|
|
|
|
|
|
def test_choose_chart_to_sync_prefers_current_name_and_marks_old_name_duplicate():
|
|
module = load_dashboard_module()
|
|
old_chart = FakeChart(7, "🌍 Geography Map", datasource_id=42)
|
|
current_chart = FakeChart(11, "🌍 Top Countries by Events", datasource_id=42)
|
|
|
|
selected, duplicates = module.choose_chart_to_sync(
|
|
[old_chart, current_chart],
|
|
"🌍 Top Countries by Events",
|
|
dataset_id=42,
|
|
)
|
|
|
|
assert selected is current_chart
|
|
assert duplicates == [old_chart]
|
|
|
|
|
|
def test_choose_chart_to_sync_ignores_same_name_from_other_dataset():
|
|
module = load_dashboard_module()
|
|
other_dataset_chart = FakeChart(7, "🌍 Top Countries by Events", datasource_id=7)
|
|
target_dataset_chart = FakeChart(11, "🌍 Geography Map", datasource_id=42)
|
|
|
|
selected, duplicates = module.choose_chart_to_sync(
|
|
[other_dataset_chart, target_dataset_chart],
|
|
"🌍 Top Countries by Events",
|
|
dataset_id=42,
|
|
)
|
|
|
|
assert selected is target_dataset_chart
|
|
assert duplicates == []
|
|
|
|
|
|
def test_dashboard_sync_ignores_same_name_same_dataset_from_other_dashboard():
|
|
module = load_dashboard_module()
|
|
other_dashboard_chart = FakeChart(
|
|
7,
|
|
"🌍 Top Countries by Events",
|
|
datasource_id=42,
|
|
dashboard_slugs=["mentee-dashboard"],
|
|
)
|
|
target_dashboard_chart = FakeChart(
|
|
11,
|
|
"🌍 Geography Map",
|
|
datasource_id=42,
|
|
dashboard_slugs=["ecommerce-analytics"],
|
|
)
|
|
|
|
owned_charts = module.dashboard_owned_charts(
|
|
[other_dashboard_chart, target_dashboard_chart],
|
|
dashboard_slug="ecommerce-analytics",
|
|
)
|
|
selected, duplicates = module.choose_chart_to_sync(
|
|
owned_charts,
|
|
"🌍 Top Countries by Events",
|
|
dataset_id=42,
|
|
)
|
|
|
|
assert owned_charts == [target_dashboard_chart]
|
|
assert selected is target_dashboard_chart
|
|
assert duplicates == []
|