feat(auto_metabase): push-all + describe/sql + auto-inject de dashcards
- push_all(): pushea todos los YAMLs de un proyecto (cards primero,
dashboards despues), solo CREATE/UPDATE, resiliente a fallos por item
- explore.py: comandos describe (schema de DB) y sql (query ad-hoc con
limite, cap 5MB, bloqueo de escrituras destructivas)
- payload.py: auto-inyecta id:-N, visualization_settings:{} y
parameter_mappings:[] en dashcards nuevas para evitar 500 en push
- test_local: 11 cards + 3 dashboards sobre Sample Database de Metabase
- registry.db regenerado con auto_metabase_py_analytics indexada
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
"""Crea database + cards + dashboard de prueba en Metabase para validar pull/push.
|
||||
|
||||
Usa la propia Postgres interna de Metabase (auto_metabase_test-postgres) como
|
||||
database de prueba, ya que es accesible desde el container metabase via la
|
||||
red docker compartida.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
APP_DIR = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(APP_DIR.parent.parent / "python" / "functions"))
|
||||
sys.path.insert(0, str(APP_DIR))
|
||||
|
||||
from main import get_client # noqa: E402
|
||||
from metabase.databases import metabase_add_database, metabase_list_databases # noqa: E402
|
||||
from metabase.cards import metabase_create_card, metabase_list_cards # noqa: E402
|
||||
from metabase.dashboards import ( # noqa: E402
|
||||
metabase_create_dashboard,
|
||||
metabase_list_dashboards,
|
||||
metabase_update_dashboard,
|
||||
)
|
||||
|
||||
|
||||
def find_or_create_database(client) -> int:
|
||||
dbs = metabase_list_databases(client)
|
||||
# list_databases puede retornar un dict con 'data' o una lista directa
|
||||
items = dbs["data"] if isinstance(dbs, dict) and "data" in dbs else dbs
|
||||
for db in items:
|
||||
if db.get("name") == "metabase_internal_pg":
|
||||
print(f" database existente id={db['id']}")
|
||||
return db["id"]
|
||||
db = metabase_add_database(
|
||||
client,
|
||||
name="metabase_internal_pg",
|
||||
engine="postgres",
|
||||
details={
|
||||
"host": "auto_metabase_test-postgres",
|
||||
"port": 5432,
|
||||
"dbname": "metabase",
|
||||
"user": "metabase",
|
||||
"password": "metabase",
|
||||
"ssl": False,
|
||||
},
|
||||
)
|
||||
print(f" database creada id={db['id']}")
|
||||
return db["id"]
|
||||
|
||||
|
||||
def find_or_create_card(client, name: str, db_id: int, sql: str, display: str = "table") -> int:
|
||||
cards = metabase_list_cards(client)
|
||||
for c in cards:
|
||||
if c.get("name") == name:
|
||||
print(f" card '{name}' existente id={c['id']}")
|
||||
return c["id"]
|
||||
card = metabase_create_card(
|
||||
client,
|
||||
name=name,
|
||||
dataset_query={
|
||||
"type": "native",
|
||||
"native": {"query": sql},
|
||||
"database": db_id,
|
||||
},
|
||||
display=display,
|
||||
)
|
||||
print(f" card '{name}' creada id={card['id']}")
|
||||
return card["id"]
|
||||
|
||||
|
||||
def find_or_create_dashboard(client, name: str) -> int:
|
||||
dashes = metabase_list_dashboards(client)
|
||||
for d in dashes:
|
||||
if d.get("name") == name:
|
||||
print(f" dashboard '{name}' existente id={d['id']}")
|
||||
return d["id"]
|
||||
d = metabase_create_dashboard(client, name=name, description="Dashboard de prueba para auto_metabase")
|
||||
print(f" dashboard '{name}' creado id={d['id']}")
|
||||
return d["id"]
|
||||
|
||||
|
||||
def main():
|
||||
client = get_client()
|
||||
print("Seeding test data en Metabase...")
|
||||
|
||||
print("\n[1] Database")
|
||||
db_id = find_or_create_database(client)
|
||||
|
||||
print("\n[2] Cards")
|
||||
c1 = find_or_create_card(
|
||||
client, "test_count_users", db_id,
|
||||
"SELECT COUNT(*) AS users FROM core_user", "scalar",
|
||||
)
|
||||
c2 = find_or_create_card(
|
||||
client, "test_users_by_locale", db_id,
|
||||
"SELECT COALESCE(locale, 'unknown') AS locale, COUNT(*) AS n FROM core_user GROUP BY locale ORDER BY n DESC",
|
||||
"bar",
|
||||
)
|
||||
|
||||
print("\n[3] Dashboard con cards")
|
||||
dash_id = find_or_create_dashboard(client, "auto_metabase test dashboard")
|
||||
|
||||
# Re-fetch dashboard para ver estado actual
|
||||
from metabase.dashboards import metabase_get_dashboard
|
||||
dash = metabase_get_dashboard(client, dash_id)
|
||||
existing_card_ids = {dc.get("card_id") for dc in dash.get("dashcards", [])}
|
||||
|
||||
if c1 in existing_card_ids and c2 in existing_card_ids:
|
||||
print(f" dashboard ya tiene las {len(dash.get('dashcards', []))} dashcards esperadas")
|
||||
else:
|
||||
# Construir dashcards: id negativo => nueva
|
||||
new_dashcards = [
|
||||
{"id": -1, "card_id": c1, "row": 0, "col": 0, "size_x": 6, "size_y": 4},
|
||||
{"id": -2, "card_id": c2, "row": 0, "col": 6, "size_x": 6, "size_y": 4},
|
||||
]
|
||||
metabase_update_dashboard(client, dash_id, dashcards=new_dashcards)
|
||||
print(f" dashcards añadidas: {len(new_dashcards)}")
|
||||
|
||||
print(f"\nListo. Abre http://localhost:3000/dashboard/{dash_id}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user