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:
@@ -3,17 +3,17 @@ name: metabase_create_document
|
||||
kind: function
|
||||
lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: impure
|
||||
signature: "def metabase_create_document(client: MetabaseClient, name: str, document: dict, collection_id: int = 0) -> dict"
|
||||
description: "Crea un document nuevo con contenido ProseMirror. Endpoint: POST /api/document. Soporta cardEmbed, smartLink, flexContainer, callout, taskList y demas nodos custom de Metabase."
|
||||
signature: "def metabase_create_document(client: MetabaseClient, name: str, document: dict, collection_id: int = 0, *, validate: bool = True) -> dict"
|
||||
description: "Crea un document con contenido ProseMirror. Valida el arbol contra la whitelist de nodos ANTES de enviar (evita documentos que la API acepta pero el frontend renderiza vacíos). Usar prosemirror_card_embed() para embeber cards."
|
||||
tags: [metabase, document, create, api, prosemirror, python]
|
||||
uses_functions: []
|
||||
uses_functions: [metabase_validate_document_payload_py_infra]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: [httpx]
|
||||
imports: [httpx, uuid]
|
||||
params:
|
||||
- name: client
|
||||
desc: "instancia autenticada de MetabaseClient"
|
||||
@@ -23,6 +23,8 @@ params:
|
||||
desc: "arbol ProseMirror JSON: {type: 'doc', content: [...]}, o '' para arrancar vacio"
|
||||
- name: collection_id
|
||||
desc: "ID de coleccion destino (0 = root)"
|
||||
- name: validate
|
||||
desc: "si True (default), valida el ProseMirror antes de enviar. Lanza ValueError si hay nodos no soportados"
|
||||
output: "dict: document recien creado con id, entity_id y metadata"
|
||||
tested: false
|
||||
tests: []
|
||||
@@ -33,17 +35,49 @@ file_path: "python/functions/metabase/documents.py"
|
||||
## Ejemplo
|
||||
|
||||
```python
|
||||
doc = metabase_create_document(client, "Notas", {
|
||||
from metabase.documents import metabase_create_document, prosemirror_card_embed
|
||||
|
||||
doc = metabase_create_document(client, "Reporte Q1", {
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{"type": "paragraph", "content": [{"type": "text", "text": "Hola"}]}
|
||||
{"type": "heading", "attrs": {"level": 1},
|
||||
"content": [{"type": "text", "text": "KPIs"}]},
|
||||
{"type": "paragraph",
|
||||
"content": [{"type": "text", "text": "Revenue por canal:"}]},
|
||||
prosemirror_card_embed(42, height=450),
|
||||
]
|
||||
})
|
||||
print(doc["id"])
|
||||
```
|
||||
|
||||
## Nodos ProseMirror — whitelist
|
||||
|
||||
**Renderizan correctamente** (TipTap v0.59):
|
||||
`doc, paragraph, text, heading, bulletList, orderedList, listItem, blockquote, codeBlock, horizontalRule, hardBreak, cardEmbed, flexContainer, smartLink, resizeNode, mention`
|
||||
|
||||
**La API acepta pero el frontend IGNORA** (resultado: documento vacío):
|
||||
`callout, taskList, taskItem, details, table, tableRow, tableCell, image, iframe`
|
||||
|
||||
**Marks que renderizan:** `bold, italic, strike, code, link`
|
||||
**Marks ignorados:** `underline, highlight, subscript, textStyle`
|
||||
|
||||
## cardEmbed — SIEMPRE envolver en resizeNode
|
||||
|
||||
Un `cardEmbed` desnudo renderiza pero queda con ~50px de alto. Metabase espera que vaya dentro de un `resizeNode`:
|
||||
|
||||
```python
|
||||
# MAL — card diminuta
|
||||
{"type": "cardEmbed", "attrs": {"id": 42}}
|
||||
|
||||
# BIEN — usar el helper
|
||||
from metabase.documents import prosemirror_card_embed
|
||||
prosemirror_card_embed(42, height=450)
|
||||
# Genera: {"type": "resizeNode", "attrs": {"height": 450, "minHeight": 280},
|
||||
# "content": [{"type": "cardEmbed", "attrs": {"id": 42, ...}}]}
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Nodos custom de Metabase observados (v0.59): `cardEmbed` (attrs.id=card_id), `smartLink` (attrs.entityId), `flexContainer` (attrs.columnWidths), `resizeNode`, `mention`. Marks estandar + `underline`, `highlight`, `subscript`, `textStyle`.
|
||||
|
||||
Cuando embebes un card via `cardEmbed`, Metabase crea una copia interna del card con `document_id` apuntando al document — no referencia el card original.
|
||||
- La validación (`validate=True`) llama internamente a `metabase_validate_document_payload`. Si detecta nodos no soportados, lanza `ValueError` ANTES de hacer el POST — evita documentos que se ven vacíos.
|
||||
- Pasar `validate=False` solo si se está experimentando con nodos nuevos.
|
||||
- Para destacar texto, usar `blockquote` (NO `callout`).
|
||||
- Cuando embebes un card via `cardEmbed`, Metabase crea una referencia al card — el card debe existir.
|
||||
|
||||
Reference in New Issue
Block a user