fix(superset): align bootstrap with clickhousedb uri
- Why: - Superset bootstrap used outdated ClickHouse URI format and did not fail fast on init errors. - docs and exported dashboard metadata diverged from runtime connection settings. - What: - build ClickHouse URI from env vars and use clickhousedb:// in init script. - refresh dataset metadata on existing datasets and surface import errors. - run create_dashboard during superset-init startup and align docs/exported URI references. - ignore node_modules in git. - Check: - python3 -m py_compile superset/init_superset.py - manual dashboard smoke check in UI (charts render)
This commit is contained in:
@@ -171,3 +171,4 @@ $RECYCLE.BIN/
|
|||||||
*.msm
|
*.msm
|
||||||
*.msp
|
*.msp
|
||||||
*.lnk
|
*.lnk
|
||||||
|
node_modules
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ curl http://localhost:8088/health # должно вернуть 200
|
|||||||
```
|
```
|
||||||
|
|
||||||
Что создаётся автоматически:
|
Что создаётся автоматически:
|
||||||
- **Подключение к ClickHouse**: `clickhouse_dwh` (URI: `clickhouse+connect://default@clickhouse:8123/default`)
|
- **Подключение к ClickHouse**: `clickhouse_dwh` (URI: `clickhousedb://default:123456@clickhouse:8123/default`)
|
||||||
- **Датасеты** (6 шт.): `v_events_enriched`, `v_daily_traffic`, `v_utm_effectiveness`, `v_top_pages_daily`, `v_session_overview`, `dq_summary`
|
- **Датасеты** (6 шт.): `v_events_enriched`, `v_daily_traffic`, `v_utm_effectiveness`, `v_top_pages_daily`, `v_session_overview`, `dq_summary`
|
||||||
- **Чарты** (10 шт.): KPI метрики, графики трафика, география, UTM-эффективность, качество данных
|
- **Чарты** (10 шт.): KPI метрики, графики трафика, география, UTM-эффективность, качество данных
|
||||||
- **Дашборд**: "🛒 E-commerce Analytics Dashboard"
|
- **Дашборд**: "🛒 E-commerce Analytics Dashboard"
|
||||||
|
|||||||
+3
-1
@@ -273,7 +273,9 @@ services:
|
|||||||
echo 'Initializing roles...' &&
|
echo 'Initializing roles...' &&
|
||||||
superset init &&
|
superset init &&
|
||||||
echo 'Creating ClickHouse connection and datasets...' &&
|
echo 'Creating ClickHouse connection and datasets...' &&
|
||||||
python /app/superset_init/init_superset.py || true &&
|
python /app/superset_init/init_superset.py &&
|
||||||
|
echo 'Creating dashboard and charts...' &&
|
||||||
|
python /app/superset_init/create_dashboard.py &&
|
||||||
echo 'Superset initialized successfully'
|
echo 'Superset initialized successfully'
|
||||||
"
|
"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -602,7 +602,7 @@ INSERT INTO dm.daily_traffic SELECT * FROM dm.v_daily_traffic;
|
|||||||
- URL: `clickhouse://default:123456@clickhouse:9000/default` (native TCP для Airflow plugin)
|
- URL: `clickhouse://default:123456@clickhouse:9000/default` (native TCP для Airflow plugin)
|
||||||
- Provider/интеграция: `airflow-clickhouse-plugin` (в `airflow/requirements.txt`), задачи выполняются через `ClickHouseOperator`.
|
- Provider/интеграция: `airflow-clickhouse-plugin` (в `airflow/requirements.txt`), задачи выполняются через `ClickHouseOperator`.
|
||||||
- Дополнительно: `kafka-python==2.0.6` для работы с Kafka из DAG.
|
- Дополнительно: `kafka-python==2.0.6` для работы с Kafka из DAG.
|
||||||
- Примечание: Superset подключается к ClickHouse по HTTP (обычно `clickhouse+connect://...:8123/...`).
|
- Примечание: Superset подключается к ClickHouse по HTTP (обычно `clickhousedb://...:8123/...`).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ make superset-dashboard
|
|||||||
# Основные
|
# Основные
|
||||||
make up # Запуск всех сервисов
|
make up # Запуск всех сервисов
|
||||||
make down # Остановка сервисов
|
make down # Остановка сервисов
|
||||||
make down-v # Остановка с удалением volumes
|
make clean # Остановка с удалением volumes
|
||||||
make logs service=superset # Логи сервиса
|
make logs service=superset # Логи сервиса
|
||||||
|
|
||||||
# ETL
|
# ETL
|
||||||
@@ -120,7 +120,7 @@ make superset-restart # Перезапуск сервиса
|
|||||||
3. Выберите **ClickHouse**
|
3. Выберите **ClickHouse**
|
||||||
4. Введите SQLAlchemy URI:
|
4. Введите SQLAlchemy URI:
|
||||||
```
|
```
|
||||||
clickhouse+native://default@clickhouse:9000/default
|
clickhousedb://default:123456@clickhouse:8123/default
|
||||||
```
|
```
|
||||||
5. Установите:
|
5. Установите:
|
||||||
- **Expose in SQL Lab:** ✅
|
- **Expose in SQL Lab:** ✅
|
||||||
|
|||||||
@@ -188,7 +188,7 @@
|
|||||||
{
|
{
|
||||||
"__Database__": {
|
"__Database__": {
|
||||||
"database_name": "clickhouse_dwh",
|
"database_name": "clickhouse_dwh",
|
||||||
"sqlalchemy_uri": "clickhouse+native://default@clickhouse:9000/default",
|
"sqlalchemy_uri": "clickhousedb://default:123456@clickhouse:8123/default",
|
||||||
"expose_in_sqllab": true,
|
"expose_in_sqllab": true,
|
||||||
"allow_ctas": false,
|
"allow_ctas": false,
|
||||||
"allow_cvas": false,
|
"allow_cvas": false,
|
||||||
|
|||||||
+62
-12
@@ -24,12 +24,11 @@
|
|||||||
================================================================================
|
================================================================================
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
import json
|
import os
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -41,6 +40,22 @@ logger = logging.getLogger(__name__)
|
|||||||
# Добавляем путь к superset
|
# Добавляем путь к superset
|
||||||
sys.path.insert(0, '/app')
|
sys.path.insert(0, '/app')
|
||||||
|
|
||||||
|
CLICKHOUSE_USER = os.getenv('CLICKHOUSE_USER', 'default')
|
||||||
|
CLICKHOUSE_PASSWORD = os.getenv('CLICKHOUSE_PASSWORD', '123456')
|
||||||
|
CLICKHOUSE_HOST = os.getenv('CLICKHOUSE_HOST', 'clickhouse')
|
||||||
|
CLICKHOUSE_PORT = os.getenv('CLICKHOUSE_HTTP_PORT', '8123')
|
||||||
|
CLICKHOUSE_DATABASE = os.getenv('CLICKHOUSE_DATABASE', 'default')
|
||||||
|
|
||||||
|
|
||||||
|
def build_clickhouse_uri() -> str:
|
||||||
|
"""Собирает URI подключения к ClickHouse для Superset."""
|
||||||
|
user = quote_plus(CLICKHOUSE_USER)
|
||||||
|
password = quote_plus(CLICKHOUSE_PASSWORD)
|
||||||
|
host = CLICKHOUSE_HOST
|
||||||
|
port = CLICKHOUSE_PORT
|
||||||
|
database = quote_plus(CLICKHOUSE_DATABASE)
|
||||||
|
return f"clickhousedb://{user}:{password}@{host}:{port}/{database}"
|
||||||
|
|
||||||
|
|
||||||
def run_superset_cli(args):
|
def run_superset_cli(args):
|
||||||
"""Запуск команды superset CLI"""
|
"""Запуск команды superset CLI"""
|
||||||
@@ -57,13 +72,14 @@ def run_superset_cli(args):
|
|||||||
def create_clickhouse_connection():
|
def create_clickhouse_connection():
|
||||||
"""Создание подключения к ClickHouse через CLI"""
|
"""Создание подключения к ClickHouse через CLI"""
|
||||||
logger.info("Creating ClickHouse database connection...")
|
logger.info("Creating ClickHouse database connection...")
|
||||||
|
database_uri = build_clickhouse_uri()
|
||||||
|
|
||||||
# Создаем подключение через set-database-uri
|
# Создаем подключение через set-database-uri
|
||||||
# clickhouse-connect использует HTTP порт 8123 внутри Docker сети
|
# clickhouse-connect использует HTTP порт 8123 внутри Docker сети
|
||||||
success, stdout, stderr = run_superset_cli([
|
success, stdout, stderr = run_superset_cli([
|
||||||
'set-database-uri',
|
'set-database-uri',
|
||||||
'-d', 'clickhouse_dwh',
|
'-d', 'clickhouse_dwh',
|
||||||
'-u', 'clickhouse+connect://default@clickhouse:8123/default'
|
'-u', database_uri
|
||||||
])
|
])
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
@@ -74,7 +90,7 @@ def create_clickhouse_connection():
|
|||||||
logger.info("Please create manually via UI:")
|
logger.info("Please create manually via UI:")
|
||||||
logger.info("1. Go to: Settings → Database Connections → + Database")
|
logger.info("1. Go to: Settings → Database Connections → + Database")
|
||||||
logger.info("2. Select: ClickHouse")
|
logger.info("2. Select: ClickHouse")
|
||||||
logger.info("3. URI: clickhouse+connect://default@clickhouse:8123/default")
|
logger.info(f"3. URI: {database_uri}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@@ -136,31 +152,65 @@ def import_datasets():
|
|||||||
"print(f'Found database: {database.database_name} (id={database.id})')",
|
"print(f'Found database: {database.database_name} (id={database.id})')",
|
||||||
"",
|
"",
|
||||||
"imported = 0",
|
"imported = 0",
|
||||||
|
"refreshed = 0",
|
||||||
|
"errors = 0",
|
||||||
]
|
]
|
||||||
|
|
||||||
for ds in datasets:
|
for ds in datasets:
|
||||||
|
table_name = ds["table_name"]
|
||||||
|
schema_name = ds["schema"]
|
||||||
|
description = ds["description"]
|
||||||
script_lines.extend([
|
script_lines.extend([
|
||||||
"",
|
"",
|
||||||
f"# Dataset: {ds['table_name']}",
|
f"# Dataset: {table_name}",
|
||||||
f"existing = db.session.query(SqlaTable).filter_by(table_name='{ds['table_name']}', schema='{ds['schema']}').first()",
|
(
|
||||||
|
"existing = db.session.query(SqlaTable).filter_by("
|
||||||
|
f"table_name={table_name!r}, schema={schema_name!r}"
|
||||||
|
").first()"
|
||||||
|
),
|
||||||
"if existing:",
|
"if existing:",
|
||||||
f" print(f'Dataset {ds['table_name']} already exists')",
|
" try:",
|
||||||
|
" if not existing.columns:",
|
||||||
|
" existing.fetch_metadata()",
|
||||||
|
" db.session.commit()",
|
||||||
|
f" print('Refreshed dataset metadata: {table_name}')",
|
||||||
|
" refreshed += 1",
|
||||||
|
" else:",
|
||||||
|
f" print('Dataset {table_name} already exists')",
|
||||||
|
" except Exception as e:",
|
||||||
|
f" print(f'ERROR: failed to refresh {table_name}: {{e}}')",
|
||||||
|
" db.session.rollback()",
|
||||||
|
" errors += 1",
|
||||||
"else:",
|
"else:",
|
||||||
" try:",
|
" try:",
|
||||||
f" dataset = SqlaTable(table_name='{ds['table_name']}', schema='{ds['schema']}', database_id=database.id, database=database, description='{ds['description']}')",
|
(
|
||||||
|
" dataset = SqlaTable("
|
||||||
|
f"table_name={table_name!r}, "
|
||||||
|
f"schema={schema_name!r}, "
|
||||||
|
"database_id=database.id, "
|
||||||
|
"database=database, "
|
||||||
|
f"description={description!r}"
|
||||||
|
")"
|
||||||
|
),
|
||||||
" db.session.add(dataset)",
|
" db.session.add(dataset)",
|
||||||
" db.session.flush()",
|
" db.session.flush()",
|
||||||
f" print(f'Created dataset: {ds['table_name']}')",
|
" dataset.fetch_metadata()",
|
||||||
|
" db.session.commit()",
|
||||||
|
f" print('Created dataset: {table_name}')",
|
||||||
" imported += 1",
|
" imported += 1",
|
||||||
" except Exception as e:",
|
" except Exception as e:",
|
||||||
f" print(f'Error creating {ds['table_name']}: {{e}}')",
|
f" print(f'ERROR: failed to create {table_name}: {{e}}')",
|
||||||
" db.session.rollback()",
|
" db.session.rollback()",
|
||||||
|
" errors += 1",
|
||||||
])
|
])
|
||||||
|
|
||||||
script_lines.extend([
|
script_lines.extend([
|
||||||
"",
|
"",
|
||||||
"db.session.commit()",
|
|
||||||
"print(f'Successfully imported {imported} datasets')",
|
"print(f'Successfully imported {imported} datasets')",
|
||||||
|
"print(f'Refreshed metadata for {refreshed} datasets')",
|
||||||
|
"print(f'Errors: {errors}')",
|
||||||
|
"if errors > 0:",
|
||||||
|
" raise SystemExit(1)",
|
||||||
])
|
])
|
||||||
|
|
||||||
script_content = '\n'.join(script_lines)
|
script_content = '\n'.join(script_lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user