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:
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user