fix(superset): заменена нечитаемая гео-карта

- Зачем:
  - гео-блок дашборда должен показывать понятную метрику, единицы и сравнение стран.
- Что:
  - legacy world_map заменён на столбцы Top Countries by Events с tooltip и легендой.
  - синхронизирован экспорт дашборда и добавлены контрактные тесты.
  - обновлены документы и урок Superset по новому гео-блоку.
- Проверка:
  - uv run --with pytest pytest tests/test_superset_dashboard_config.py.
  - uv run python -m py_compile superset/create_dashboard.py tests/test_superset_dashboard_config.py.
  - jq empty superset/dashboards/ecommerce_analytics.zip.json.
This commit is contained in:
2026-07-04 22:05:56 +03:00
parent 0f435d70fd
commit 0c80e2438e
5 changed files with 389 additions and 78 deletions
+60 -13
View File
@@ -147,18 +147,40 @@ CHARTS_CONFIG = [
},
# География
{
"slice_name": "🌍 Geography Map",
"viz_type": "world_map",
"slice_name": "🌍 Top Countries by Events",
"previous_slice_names": ["🌍 Geography Map"],
# Legacy world_map показывает разреженную географию плохо: нет явной
# легенды, подписи единиц и стабильного tooltip. Для текущего сида
# читаемее top-N стран столбцами: сразу видны страна, значение и порядок.
# Перекос стран — свойство geo-фактуры из сида, а не настройка чарта.
"viz_type": "echarts_timeseries_bar",
"dataset_name": "v_events_enriched",
"params": {
"entity": "geo_country",
"metric": {
"expressionType": "SQL",
"sqlExpression": "COUNT(*)",
"label": "Events"
},
"row_limit": 500,
"linear_color_scheme": "blue_white_yellow",
"x_axis": "geo_country",
"metrics": [
{
"expressionType": "SQL",
"sqlExpression": "COUNT(*)",
"label": "Events, pcs",
}
],
"row_limit": 15,
"order_desc": True,
"sort_series_type": "sum",
"orientation": "vertical",
"color_scheme": "supersetColors",
"show_legend": True,
"legendOrientation": "top",
"legendType": "scroll",
"rich_tooltip": True,
"tooltipTimeFormat": "smart_date",
"x_axis_title": "Country",
"x_axis_title_margin": 15,
"truncateXAxis": True,
"y_axis_title": "Events, pcs",
"y_axis_title_margin": 15,
"y_axis_title_position": "Left",
"y_axis_format": ",d",
"time_range": "No filter"
}
},
@@ -290,7 +312,7 @@ DASHBOARD_ROWS = [
# Динамика во времени + разрез по устройствам
[("📅 Events over Time", 8), ("📱 Traffic by Device", 4)],
# География + эффективность маркетинговых каналов
[("🌍 Geography Map", 6), ("🔗 UTM Effectiveness Table", 6)],
[("🌍 Top Countries by Events", 6), ("🔗 UTM Effectiveness Table", 6)],
# Популярные страницы + прохождение строк по слоям
[("🪜 Page Funnel", 6), ("🧱 Rows by Layer (event)", 6)],
]
@@ -360,6 +382,19 @@ def sync_query_context(chart, params: dict, dataset_id: int) -> None:
chart.query_context = json.dumps(query_context)
def choose_chart_to_sync(existing_charts: list, current_name: str):
"""Выбирает один chart для синхронизации и отдаёт лишние дубли на удаление."""
if not existing_charts:
return None, []
selected = next(
(chart for chart in existing_charts if chart.slice_name == current_name),
existing_charts[0],
)
duplicates = [chart for chart in existing_charts if chart is not selected]
return selected, duplicates
def build_dashboard_metadata(filter_dataset_id: int | None) -> str:
"""Формирует json_metadata с валидными datasetId для native filters."""
native_filters = []
@@ -467,11 +502,23 @@ def main() -> bool:
# идемпотентного rename без дублей в списке Charts.
chart_names = [chart_config["slice_name"]]
chart_names.extend(chart_config.get("previous_slice_names", []))
existing = db.session.query(Slice).filter(
existing_charts = db.session.query(Slice).filter(
Slice.slice_name.in_(chart_names)
).order_by(Slice.id.asc()).first()
).order_by(Slice.id.asc()).all()
existing, duplicate_charts = choose_chart_to_sync(
existing_charts,
chart_config["slice_name"],
)
if existing:
for duplicate in duplicate_charts:
db.session.delete(duplicate)
logger.info(
"Deleted duplicate chart after rename: %s (ID: %s)",
duplicate.slice_name,
duplicate.id,
)
# Синхронизируем параметры существующего чарта с конфигом.
existing.slice_name = chart_config["slice_name"]
existing.viz_type = chart_config["viz_type"]