# Superset: витрины глазами Документ описывает зону BI: куда Superset ходит за данными и за своими метаданными, из чего собран стендовый дашборд, как снять свежий экспорт после правки в интерфейсе и почему правка руками живет только до перезапуска. Состав сюжетов и колонок витрин здесь не повторяется: он живет в [спеке](../specs/2026-07-30-stand-v2-realism.md), раздел 7, и в [доке слоя DM](dm.md). ## Устройство зоны Superset читает ClickHouse пользователем `bi` **на ноде 2** — так задано подключением `ClickHouse` (`clickhousedb://bi@clickhouse-02:8123`). Нода 2 здесь — ловушка правильных ошибок из [спеки](../specs/2026-07-30-stand-v2-realism.md), раздел «Роли нод»: DDL идет с ноды 1, и забытый `ON CLUSTER` виден в дашборде. Пароля в подключении нет. Его отдает штатное хранилище паролей Superset (`SQLALCHEMY_CUSTOM_PASSWORD_STORE` в `infra/superset/superset_config.py`), которое читает `CLICKHOUSE_BI_PASSWORD` из окружения. Поэтому файл подключения безопасно лежит в git, и экспорт из интерфейса не должен возвращать пароль обратно в YAML. Свои метаданные — подключения, датасеты, графики, дашборды, пользователи — Superset держит в базе `superset` служебного `postgres-metadata`, рядом с базой Airflow. В ClickHouse он не пишет ничего. ## Дерево импорта `infra/superset/import/` — это дашборд стенда в виде файлов. Каталог смонтирован в контейнер как `/app/import`. | Файл | Что описывает | |---|---| | `metadata.yaml` | версия формата, `1.0.0`; больше в нем ничего нет | | `databases/clickhouse.yaml` | подключение к ClickHouse: URI без пароля и постоянный uuid | | `datasets/ClickHouse/*.yaml` | три витрины: `revenue_daily_v`, `purchase_vs_orders_v`, `daily_traffic_v` | | `charts/*.yaml` | девять графиков: четыре крупных числа, столбики выручки и кольцо долей, сводная таблица сверки, линии трафика и доля известных | | `dashboards/business_stories.yaml` | дашборд «Бизнес-сюжеты» и раскладка панелей | Связаны файлы не путями, а uuid. Датасет называет `database_uuid`, график — `dataset_uuid`, а дашборд не называет графики вовсе: единственная связь с ними лежит в его `position`, где узел `CHART-*` несет `meta.uuid` нужного графика. График, которого нет в `position`, импортируется молча и не появляется нигде. Uuid у стенда розданы рукой: общее начало `19600000-0000-4000-8000-`, дальше номер объекта в последнем блоке — `1`–`3` у датасетов, `101`–`109` у графиков, `201` у дашборда (`…-000000000101`). Экспорт из интерфейса приносит свои, сгенерированные; их заменяют на эти, иначе повторный импорт заведет вторую копию объекта вместо обновления первой. Датасет несет русские имена колонок, формат чисел и метрики — а график только ссылается на них по имени. Значит, подпись или формат правятся в одном месте, а не в каждом графике. Два исключения проверены на живом стенде, Superset 6.1. Подписи оси график берет не из датасета, а из своего `y_axis_format`: без него ось выручки уходит в «1.2M». И разрез сводной таблицы подписан именем колонки (`mismatch_class`), хотя колонке даты в той же таблице русское имя достается. Датасеты объявлены физическими (`schema: dm`, `sql: null`), хотя за именем стоит представление ClickHouse. Импорт в источник не ходит и разницы между таблицей и представлением не знает, а виртуальный датасет со своим SQL просто повторил бы запрос, который уже живет в `sql/ddl/60-dm-views.sql`. ## Из чего собран экран Четыре строки панелей: - ряд крупных чисел — выручка, заказы, средний чек, посетители; - выручка по дням и категориям, рядом доля категорий кольцом; - сводная сверки purchase и заказов; - посетители и известные пользователи, рядом доля известных. Правая панель пары отвечает на вопрос «сколько из целого» и берет цвета левой: категория окрашена одинаково на столбиках и в кольце, доля известных — как линия известных пользователей рядом. Цвет закрепляет `label_colors` в `metadata` дашборда. Шесть категорий выписаны в него руками по каталогу генератора (`generator/src/clickstream_generator/catalog.py`): седьмая останется без закрепленного цвета, пока файл не поправят. Средний чек и доля известных считаются метриками датасета — `SUM(revenue) / nullIf(SUM(orders), 0)` и то же для доли известных. Знаменатель обернут не зря: на периоде без заказов деление Decimal на ноль в ClickHouse — не `nan`, а исключение, и панель показала бы ошибку вместо пустоты. У витрины выручки есть готовая колонка `aov`, но среднее по ней дало бы другое число: день с тремя заказами получил бы тот же вес, что день с тремя сотнями. «Период» — единственный фильтр дашборда, тип `filter_time` с пустой целью (`targets: [{}]`). Такой фильтр колонку не называет: он подставляет выбранный период в то временное условие, которое уже стоит у каждого графика. Поэтому сверка сужается по `order_day`, а остальные панели — по `report_date`. ## Импорт при каждой инициализации Последняя строка `infra/superset/init.sh`: ``` superset import-directory /app/import --overwrite ``` Служба `superset-init` отрабатывает перед подъемом самого Superset, то есть при каждом `make up`. Без `--overwrite` второй прогон падал бы на первом же знакомом uuid; с ним объект обновляется на месте, и повторная инициализация проходит без ошибок. Отсюда правило зоны: **стендовый дашборд не расширяют руками в интерфейсе.** Правка живет до следующей инициализации, а потом молча заменяется тем, что лежит в git. Пробовать интерфейс менти никто не мешает — но все, что должно пережить перезапуск, приезжает файлом. Это правило относится только к стендовым объектам. Свои датасеты, графики и дашборды менти живут в той же базе метаданных Superset, а импорт трогает только объекты с известными ему uuid. Поэтому свои объекты переживают `make up` и `make rebuild-storage`. Их стирает только `make clean` или адресный сброс базы метаданных по рецепту ниже. ## Как снять свежий экспорт Маршрут один: правишь на живом стенде, забираешь экспорт, кладешь дерево в git. 1. Собрать или поправить график и дашборд в интерфейсе — на стенде, а не в файлах. Ключи `params` графиков велики, недокументированы и меняются от версии к версии; неверный ключ не ошибается, а рисует пустоту. Интерфейс здесь единственный надежный оракул. 2. Снять экспорт и достать его из контейнера: ``` docker compose exec superset superset export-dashboards -f /tmp/export.zip docker compose cp superset:/tmp/export.zip /tmp/export.zip unzip -d /tmp/superset-export /tmp/export.zip ``` Внутри архива — один каталог `dashboard_export_<дата>` ровно той же формы, что и дерево импорта. Команда выгружает все дашборды сразу; на стенде он один. 3. Вычистить экспортный сор перед коммитом: - выбросить экспортный `databases/` — наш `clickhouse.yaml` остается как есть, с постоянным uuid и без пароля; - оставить в `metadata.yaml` одну строку `version: 1.0.0`; - убрать из графиков `params.datasource`, `params.slice_id` и `params.dashboards` — это идентификаторы строк того Superset, где снимали, а импорт выводит связи из uuid; - убрать `query_context` — снимок запроса оттуда же. Импорт его не требует и обратно не заполняет: после переимпорта он остается пустым, на отрисовку это не влияет; - перезаписать русские строки читаемыми. Экспорт кодирует их `\uXXXX`-последовательностями, и после каждого круга их приходится раскодировать заново: имена графиков и подписи колонок читает менти, и в файле, и в диффе. Годится любой повторный дамп YAML с `allow_unicode=True`; - вернуть постоянные uuid из схемы выше. 4. Скопировать вычищенное дерево в `infra/superset/import/` и проверить импортом **на пустых метаданных** — иначе прогон проверит только обновление уже стоящих объектов, а молчаливый пропуск нового спрячется за старым. Активы Superset лежат в базе `superset` службы `postgres-metadata`, поэтому адресный сброс — эта база: ``` docker compose stop superset docker compose exec postgres-metadata sh -c \ 'psql -U "$POSTGRES_USER" -d postgres \ -c "DROP DATABASE superset WITH (FORCE)" \ -c "CREATE DATABASE superset OWNER \"$SUPERSET_METADATA_USER\""' docker compose run --rm superset-init docker compose up -d superset ``` Базу приходится создавать тут же: скрипт `infra/postgres/init/` заводит ее один раз, при первой инициализации тома Postgres. `make rebuild-storage` для этой проверки не годится — он гоняет `world_recreate` в ClickHouse и метаданных Superset не касается. `make clean` сбросит и их, но заодно снесет весь стенд, включая метаданные Airflow в той же службе. После импорта дашборд смотрят глазами: это единственное доказательство — см. ловушку ниже. ## Ловушки **Импорт молчит.** График, чей `dataset_uuid` не разрешился, отбрасывается без единого слова; дашборд, у которого не сошлись ссылки на графики, пропускается. Ошибки уходят в журнал, и `init.sh` завершается нулем в любом случае. Поэтому импорт проверяют глазами или запросом к API — но не тем, что команда отработала. **Знак рубля приходит не из формата валюты.** Символ Superset берет у `Intl.NumberFormat` по коду валюты и английской локали стенда: код `RUB` печатается как «RUB», а знак «₽» вызов роняет, и форматирование молча откатывается к числу без валюты. Поэтому единица стоит в названии панели — «Выручка, ₽», — а метрика несет обычный формат `,d`. **`label_colors` знает имя ряда, а не подпись.** Для разреза имя ряда — это значение колонки («Бытовая техника»), для графика без разреза — имя метрики (`known_share`), а не ее русская подпись. Цвет, закрепленный за подписью «Доля известных», не применяется. **`--overwrite` синхронизирует датасет разрушительно.** Колонки и метрики берутся из YAML целиком: добавленная в интерфейсе метрика или подпись колонки исчезнет при следующем старте контейнера. Правка датасета — это правка файла. ## Что проверяет стенд `make check-services` входит в Superset, находит подключение `ClickHouse`, сверяет его uuid и безопасный URI и требует схему `dm` — то есть подтверждает живое подключение и хранилище пароля. Сами графики и дашборд ни одна цель не проверяет: их смотрят глазами после переинициализации. Полная карта целей — в [доке проверок](testing.md). ## Отклоненные варианты Оживить дашборд предложат снова, поэтому отказы записаны с доводами. Замерено на живом стенде 7 сентября 2026 года, Superset 6.1. **Карта регионов России.** Замысел стоял на клике: щелчок по региону перестраивает весь экран. Плагин Country Map кросс-фильтр не отдает вовсе — `behaviors` не объявлены, `setDataMask` не вызывается, а щелчок приближает карту. Без клика остается картинка, и она рассказывает меньше, чем кажется: 16 городов стенда сворачиваются в 14 регионов из 83, и больше половины сессий приходится на один. Карта отдана факультативу «Добавь названия регионов в отчет»: там читатель собирает справочник сам, и она становится наградой за него. **Регион как общее измерение витрин.** Чтобы фильтр по региону действовал на весь экран, регион пришлось бы завести в каждой витрине: Superset датасеты между собой не соединяет, а фильтр по колонке, которой нет в датасете панели, отбрасывается с причиной `COL_NOT_IN_DATASOURCE`. У заказа географии нет — она бралась бы от человека через карту идентичностей и приписывалась бы сущности, у которой ее в источнике не было. Вдобавок не сходятся ритмы: витрина от карты идентичностей не замерзает никогда, а выручка живет окном последнего слепка ([дока DM](dm.md)). Стендовый словарь регионов отклонен раньше и по своей причине — он повторил бы урок `dictGet` каталога вторым разом. **Кросс-фильтрация между панелями.** Общих измерений у витрин почти нет: категория есть только у выручки, а день у сверки называется `order_day` против `report_date` у остальных. Клик перестроил бы часть экрана и оставил другую нетронутой, а вывод из этого читатель делает про данные, а не про устройство витрин. Панель фильтров этой болезнью не болеет — там колонка задается явно для каждой витрины, — поэтому фильтр периода дашборду разрешен, а кросс-фильтрация нет. **Вкладки и блоки «что смотреть».** Вкладки были условным вариантом: их заводят, когда экран перестает читаться одним взглядом. Четыре строки панелей читаются сразу, поэтому вкладки спрятали бы половину экрана за щелчком без выигрыша. Блок с текстом «что смотреть» рядом с панелью означал бы, что панель сама себя не объясняет: тогда чинить надо название и подписи, а не дописывать к ним пояснение. **Пороги и раскраска панелей.** Правило [спеки](../specs/2026-07-30-stand-v2-realism.md), раздел 9: на дашборд смотрят глазами, красное и зеленое живет в проверках. Поводы вернуться к разговору: плагин карты начинает отдавать кросс-фильтр; у заказа появляется адрес доставки; витрины получают общее измерение по своей причине, не ради дашборда.