feat(superset): обновлен состав KPI и воронки дашборда

- Зачем:
  - нужно убрать дублирующий KPI Unique Sessions и сделать эталонный dashboard честнее для учебного анализа.
- Что:
  - обновлены KPI, добавлена Conversion to /confirmation и Page Funnel.
  - добавлена идемпотентная миграция старых chart names без дублей.
  - синхронизированы документация, урок 6 и спека редизайна.
  - добавлен handoff для продолжения работы в новой сессии.
- Проверка:
  - python3 -m py_compile superset/create_dashboard.py.
  - make superset-dashboard.
  - Superset metadata: dashboard_charts=10, obsolete_unique_sessions=0, page_funnel_type=funnel.
This commit is contained in:
2026-06-06 17:00:02 +03:00
parent 0fd8a08667
commit 1bec6bb8ad
5 changed files with 237 additions and 68 deletions
@@ -0,0 +1,96 @@
# Handoff: Superset dashboard redesign implemented
Дата: 2026-06-06 · Язык сессии: русский
## Статус
Редизайн Superset-дашборда реализован и закоммичен.
Коммит:
```text
04995af832743ed984c25af08a185f7abe0485f1
feat(superset): обновлен состав KPI и воронки дашборда
```
Рабочее дерево было чистым перед созданием этого handoff; сам handoff создан после коммита.
## Источники истины
- Спека: [`docs/specs/2026-06-06-superset-dashboard-redesign.md`](../../docs/specs/2026-06-06-superset-dashboard-redesign.md)
- Реализация: [`superset/create_dashboard.py`](../../superset/create_dashboard.py)
- Пользовательская документация: [`docs/SUPERSET_DASHBOARD.md`](../../docs/SUPERSET_DASHBOARD.md)
- Урок 6: [`docs/course/lessons/06_superset_bi.md`](../../docs/course/lessons/06_superset_bi.md)
- Предыдущий handoff: [`2026-06-06-superset-dashboard-redesign.md`](./2026-06-06-superset-dashboard-redesign.md)
## Что сделано
- KPI-полоса теперь:
`Total Events · Unique Users · Avg Events/Visit · Conversion to /confirmation`.
- `Unique Sessions` удалён из KPI и из Superset metadata как obsolete chart.
- `Avg Events/Session` переименован в `Avg Events/Visit` идемпотентно через `previous_slice_names`.
- `Top Pages` переименован в `Page Funnel` и переведён на `viz_type: funnel`.
- Conversion считается как page-funnel metric:
`countIf(page_url_path = '/confirmation') / countIf(page_url_path = '/home')`.
- UTM-таблица оставлена без мёртвых колонок `purchases` / `add_to_cart`.
- `Events by Hour` оставлен: на полном датасете есть два часовых бакета.
- Документация и урок 6 синхронизированы с полным датасетом и новым dashboard flow.
## Проверки
Выполнено:
```bash
make data
make transform
python3 -m py_compile superset/create_dashboard.py
make superset-dashboard
make superset-dashboard
```
Результаты:
- полный датасет в DM: `1000` events, `99` visits, `99` users;
- `Avg Events/Visit = 10.1`;
- `Conversion to /confirmation = 35 / 426 = 8.2%`;
- Superset dashboard metadata:
- `dashboard_charts = 10`;
- `obsolete_unique_sessions = 0`;
- `page_funnel_type = funnel`;
- Superset API `/api/v1/dashboard/1/datasets` вернул `200`;
- browser chart-data для `Page Funnel` вернул `status: success`, 6 строк;
- browser chart-data для `Data Quality Summary` вернул `errors: []`, `status: success`.
## Визуальная проверка
Пользователь визуально подтвердил: «Визуально - выглядит норм».
Скриншоты были сохранены в `/tmp` во время сессии:
- `/tmp/ecommerce-analytics-dashboard-desktop.png`
- `/tmp/ecommerce-analytics-dashboard-lower.png`
Они могут исчезнуть после очистки `/tmp`; при необходимости переснять через `playwright-cli`.
## Важные детали реализации
- Для Superset 4.1.2 `funnel` проверялся через MCP Context7 по `/apache/superset`; Context7 не дал точную строку `viz_type`.
- Решение подтверждено по установленному Superset 4.1.2: bundled example `Featured Charts/Funnel.yaml` использует `viz_type: funnel`.
- `create_dashboard.py` ищет старые имена через `previous_slice_names`, чтобы не плодить дубли при rename.
- Cleanup obsolete charts удаляет все найденные `🎯 Unique Sessions`, если metadata уже была загрязнена дублями.
## Что осталось
Обязательных незакрытых задач по редизайну нет.
Возможные следующие шаги, если продолжать dashboard-направление:
- экспортировать обновлённый dashboard JSON через `make superset-export`, если экспортный артефакт должен соответствовать новой metadata;
- отдельно модернизировать legacy chart types (`pie`, `world_map`, `dist_bar`) на ECharts, если это станет целью следующего захода;
- при развитии генератора перенести требования из спеки в его `KNOWN_ISSUES.md`.
## Suggested skills
- `playwright-cli` — если нужно переснять визуальную проверку dashboard.
- `conventional-commits` — если нужно коммитить этот handoff или дальнейшие правки.
- `diagnose` — если Superset chart-data или layout начнут падать после сброса volumes.
+15 -3
View File
@@ -59,10 +59,12 @@ make superset-dashboard
#### KPI-блок (верх дашборда) #### KPI-блок (верх дашборда)
- **📊 Total Events** — общее количество событий - **📊 Total Events** — общее количество событий
- **👤 Unique Users** — уникальные пользователи - **👤 Unique Users** — уникальные пользователи
- **🎯 Unique Sessions** — уникальные сессии (click_id) - **📈 Avg Events/Visit** — среднее количество событий на визит (`click_id`)
- **📈 Avg Events/Session** — среднее количество событий на сессию - **🎯 Conversion to /confirmation** — доля просмотров `/confirmation` от просмотров `/home`
KPI разложены в одну строку по 12-колоночной сетке Superset: четыре блока по 3 колонки. KPI разложены в одну строку по 12-колоночной сетке Superset: четыре блока по 3 колонки.
`Unique Sessions` не вынесен отдельной KPI-плиткой, потому что в демо-данных
`user_domain_id` и `click_id` идут 1:1 и дают то же число, что `Unique Users`.
#### Динамика трафика #### Динамика трафика
- **📅 Events by Hour** — линейный график событий по часам - **📅 Events by Hour** — линейный график событий по часам
@@ -73,7 +75,13 @@ KPI разложены в одну строку по 12-колоночной с
#### Маркетинг #### Маркетинг
- **🔗 UTM Effectiveness Table** — таблица эффективности UTM-меток - **🔗 UTM Effectiveness Table** — таблица эффективности UTM-меток
- **📄 Top Pages** — bar chart топ-20 страниц - **🪜 Page Funnel** — funnel chart по просмотрам страниц, от `/home` к `/confirmation`
> **Что проверили по Superset 4.1.2.** Через MCP Context7 проверили официальную
> библиотеку `/apache/superset`; документация не дала точной строки `viz_type`.
> В установленном Superset 4.1.2 дополнительно проверили bundled example
> `Featured Charts/Funnel.yaml` и frontend assets: для воронки используется
> `viz_type: funnel`, поэтому dashboard создаёт именно funnel chart.
#### Качество данных #### Качество данных
- **🔍 Data Quality Summary** — статистика по слоям STG/ODS/DDS - **🔍 Data Quality Summary** — статистика по слоям STG/ODS/DDS
@@ -219,7 +227,11 @@ make superset-restart
# Полная переинициализация # Полная переинициализация
docker compose down -v docker compose down -v
docker compose up -d docker compose up -d
make ddl
make data
make transform
make superset-init make superset-init
make superset-dashboard
``` ```
### Нет данных в чартах ### Нет данных в чартах
+47 -27
View File
@@ -63,17 +63,19 @@ DM-витрина — это SQL-объект в ClickHouse. Она задаёт
## 2. Руки: запускаем Superset и смотрим дашборд ## 2. Руки: запускаем Superset и смотрим дашборд
Подними стенд и прогони маленький срез: Подними стенд и прогони полный демо-датасет:
```bash ```bash
make up make up
make ddl make ddl
LIMIT=50 make data make data
make transform make transform
``` ```
`make transform` прогоняет цепочку STG → ODS → DDS → DM вне Airflow. Для этого урока так `make transform` прогоняет цепочку STG → ODS → DDS → DM вне Airflow. Для этого урока так
быстрее: нам нужен готовый DM-слой, а не разбор DAG. быстрее: нам нужен готовый DM-слой, а не разбор DAG. Отладочный срез через
`LIMIT=50 make data` можно использовать для быстрых экспериментов, но эталонный dashboard
и числа урока рассчитаны на полном наборе данных.
Теперь инициализируй Superset: Теперь инициализируй Superset:
@@ -84,7 +86,16 @@ make superset-init
Эта команда создаёт или обновляет: Эта команда создаёт или обновляет:
- подключение `clickhouse_dwh`; - подключение `clickhouse_dwh`;
- 6 datasets поверх `dm.*`; - 6 datasets поверх `dm.*`.
После этого создай или обнови charts и сам dashboard:
```bash
make superset-dashboard
```
Эта команда создаёт или обновляет:
- 10 charts; - 10 charts;
- dashboard `E-commerce Analytics Dashboard`. - dashboard `E-commerce Analytics Dashboard`.
@@ -105,12 +116,17 @@ http://localhost:8088/superset/dashboard/1/
На экране должны быть блоки: На экране должны быть блоки:
- KPI сверху: `Total Events`, `Unique Users`, `Unique Sessions`, `Avg Events/Session`; - KPI сверху: `Total Events`, `Unique Users`, `Avg Events/Visit`,
`Conversion to /confirmation`;
- динамика: `Events by Hour`, `Traffic by Device`; - динамика: `Events by Hour`, `Traffic by Device`;
- география: `Geography Map`; - география: `Geography Map`;
- маркетинг: `UTM Effectiveness Table`, `Top Pages`; - маркетинг: `UTM Effectiveness Table`, `Page Funnel`;
- качество данных: `Data Quality Summary`. - качество данных: `Data Quality Summary`.
`Conversion to /confirmation` считается как просмотры `/confirmation` / просмотры `/home`.
Это page-funnel метрика, а не доля визитов: она совпадает с тем, как ниже устроен chart
`Page Funnel`.
### Фильтр даты ### Фильтр даты
В демо-данных события датированы `2022-11-28`. В текущей конфигурации dashboard фильтр даты В демо-данных события датированы `2022-11-28`. В текущей конфигурации dashboard фильтр даты
@@ -136,7 +152,7 @@ SELECT count() AS events
FROM dm.v_events_enriched; FROM dm.v_events_enriched;
``` ```
И посмотри, откуда берётся график `Top Pages`: И посмотри, откуда берётся график `Page Funnel`:
```sql ```sql
SELECT page_url_path, sum(pageviews) AS pageviews SELECT page_url_path, sum(pageviews) AS pageviews
@@ -233,18 +249,20 @@ Metadata Superset живёт в PostgreSQL, а сами данные остаю
```python ```python
{ {
"slice_name": "📄 Top Pages", "slice_name": "🪜 Page Funnel",
"viz_type": "dist_bar", "viz_type": "funnel",
"dataset_name": "v_top_pages_daily", "dataset_name": "v_top_pages_daily",
"params": { "params": {
"groupby": ["page_url_path"], "groupby": ["page_url_path"],
"metrics": [ "metric": {
{"expressionType": "SQL", "sqlExpression": "SUM(pageviews)", "label": "Pageviews"} "expressionType": "SQL",
], "sqlExpression": "SUM(pageviews)",
"label": "Pageviews"
},
"row_limit": 20, "row_limit": 20,
"time_range": "No filter", "time_range": "No filter",
"orientation": "vertical", "sort_by_metric": True,
"show_legend": False "percent_calculation_type": "first_step"
} }
} }
``` ```
@@ -280,14 +298,16 @@ Native filters создаются в `build_dashboard_metadata`. Там есть
> **Что проверили по API.** Перед уроком Superset сверили через MCP Context7 (`/apache/superset`): > **Что проверили по API.** Перед уроком Superset сверили через MCP Context7 (`/apache/superset`):
> в Superset есть отдельные сущности charts и dashboards, metadata хранится отдельно от > в Superset есть отдельные сущности charts и dashboards, metadata хранится отдельно от
> подключаемых источников данных, а row limit — часть настройки запросов и конфигурации. > подключаемых источников данных. Для `funnel` Context7 не дал точную строку `viz_type`, поэтому
> Поэтому в уроке не лезем в REST API Superset, а работаем через уже существующий скрипт стенда. > дополнительно проверили установленный Superset 4.1.2: bundled example
> `Featured Charts/Funnel.yaml` использует `viz_type: funnel`. Поэтому в уроке не лезем в REST API
> Superset, а работаем через уже существующий скрипт стенда.
--- ---
## 4. Управляемая правка: уменьшаем Top Pages ## 4. Управляемая правка: уменьшаем Page Funnel
Сейчас chart `Top Pages` показывает до 20 страниц: Сейчас chart `Page Funnel` показывает до 20 страниц:
```python ```python
"row_limit": 20, "row_limit": 20,
@@ -296,7 +316,7 @@ Native filters создаются в `build_dashboard_metadata`. Там есть
Сделай маленькую видимую правку: временно покажи только топ-3 страницы. Сделай маленькую видимую правку: временно покажи только топ-3 страницы.
Открой [`superset/create_dashboard.py`](../../../superset/create_dashboard.py), найди chart Открой [`superset/create_dashboard.py`](../../../superset/create_dashboard.py), найди chart
`Top Pages` и поменяй: `Page Funnel` и поменяй:
```python ```python
"row_limit": 20, "row_limit": 20,
@@ -314,7 +334,7 @@ Native filters создаются в `build_dashboard_metadata`. Там есть
make superset-dashboard make superset-dashboard
``` ```
Вернись в Superset и обнови страницу dashboard. В chart `Top Pages` должно остаться не больше Вернись в Superset и обнови страницу dashboard. В chart `Page Funnel` должно остаться не больше
трёх страниц. Если фильтр даты снова скрыл данные, поставь **Date Range → No filter** и нажми трёх страниц. Если фильтр даты снова скрыл данные, поставь **Date Range → No filter** и нажми
**Apply filters**. **Apply filters**.
@@ -339,17 +359,17 @@ make superset-dashboard
make superset-dashboard make superset-dashboard
``` ```
После обновления страницы chart `Top Pages` снова может показывать до 20 страниц. После обновления страницы chart `Page Funnel` снова может показывать до 20 страниц.
Если после экспериментов Superset выглядит странно, самый простой учебный возврат dashboard Если после экспериментов Superset выглядит странно, самый простой учебный возврат dashboard
metadata к конфигурации из репозитория: metadata к конфигурации из репозитория:
```bash ```bash
make superset-init make superset-dashboard
``` ```
Данные в ClickHouse эта команда не удаляет. Она повторно применяет подключение, datasets и Данные в ClickHouse эта команда не удаляет. Она повторно применяет charts и dashboard
dashboard metadata Superset. metadata Superset.
--- ---
@@ -362,8 +382,8 @@ dashboard metadata Superset.
| открыть **Datasets** | Superset UI | есть datasets `v_events_enriched`, `v_top_pages_daily`, `dq_summary` | | открыть **Datasets** | Superset UI | есть datasets `v_events_enriched`, `v_top_pages_daily`, `dq_summary` |
| открыть dashboard | Superset UI | видны KPI, маркетинг, география и качество данных | | открыть dashboard | Superset UI | видны KPI, маркетинг, география и качество данных |
| поставить **Date Range → No filter** | dashboard filters | графики не скрываются из-за даты `2022-11-28` | | поставить **Date Range → No filter** | dashboard filters | графики не скрываются из-за даты `2022-11-28` |
| поменять `row_limit` у `Top Pages` на `3` и запустить `make superset-dashboard` | chart `Top Pages` | не больше трёх страниц | | поменять `row_limit` у `Page Funnel` на `3` и запустить `make superset-dashboard` | chart `Page Funnel` | не больше трёх страниц |
| вернуть `row_limit` на `20` и запустить `make superset-dashboard` | chart `Top Pages` | ограничение снова до 20 страниц | | вернуть `row_limit` на `20` и запустить `make superset-dashboard` | chart `Page Funnel` | ограничение снова до 20 страниц |
Вопросы для созвона: Вопросы для созвона:
@@ -378,7 +398,7 @@ dashboard metadata Superset.
## 6. Что должно получиться ## 6. Что должно получиться
К концу урока у тебя должен быть открытый dashboard `E-commerce Analytics Dashboard` в Superset. К концу урока у тебя должен быть открытый dashboard `E-commerce Analytics Dashboard` в Superset.
Сделай скриншот после временной правки `Top Pages`: на нём должно быть видно, что chart показывает Сделай скриншот после временной правки `Page Funnel`: на нём должно быть видно, что chart показывает
не больше трёх страниц. не больше трёх страниц.
Второй артефакт — короткий абзац своими словами: Второй артефакт — короткий абзац своими словами:
@@ -1,8 +1,8 @@
# Редизайн KPI-полосы и состава чартов дашборда «E-commerce Analytics» # Редизайн KPI-полосы и состава чартов дашборда «E-commerce Analytics»
Дата: 2026-06-06 Дата: 2026-06-06
Статус: Accepted (не реализовано — код `superset/create_dashboard.py` правится Статус: Implemented (`superset/create_dashboard.py`, `docs/SUPERSET_DASHBOARD.md`,
отдельным заходом на реализацию) `docs/course/lessons/06_superset_bi.md`)
Связано: [`CONTEXT.md`](../../CONTEXT.md), [ADR-0002](../adr/0002-specs-as-durable-design-docs.md), Связано: [`CONTEXT.md`](../../CONTEXT.md), [ADR-0002](../adr/0002-specs-as-durable-design-docs.md),
урок [`docs/course/lessons/06_superset_bi.md`](../course/lessons/06_superset_bi.md) урок [`docs/course/lessons/06_superset_bi.md`](../course/lessons/06_superset_bi.md)
@@ -141,11 +141,16 @@ KPI-полоса:
## Open questions ## Open questions
- **Точная формула Conversion:** доля визитов с ≥1 pageview `/confirmation`, или - **Resolved: точная формула Conversion.** Используем page-funnel conversion:
просмотры `/confirmation` / просмотры `/home`? Влияет на число (~8%) и на то, `countIf(page_url_path = '/confirmation') / countIf(page_url_path = '/home')`.
что именно учим (визит-конверсия vs page-to-page). На полном датасете это `35 / 426 = 8.2%`. Визитовая формула даёт 25.3% и
- **Events by Hour** — оставляем ли (зависит от проверки на пустоту на полных отклонена, потому что KPI должен совпадать с логикой chart `Page Funnel`.
данных: разброс `event_ts` по часам). - **Resolved: Events by Hour.** Оставляем: на полном датасете есть два часовых
бакета (`20 → 256`, `21 → 744`), график не пустой.
- **Resolved: Funnel в Superset 4.1.2.** MCP Context7 по `/apache/superset` не
дал точной строки `viz_type`; установленный Superset 4.1.2 проверен по bundled
example `Featured Charts/Funnel.yaml` и frontend assets. Используем
`viz_type: funnel`.
- **Требования к будущему генератору**, вытекающие из упёртостей этих данных - **Требования к будущему генератору**, вытекающие из упёртостей этих данных
(разнообразие `event_type`, возвраты пользователей → sessions>users, (разнообразие `event_type`, возвраты пользователей → sessions>users,
реалистичная воронка) — при возврате к генератору перенести в его реалистичная воронка) — при возврате к генератору перенести в его
+67 -31
View File
@@ -25,6 +25,11 @@ logger = logging.getLogger(__name__)
sys.path.insert(0, '/app') sys.path.insert(0, '/app')
OBSOLETE_CHART_NAMES = {
"🎯 Unique Sessions",
}
# Конфигурация чартов # Конфигурация чартов
CHARTS_CONFIG = [ CHARTS_CONFIG = [
# KPI блок # KPI блок
@@ -63,32 +68,36 @@ CHARTS_CONFIG = [
} }
}, },
{ {
"slice_name": "🎯 Unique Sessions", "slice_name": "📈 Avg Events/Visit",
"viz_type": "big_number_total", "previous_slice_names": ["📈 Avg Events/Session"],
"dataset_name": "v_events_enriched",
"params": {
"metric": {
"expressionType": "SQL",
"sqlExpression": "COUNT(DISTINCT click_id)",
"label": "Unique Sessions",
"optionName": "metric_3"
},
"y_axis_format": ",d",
"time_range": "No filter"
}
},
{
"slice_name": "📈 Avg Events/Session",
"viz_type": "big_number_total", "viz_type": "big_number_total",
"dataset_name": "v_events_enriched", "dataset_name": "v_events_enriched",
"params": { "params": {
"metric": { "metric": {
"expressionType": "SQL", "expressionType": "SQL",
"sqlExpression": "COUNT(*) / COUNT(DISTINCT click_id)", "sqlExpression": "COUNT(*) / COUNT(DISTINCT click_id)",
"label": "Avg Events/Session", "label": "Avg Events/Visit",
"optionName": "metric_4" "optionName": "metric_4"
}, },
"y_axis_format": ".2f", "y_axis_format": ".1f",
"time_range": "No filter"
}
},
{
"slice_name": "🎯 Conversion to /confirmation",
"viz_type": "big_number_total",
"dataset_name": "v_events_enriched",
"params": {
"metric": {
"expressionType": "SQL",
"sqlExpression": (
"if(countIf(page_url_path = '/home') = 0, 0, "
"countIf(page_url_path = '/confirmation') / countIf(page_url_path = '/home'))"
),
"label": "Conversion to /confirmation",
"optionName": "metric_5"
},
"y_axis_format": ".1%",
"time_range": "No filter" "time_range": "No filter"
} }
}, },
@@ -175,18 +184,32 @@ CHARTS_CONFIG = [
} }
}, },
{ {
"slice_name": "📄 Top Pages", "slice_name": "🪜 Page Funnel",
"viz_type": "dist_bar", "previous_slice_names": ["📄 Top Pages"],
# В Superset 4.1.2 `funnel` есть в frontend-плагинах и примерах
# поставки, хотя legacy registry `superset.viz.viz_types` его не
# показывает. Параметры взяты из bundled Featured Charts/Funnel.yaml.
"viz_type": "funnel",
"dataset_name": "v_top_pages_daily", "dataset_name": "v_top_pages_daily",
"params": { "params": {
"groupby": ["page_url_path"], "groupby": ["page_url_path"],
"metrics": [ "metric": {
{"expressionType": "SQL", "sqlExpression": "SUM(pageviews)", "label": "Pageviews"} "expressionType": "SQL",
], "sqlExpression": "SUM(pageviews)",
"label": "Pageviews"
},
"row_limit": 20, "row_limit": 20,
"time_range": "No filter", "time_range": "No filter",
"orientation": "vertical", "sort_by_metric": True,
"show_legend": False "percent_calculation_type": "first_step",
"color_scheme": "supersetColors",
"show_legend": True,
"legendOrientation": "top",
"legendMargin": 50,
"tooltip_label_type": 5,
"number_format": "SMART_NUMBER",
"show_labels": True,
"show_tooltip_labels": True
} }
}, },
# Качество данных # Качество данных
@@ -233,13 +256,13 @@ DASHBOARD_CONFIG = {
DASHBOARD_ROWS = [ DASHBOARD_ROWS = [
# KPI-полоса: 4 числа в один ряд # KPI-полоса: 4 числа в один ряд
[("📊 Total Events", 3), ("👤 Unique Users", 3), [("📊 Total Events", 3), ("👤 Unique Users", 3),
("🎯 Unique Sessions", 3), ("📈 Avg Events/Session", 3)], ("📈 Avg Events/Visit", 3), ("🎯 Conversion to /confirmation", 3)],
# Динамика во времени + разрез по устройствам # Динамика во времени + разрез по устройствам
[("📅 Events by Hour", 8), ("📱 Traffic by Device", 4)], [("📅 Events by Hour", 8), ("📱 Traffic by Device", 4)],
# География + эффективность маркетинговых каналов # География + эффективность маркетинговых каналов
[("🌍 Geography Map", 6), ("🔗 UTM Effectiveness Table", 6)], [("🌍 Geography Map", 6), ("🔗 UTM Effectiveness Table", 6)],
# Популярные страницы + качество данных # Популярные страницы + качество данных
[("📄 Top Pages", 6), ("🔍 Data Quality Summary", 6)], [("🪜 Page Funnel", 6), ("🔍 Data Quality Summary", 6)],
] ]
# Высота строки в grid-units Superset (одинаковая для всех чартов строки — # Высота строки в grid-units Superset (одинаковая для всех чартов строки —
@@ -410,13 +433,17 @@ def main() -> bool:
params["viz_type"] = chart_config["viz_type"] params["viz_type"] = chart_config["viz_type"]
serialized_params = json.dumps(params) serialized_params = json.dumps(params)
# Проверяем, существует ли уже чарт # Проверяем, существует ли уже чарт. Старые имена нужны для
existing = db.session.query(Slice).filter_by( # идемпотентного rename без дублей в списке Charts.
slice_name=chart_config["slice_name"] chart_names = [chart_config["slice_name"]]
).first() chart_names.extend(chart_config.get("previous_slice_names", []))
existing = db.session.query(Slice).filter(
Slice.slice_name.in_(chart_names)
).order_by(Slice.id.asc()).first()
if existing: if existing:
# Синхронизируем параметры существующего чарта с конфигом. # Синхронизируем параметры существующего чарта с конфигом.
existing.slice_name = chart_config["slice_name"]
existing.viz_type = chart_config["viz_type"] existing.viz_type = chart_config["viz_type"]
existing.datasource_id = dataset.id existing.datasource_id = dataset.id
existing.datasource_type = "table" existing.datasource_type = "table"
@@ -456,6 +483,15 @@ def main() -> bool:
db.session.rollback() db.session.rollback()
logger.info(f"Created/Found {len(created_charts)} charts") logger.info(f"Created/Found {len(created_charts)} charts")
current_chart_names = {chart_config["slice_name"] for chart_config in CHARTS_CONFIG}
for obsolete_name in sorted(OBSOLETE_CHART_NAMES - current_chart_names):
obsolete_charts = db.session.query(Slice).filter_by(slice_name=obsolete_name).all()
for obsolete in obsolete_charts:
db.session.delete(obsolete)
logger.info("Deleted obsolete chart: %s (ID: %s)", obsolete_name, obsolete.id)
db.session.flush()
metadata_json = build_dashboard_metadata(datasets_by_name.get("v_events_enriched")) metadata_json = build_dashboard_metadata(datasets_by_name.get("v_events_enriched"))
# Создаём позиции чартов для layout. # Создаём позиции чартов для layout.