From 9f69a33c311eba61d10fb9bebde8c8d91f44055d Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Fri, 5 Jun 2026 22:30:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(superset):=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=20BI-=D0=B4=D0=B0=D1=88=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=B4=20=D0=B8=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=83=D1=80=D0=BE=D0=BA=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - Superset dashboard открывался с ошибками datasources и неудобным layout, а курс не содержал готового урока по BI-витрине. - Что: - добавлен урок 6 про Superset поверх ClickHouse DM-витрин. - исправлена раскладка dashboard и дефолтный фильтр даты для исторических демо-данных. - добавлено восстановление metadata колонок датасетов при обновлении dashboard. - Проверка: - make superset-dashboard. - /api/v1/dashboard/1/datasets и /superset/explore_json для chart 10 возвращают 200. - python3 -m py_compile superset/create_dashboard.py; git diff --cached --check. --- README.md | 5 +- docs/SUPERSET_DASHBOARD.md | 6 +- docs/course/README.md | 4 +- docs/course/lessons/06_superset_bi.md | 400 ++++++++++++++++++++++++++ superset/create_dashboard.py | 47 ++- 5 files changed, 444 insertions(+), 18 deletions(-) create mode 100644 docs/course/lessons/06_superset_bi.md diff --git a/README.md b/README.md index 18b30fc..8cb4a34 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ flowchart LR | `make ddl` | Применить DDL в ClickHouse (вне Airflow) | | `make transform` | Запустить batch-процесс `STG -> ODS -> DDS -> DM` (вне Airflow) | | `make superset-init` | Подключение к ClickHouse + импорт датасетов | -| `make superset-dashboard` | Создание дашборда с чартами | +| `make superset-dashboard` | Создание дашборда с чартами, обновление layout/metadata | | `make superset-ui` | Показать URL Superset | | `make superset-restart` | Перезапуск Superset | @@ -276,6 +276,9 @@ make superset-dashboard | **Маркетинг** | UTM Effectiveness Table, Top Pages | `v_utm_effectiveness`, `v_top_pages_daily` | | **Quality** | Data Quality Summary | `dq_summary` | +Фильтр `Date Range` по умолчанию открыт как `No filter`, потому что демо-данные лежат в +историческом диапазоне (`2022-11-28`). + ### Архитектура Superset ``` diff --git a/docs/SUPERSET_DASHBOARD.md b/docs/SUPERSET_DASHBOARD.md index 2fe6a04..ff61627 100644 --- a/docs/SUPERSET_DASHBOARD.md +++ b/docs/SUPERSET_DASHBOARD.md @@ -28,7 +28,7 @@ make transform # Автоматическая инициализация (создание подключения и датасетов) make superset-init -# Создание дашборда с чартами +# Создание дашборда с чартами; при необходимости обновляет metadata колонок датасетов make superset-dashboard ``` @@ -62,6 +62,8 @@ make superset-dashboard - **🎯 Unique Sessions** — уникальные сессии (click_id) - **📈 Avg Events/Session** — среднее количество событий на сессию +KPI разложены в одну строку по 12-колоночной сетке Superset: четыре блока по 3 колонки. + #### Динамика трафика - **📅 Events by Hour** — линейный график событий по часам - **📱 Traffic by Device** — pie chart распределения по устройствам @@ -80,7 +82,7 @@ make superset-dashboard | Фильтр | Поле | Тип | Применение | |--------|------|-----|------------| -| 📅 Date Range | `event_date` | Time Range | Все чарты | +| 📅 Date Range | `event_date` | Time Range | Все чарты; по умолчанию `No filter`, чтобы демо-данные 2022 года не скрывались | | 🌍 Country | `geo_country` | Multi-select | Все чарты | | 📱 Device Type | `device_type` | Multi-select | Все чарты | | 🌐 Browser | `browser_name` | Multi-select | Все чарты | diff --git a/docs/course/README.md b/docs/course/README.md index 87c2436..5c9c325 100644 --- a/docs/course/README.md +++ b/docs/course/README.md @@ -1,7 +1,7 @@ # Курс «Кликстрим на ClickHouse» (со звёздочкой) Продвинутый курс для менти, уже прошедших базовую программу: основные паттерны -инженерии данных на стенде Kafka + ClickHouse + Airflow + мониторинг. Менти проходит +инженерии данных на стенде Kafka + ClickHouse + Airflow + мониторинг + BI. Менти проходит материал самостоятельно, а на еженедельном созвоне с ментором разбирает затыки и отвечает на вопросы по теме — чтобы проверить глубину понимания. @@ -18,7 +18,7 @@ | [`PRD.md`](./PRD.md) | Рамка: зачем курс, цели, аудитория, скоуп, критерии успеха | Чтобы понять «что и зачем». Замороженный документ | | [`LEARNING_PLAN.md`](./LEARNING_PLAN.md) | План обучения: карта уроков, маршрут, аудит эталонных путей | Чтобы понять «в каком порядке и из чего» | | [`LESSON_STANDARD.md`](./LESSON_STANDARD.md) | Стандарт уроков: шаблон урока, качество кода, самопроверка | Рабочий чеклист при написании каждого урока | -| [`lessons/`](./lessons/) | Сами уроки, по одному файлу (есть: уроки 0–5) | Прохождение курса менти | +| [`lessons/`](./lessons/) | Сами уроки, по одному файлу (есть: уроки 0–6) | Прохождение курса менти | ## Порядок чтения diff --git a/docs/course/lessons/06_superset_bi.md b/docs/course/lessons/06_superset_bi.md new file mode 100644 index 0000000..047a13b --- /dev/null +++ b/docs/course/lessons/06_superset_bi.md @@ -0,0 +1,400 @@ +# Урок 6. BI-витрина в Superset + +> Формат: **практика** — будешь запускать Superset поверх готовых витрин ClickHouse, +> смотреть дашборд и делать маленькую обратимую правку в конфигурации чарта. +> Пререквизит: пройдены уроки 4–5 (ты уже запускал `etl_pipeline`, видел DM-слой в конце +> пайплайна и понимаешь разницу между мониторингом стенда и данными для анализа). +> Эталонные пути: +> [`sql/ddl/dm/40_dm.sql`](../../../sql/ddl/dm/40_dm.sql), +> [`sql/dm/40_dds_to_dm.sql`](../../../sql/dm/40_dds_to_dm.sql), +> [`superset/init_superset.py`](../../../superset/init_superset.py), +> [`superset/create_dashboard.py`](../../../superset/create_dashboard.py). +> +> Поток данных одной строкой: +> `DDS → DM views / dm.dq_summary → Superset datasets → charts → dashboard → native filters` +> +> О чём урок простыми словами: ClickHouse уже подготовил таблицы и представления для +> потребления. Superset превращает их в экран для аналитика: датасеты, графики, фильтры +> и один общий дашборд. + +--- + +## 1. Зачем и где в проде + +После уроков 1–4 у нас есть данные: поток приземлился в STG, разобрался в ODS, собрался в DDS, +а в конце появился DM-слой. После урока 5 у нас есть мониторинг: он отвечает, жив ли стенд и +не сломался ли пайплайн. + +Теперь нужен другой взгляд — **BI** (Business Intelligence, «аналитический интерфейс для +бизнес-вопросов»). BI отвечает не «жив ли ClickHouse», а: + +- сколько событий пришло; +- какие устройства чаще встречаются; +- из каких стран пришёл трафик; +- какие UTM-каналы дают больше кликов; +- какие страницы самые популярные; +- есть ли видимые проблемы качества данных. + +**Superset** — BI-инструмент. Он не заменяет ClickHouse, Airflow или Grafana. Он сидит поверх +готовых данных и даёт интерфейс для просмотра, фильтрации и сборки графиков. + +В нашем стенде роли такие: + +| Слой | Что делает | +|------|------------| +| `dds.click`, `dds.event` | хранит собранные сущности после ODS | +| `dm.v_*`, `dm.dq_summary` | готовит поверхность потребления для аналитики | +| Superset dataset | регистрирует таблицу или VIEW из ClickHouse в Superset | +| Superset chart | сохраняет один график или KPI на базе dataset | +| Superset dashboard | собирает charts в один экран | +| Native filters | фильтруют dashboard по дате, стране, устройству, браузеру | + +Граница урока: **витрина DM и BI-экран — не одно и то же**. + +DM-витрина — это SQL-объект в ClickHouse. Она задаёт форму данных: какие поля есть, на какой +гранулярности лежит агрегат, какие joins уже сделаны. BI-экран — это способ показать эту +витрину человеку: график, таблица, фильтр, порядок блоков на странице. + +> **В проде иначе.** Superset обычно подключают к нескольким хранилищам, заводят роли и права, +> разделяют черновые и опубликованные дашборды, а тяжёлые витрины материализуют. Но базовая +> схема та же: хранилище готовит данные, BI даёт удобную точку потребления. + +--- + +## 2. Руки: запускаем Superset и смотрим дашборд + +Подними стенд и прогони маленький срез: + +```bash +make up +make ddl +LIMIT=50 make data +make transform +``` + +`make transform` прогоняет цепочку STG → ODS → DDS → DM вне Airflow. Для этого урока так +быстрее: нам нужен готовый DM-слой, а не разбор DAG. + +Теперь инициализируй Superset: + +```bash +make superset-init +``` + +Эта команда создаёт или обновляет: + +- подключение `clickhouse_dwh`; +- 6 datasets поверх `dm.*`; +- 10 charts; +- dashboard `E-commerce Analytics Dashboard`. + +Открой Superset: `http://localhost:8088` (логин `admin`, пароль `admin`). + +Если Superset предлагает сменить пароль после первого входа, для учебного стенда можно нажать +**Skip**. В проде так не делают, но локальный курс держит одинаковые инструкции для всех. + +### Открываем dashboard + +Открой готовый dashboard: + +```text +http://localhost:8088/superset/dashboard/1/ +``` + +Если URL не открылся, зайди через меню **Dashboards** и найди `E-commerce Analytics Dashboard`. + +На экране должны быть блоки: + +- KPI сверху: `Total Events`, `Unique Users`, `Unique Sessions`, `Avg Events/Session`; +- динамика: `Events by Hour`, `Traffic by Device`; +- география: `Geography Map`; +- маркетинг: `UTM Effectiveness Table`, `Top Pages`; +- качество данных: `Data Quality Summary`. + +### Фильтр даты + +В демо-данных события датированы `2022-11-28`. В текущей конфигурации dashboard фильтр даты +открывается как `No filter`. Если у тебя осталась старая metadata Superset и native filter +**Date Range** стоит в значении `Last week`, часть графиков может быть пустой, хотя данные есть. + +Для этого урока поставь в фильтре даты одно из двух: + +- `No filter`; +- или ручной диапазон вокруг `2022-11-28`. + +После этого нажми **Apply filters**. Теперь смотри на dashboard как аналитик: какие графики +отвечают на бизнес-вопросы, а какие только показывают техническое качество данных. + +### Проверяем данные напрямую в ClickHouse + +Открой ClickHouse play-консоль: `http://localhost:9123/play`. + +Проверь, что основной dataset Superset не пустой: + +```sql +SELECT count() AS events +FROM dm.v_events_enriched; +``` + +И посмотри, откуда берётся график `Top Pages`: + +```sql +SELECT page_url_path, sum(pageviews) AS pageviews +FROM dm.v_top_pages_daily +GROUP BY page_url_path +ORDER BY pageviews DESC +LIMIT 20; +``` + +Запомни эту связку: Superset показывает график, но данные и логика агрегации живут в ClickHouse. + +--- + +## 3. Загляни внутрь + +Разберём три места: DM-витрины в ClickHouse, регистрацию datasets в Superset и сборку charts / +dashboard. + +### DM: поверхность потребления + +Открой [`sql/ddl/dm/40_dm.sql`](../../../sql/ddl/dm/40_dm.sql). + +В начале файла написано, почему DM сейчас сделан через `VIEW`: + +- логику можно менять без пересоздания таблиц; +- нет копии данных поверх DDS; +- для демо производительности достаточно. + +Основная витрина для dashboard — `dm.v_events_enriched`. Она соединяет `dds.event` и `dds.click` +через `click_id`: + +```sql +FROM dds.event AS e +LEFT JOIN dds.click AS c ON c.click_id = e.click_id; +``` + +`LEFT JOIN` здесь осознанный: событие может существовать без части контекста из клика. Для BI это +значит: график событий не исчезает только потому, что у части строк нет устройства или географии. + +Другие VIEW дают более узкие поверхности: + +| VIEW / таблица | Для чего нужна в Superset | +|----------------|---------------------------| +| `dm.v_events_enriched` | KPI, динамика, устройства, география, фильтры | +| `dm.v_daily_traffic` | готовая дневная агрегация трафика | +| `dm.v_utm_effectiveness` | таблица по UTM-каналам | +| `dm.v_top_pages_daily` | популярные страницы | +| `dm.v_session_overview` | обзор сессий | +| `dm.dq_summary` | сводка качества данных по слоям | + +Открой [`sql/dm/40_dds_to_dm.sql`](../../../sql/dm/40_dds_to_dm.sql). Этот файл не пересчитывает +все `dm.v_*`: VIEW создаются в DDL. Здесь batch-часть наполняет `dm.dq_summary`, чтобы dashboard +мог показать качество данных после каждого прогона. + +### `init_superset.py`: подключение и datasets + +Открой [`superset/init_superset.py`](../../../superset/init_superset.py). + +Сначала скрипт собирает URI ClickHouse: + +```python +return f"clickhousedb://{user}:{password}@{host}:{port}/{database}" +``` + +Внутри Docker-сети Superset ходит в ClickHouse по HTTP-порту `8123`, поэтому URI использует +`clickhousedb://...@clickhouse:8123/default`. + +Потом скрипт создаёт подключение `clickhouse_dwh` и регистрирует datasets: + +```python +datasets = [ + { + "table_name": "v_events_enriched", + "schema": "dm", + "database_name": "clickhouse_dwh", + "description": "Полная обогащённая витрина событий (event + click)" + }, + ... +] +``` + +**Dataset** в Superset — это не копия данных. Это запись в metadata Superset: какая таблица или +VIEW есть в ClickHouse, какие у неё колонки и как её можно использовать в графиках. + +Metadata Superset живёт в PostgreSQL, а сами данные остаются в ClickHouse. Поэтому после полного +сброса volumes нужно заново создать metadata Superset, а после пересчёта данных — сами charts +обычно остаются теми же. + +### `create_dashboard.py`: charts, dashboard, filters + +Открой [`superset/create_dashboard.py`](../../../superset/create_dashboard.py). + +В `CHARTS_CONFIG` лежит список charts. Один элемент списка — один график: + +```python +{ + "slice_name": "📄 Top Pages", + "viz_type": "dist_bar", + "dataset_name": "v_top_pages_daily", + "params": { + "groupby": ["page_url_path"], + "metrics": [ + {"expressionType": "SQL", "sqlExpression": "SUM(pageviews)", "label": "Pageviews"} + ], + "row_limit": 20, + "time_range": "No filter", + "orientation": "vertical", + "show_legend": False + } +} +``` + +Здесь видно четыре идеи: + +- `slice_name` — имя chart в Superset; +- `viz_type` — тип визуализации; +- `dataset_name` — на каком dataset строится chart; +- `params` — настройка запроса и отображения. + +Ниже `DASHBOARD_CONFIG` задаёт сам dashboard: + +```python +DASHBOARD_CONFIG = { + "dashboard_title": "🛒 E-commerce Analytics Dashboard", + "description": "...", + "published": True, + "slug": "ecommerce-analytics", +} +``` + +Dashboard находится по `slug`, а charts добавляются в layout. Если dashboard уже существует, +скрипт обновляет metadata, layout и список charts. На этом держится управляемая правка: можно +поменять параметр chart, запустить `make superset-dashboard` и увидеть результат в UI. + +Native filters создаются в `build_dashboard_metadata`. Там есть фильтры: + +- `Date Range` по `event_date`; +- `Country` по `geo_country`; +- `Device Type` по `device_type`; +- `Browser` по `browser_name`. + +> **Что проверили по API.** Перед уроком Superset сверили через MCP Context7 (`/apache/superset`): +> в Superset есть отдельные сущности charts и dashboards, metadata хранится отдельно от +> подключаемых источников данных, а row limit — часть настройки запросов и конфигурации. +> Поэтому в уроке не лезем в REST API Superset, а работаем через уже существующий скрипт стенда. + +--- + +## 4. Управляемая правка: уменьшаем Top Pages + +Сейчас chart `Top Pages` показывает до 20 страниц: + +```python +"row_limit": 20, +``` + +Сделай маленькую видимую правку: временно покажи только топ-3 страницы. + +Открой [`superset/create_dashboard.py`](../../../superset/create_dashboard.py), найди chart +`Top Pages` и поменяй: + +```python +"row_limit": 20, +``` + +на: + +```python +"row_limit": 3, +``` + +Запусти обновление dashboard: + +```bash +make superset-dashboard +``` + +Вернись в Superset и обнови страницу dashboard. В chart `Top Pages` должно остаться не больше +трёх страниц. Если фильтр даты снова скрыл данные, поставь **Date Range → No filter** и нажми +**Apply filters**. + +Почему это хорошая маленькая правка: + +- мы не меняем SQL-витрину в ClickHouse; +- не создаём новый dataset; +- не трогаем подключение к ClickHouse; +- меняем только BI-представление уже готовых данных. + +### Верни как было + +Верни в [`superset/create_dashboard.py`](../../../superset/create_dashboard.py): + +```python +"row_limit": 20, +``` + +И снова запусти: + +```bash +make superset-dashboard +``` + +После обновления страницы chart `Top Pages` снова может показывать до 20 страниц. + +Если после экспериментов Superset выглядит странно, самый простой учебный возврат dashboard +metadata к конфигурации из репозитория: + +```bash +make superset-init +``` + +Данные в ClickHouse эта команда не удаляет. Она повторно применяет подключение, datasets и +dashboard metadata Superset. + +--- + +## 5. Проверь себя + +| Действие | Где смотреть | Что ожидать | +|----------|--------------|-------------| +| `make transform` | ClickHouse `dm.v_events_enriched` | `count() > 0` | +| `make superset-init` | Superset → **Settings → Database Connections** | есть подключение `clickhouse_dwh` | +| открыть **Datasets** | Superset UI | есть datasets `v_events_enriched`, `v_top_pages_daily`, `dq_summary` | +| открыть dashboard | Superset UI | видны KPI, маркетинг, география и качество данных | +| поставить **Date Range → No filter** | dashboard filters | графики не скрываются из-за даты `2022-11-28` | +| поменять `row_limit` у `Top Pages` на `3` и запустить `make superset-dashboard` | chart `Top Pages` | не больше трёх страниц | +| вернуть `row_limit` на `20` и запустить `make superset-dashboard` | chart `Top Pages` | ограничение снова до 20 страниц | + +Вопросы для созвона: + +- чем DM-витрина отличается от Superset dataset; +- почему Superset не должен ходить напрямую в сырые STG-таблицы; +- зачем dashboard нужен `Date Range`, если SQL-витрина уже агрегирована; +- почему изменение `row_limit` — это BI-правка, а не изменение модели данных; +- где хранятся данные, а где metadata Superset. + +--- + +## 6. Что должно получиться + +К концу урока у тебя должен быть открытый dashboard `E-commerce Analytics Dashboard` в Superset. +Сделай скриншот после временной правки `Top Pages`: на нём должно быть видно, что chart показывает +не больше трёх страниц. + +Второй артефакт — короткий абзац своими словами: + +> DM-слой в ClickHouse готовит данные для потребления, dataset в Superset регистрирует эту +> витрину, chart задаёт один график, dashboard собирает charts в экран, а native filters дают +> аналитику быстрый способ менять срез данных. + +После этого обязательно верни `row_limit` на `20`, чтобы следующий урок или следующий прогон +стенда начинался с исходной конфигурации. + +--- + +## Мост после курса + +Теперь у тебя есть сквозная цепочка: Kafka → ClickHouse STG → ODS → DDS → DM → мониторинг → +Superset. Следующий честный вопрос уже не про этот стенд, а про продакшен: какие витрины стоит +материализовать, какие права дать BI-пользователям и как не превратить dashboard в единственный +источник правды вместо версионированного SQL в репозитории. diff --git a/superset/create_dashboard.py b/superset/create_dashboard.py index 7d9e0ed..b768999 100644 --- a/superset/create_dashboard.py +++ b/superset/create_dashboard.py @@ -114,7 +114,7 @@ CHARTS_CONFIG = [ } ], "groupby": [], - "time_range": "Last week", + "time_range": "No filter", "adhoc_filters": [], "row_limit": 10000 } @@ -231,6 +231,20 @@ DASHBOARD_CONFIG = { } +CHART_LAYOUT_BY_TITLE = { + "📊 Total Events": {"width": 3, "height": 32, "x": 0, "y": 0}, + "👤 Unique Users": {"width": 3, "height": 32, "x": 3, "y": 0}, + "🎯 Unique Sessions": {"width": 3, "height": 32, "x": 6, "y": 0}, + "📈 Avg Events/Session": {"width": 3, "height": 32, "x": 9, "y": 0}, + "📅 Events by Hour": {"width": 8, "height": 60, "x": 0, "y": 32}, + "📱 Traffic by Device": {"width": 4, "height": 60, "x": 8, "y": 32}, + "🌍 Geography Map": {"width": 6, "height": 60, "x": 0, "y": 92}, + "🔗 UTM Effectiveness Table": {"width": 6, "height": 60, "x": 6, "y": 92}, + "📄 Top Pages": {"width": 6, "height": 60, "x": 0, "y": 152}, + "🔍 Data Quality Summary": {"width": 6, "height": 60, "x": 6, "y": 152}, +} + + def sync_query_context(chart, params: dict, dataset_id: int) -> None: """ Синхронизирует сохраненный query_context с обновленными params чарта. @@ -302,7 +316,7 @@ def build_dashboard_metadata(filter_dataset_id: int | None) -> str: "name": "📅 Date Range", "filterType": "filter_time", "targets": [{"datasetId": filter_dataset_id, "column": {"name": "event_date"}}], - "defaultValue": "Last week", + "defaultValue": "No filter", "scope": {"rootPath": ["ROOT_ID"], "excluded": []}, "cascadeParentIds": [], "isInstant": True @@ -377,6 +391,14 @@ def main() -> bool: if not dataset: logger.warning(f"Dataset '{chart_config['dataset_name']}' not found, skipping chart") continue + + if not dataset.columns: + # Если dataset создали до DDL/DM, в metadata Superset нет колонок. + # Обновляем их здесь, чтобы dashboard восстанавливался через make superset-dashboard. + dataset.fetch_metadata() + db.session.commit() + logger.info("Refreshed dataset metadata: %s", dataset.table_name) + datasets_by_name[chart_config["dataset_name"]] = dataset.id try: @@ -452,13 +474,15 @@ def main() -> bool: }, } - # Добавляем чарты в layout (grid: 12 columns) - y_position = 0 - chart_index = 0 - + # Раскладываем dashboard вручную по 12-колоночной сетке: + # KPI в одну строку, затем аналитические блоки парами. for chart in created_charts: if chart: chart_component_id = f"CHART-{chart['id']}" + layout = CHART_LAYOUT_BY_TITLE.get( + chart["title"], + {"width": 6, "height": 50, "x": 0, "y": 212}, + ) positions[chart_component_id] = { "id": chart_component_id, "type": "CHART", @@ -467,16 +491,13 @@ def main() -> bool: "meta": { "chartId": chart['id'], "sliceName": chart['title'], - "height": 50, - "width": 4 if chart_index < 4 else 6, - "x": (chart_index % 3) * 4 if chart_index < 4 else (chart_index % 2) * 6, - "y": y_position, + "height": layout["height"], + "width": layout["width"], + "x": layout["x"], + "y": layout["y"], }, } positions["GRID_ID"]["children"].append(chart_component_id) - chart_index += 1 - if chart_index % 4 == 0: - y_position += 50 # Создаём дашборд if created_charts: