feat(metabase): expansion cards y documents — export, model, ProseMirror validation, copy nativo

Cards: export_card (CSV/XLSX/JSON), create_model (type=model para fuentes MBQL).
Documents: prosemirror_card_embed helper (resizeNode envolviendo cardEmbed),
validacion automatica contra whitelist TipTap antes de enviar, copy_document
refactorizado al endpoint nativo POST /api/document/:id/copy.
Docs: dataset_query legacy vs MBQL5, template-tags, whitelist de nodos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 19:03:19 +02:00
parent cb392a48ee
commit cf70385bca
9 changed files with 516 additions and 71 deletions
+80
View File
@@ -354,3 +354,83 @@ def metabase_create_card_raw(client: MetabaseClient, payload: dict) -> dict:
>>> print(card["id"]) # ID asignado por Metabase
"""
return client.request("POST", "/api/card", json=payload)
def metabase_export_card(
client: MetabaseClient,
card_id: int,
format: str = "csv",
) -> bytes:
"""Exporta los resultados de una card en CSV, XLSX o JSON.
Endpoint: POST /api/card/:id/query/:format.
Args:
client: Cliente autenticado.
card_id: ID de la card.
format: Formato de exportación: "csv", "xlsx" o "json".
Returns:
bytes con el contenido del archivo exportado.
Example:
>>> data = metabase_export_card(client, 42, format="csv")
>>> with open("export.csv", "wb") as f:
... f.write(data)
"""
resp = client._http.request("POST", f"/api/card/{card_id}/query/{format}")
resp.raise_for_status()
return resp.content
def metabase_create_model(
client: MetabaseClient,
name: str,
sql: str,
database_id: int,
collection_id: int = 0,
description: str = "",
) -> dict:
"""Crea un modelo (card tipo model) que otras cards pueden referenciar.
Un modelo es una card con type="model". Otras cards MBQL pueden usarlo
como fuente con source-table: "card__<model_id>".
Endpoint: POST /api/card con type="model".
Args:
client: Cliente autenticado.
name: Nombre del modelo.
sql: Query SQL del modelo.
database_id: ID de la database en Metabase.
collection_id: Coleccion destino. 0 = root.
description: Descripcion opcional.
Returns:
Dict con el modelo creado (id, name, type="model").
Example:
>>> model = metabase_create_model(client, "supply_orders_base",
... "SELECT * FROM supply_orders WHERE ...", database_id=6)
>>> # Usar en otra card MBQL:
>>> card = metabase_create_card(client, "Revenue", {
... "database": 6, "type": "query",
... "query": {"source-table": f"card__{model['id']}"}
... })
"""
body: dict = {
"name": name,
"type": "model",
"dataset_query": {
"database": database_id,
"type": "native",
"native": {"query": sql},
},
"display": "table",
"visualization_settings": {},
}
if collection_id > 0:
body["collection_id"] = collection_id
if description:
body["description"] = description
return client.request("POST", "/api/card", json=body)