test(generator): усилены проверки startup-history и Superset

- Зачем:
  - зелёный результат проверок должен означать фактический стык и точный контракт дашборда.
- Что:
  - проверка 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.
This commit is contained in:
2026-07-05 21:32:51 +03:00
parent 9804d0beae
commit 00c97eb063
11 changed files with 500 additions and 23 deletions
+43 -6
View File
@@ -167,6 +167,8 @@ CHARTS_CONFIG = [
"row_limit": 15,
"order_desc": True,
"sort_series_type": "sum",
"x_axis_sort": "Events, pcs",
"x_axis_sort_asc": False,
"orientation": "vertical",
"color_scheme": "supersetColors",
"show_legend": True,
@@ -382,8 +384,14 @@ 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):
def choose_chart_to_sync(existing_charts: list, current_name: str, dataset_id: int | None = None):
"""Выбирает один chart для синхронизации и отдаёт лишние дубли на удаление."""
if dataset_id is not None:
existing_charts = [
chart
for chart in existing_charts
if getattr(chart, "datasource_id", None) == dataset_id
]
if not existing_charts:
return None, []
@@ -395,6 +403,16 @@ def choose_chart_to_sync(existing_charts: list, current_name: str):
return selected, duplicates
def dashboard_owned_charts(existing_charts: list, dashboard_slug: str) -> list:
"""Оставляет только charts, уже привязанные к целевому dashboard."""
owned = []
for chart in existing_charts:
dashboards = getattr(chart, "dashboards", []) or []
if any(getattr(dashboard, "slug", None) == dashboard_slug for dashboard in dashboards):
owned.append(chart)
return owned
def build_dashboard_metadata(filter_dataset_id: int | None) -> str:
"""Формирует json_metadata с валидными datasetId для native filters."""
native_filters = []
@@ -470,6 +488,9 @@ def main() -> bool:
created_charts = []
datasets_by_name = {}
existing_dashboard = db.session.query(Dashboard).filter_by(
slug=DASHBOARD_CONFIG["slug"]
).first()
# Создаём чарты
for chart_config in CHARTS_CONFIG:
@@ -503,11 +524,18 @@ def main() -> bool:
chart_names = [chart_config["slice_name"]]
chart_names.extend(chart_config.get("previous_slice_names", []))
existing_charts = db.session.query(Slice).filter(
Slice.slice_name.in_(chart_names)
Slice.slice_name.in_(chart_names),
Slice.datasource_id == dataset.id,
).order_by(Slice.id.asc()).all()
if existing_dashboard:
existing_charts = dashboard_owned_charts(
existing_charts,
DASHBOARD_CONFIG["slug"],
)
existing, duplicate_charts = choose_chart_to_sync(
existing_charts,
chart_config["slice_name"],
dataset_id=dataset.id,
)
if existing:
@@ -562,8 +590,19 @@ def main() -> bool:
logger.info(f"Created/Found {len(created_charts)} charts")
current_chart_names = {chart_config["slice_name"] for chart_config in CHARTS_CONFIG}
target_dataset_ids = set(datasets_by_name.values())
for obsolete_name in sorted(OBSOLETE_CHART_NAMES - current_chart_names):
obsolete_charts = db.session.query(Slice).filter_by(slice_name=obsolete_name).all()
if not existing_dashboard:
obsolete_charts = []
else:
obsolete_charts = db.session.query(Slice).filter(
Slice.slice_name == obsolete_name,
Slice.datasource_id.in_(target_dataset_ids),
).all()
obsolete_charts = dashboard_owned_charts(
obsolete_charts,
DASHBOARD_CONFIG["slug"],
)
for obsolete in obsolete_charts:
db.session.delete(obsolete)
logger.info("Deleted obsolete chart: %s (ID: %s)", obsolete_name, obsolete.id)
@@ -651,9 +690,7 @@ def main() -> bool:
if created_charts:
try:
# Проверяем, существует ли дашборд
existing = db.session.query(Dashboard).filter_by(
slug=DASHBOARD_CONFIG["slug"]
).first()
existing = existing_dashboard
if existing:
existing.description = DASHBOARD_CONFIG["description"]
@@ -90,7 +90,7 @@
"viz_type": "echarts_timeseries_bar",
"datasource_type": "table",
"datasource_name": "dm.v_events_enriched",
"params": "{\"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\", \"datasource\": \"1__table\", \"viz_type\": \"echarts_timeseries_bar\"}",
"params": "{\"x_axis\": \"geo_country\", \"metrics\": [{\"expressionType\": \"SQL\", \"sqlExpression\": \"COUNT(*)\", \"label\": \"Events, pcs\"}], \"row_limit\": 15, \"order_desc\": true, \"sort_series_type\": \"sum\", \"x_axis_sort\": \"Events, pcs\", \"x_axis_sort_asc\": false, \"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\", \"datasource\": \"1__table\", \"viz_type\": \"echarts_timeseries_bar\"}",
"description": "Chart created automatically for v_events_enriched"
}
},