feat(metabase): auto-commit con 17 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,177 @@ metabase_update_dashboard(client, dash["id"], dashcards=[
|
||||
|
||||
**Filtros de list_dashboards:** `all`, `mine`, `archived`
|
||||
|
||||
### Dashboards — helpers compositivos (añadir KPIs a dashboard existente)
|
||||
|
||||
Helpers para el flujo tipico "anadir N cards (KPI) al final de un tab existente reusando los mismos filtros que otro card vecino". Evitan los gotchas: replicar `parameter_mappings`, calcular `row` libre, escapado raro de `column_settings`, generacion de `lib/uuid` en MBQL.
|
||||
|
||||
```python
|
||||
from metabase import (
|
||||
metabase_mbql_from_source_card,
|
||||
metabase_copy_dashcard_mappings,
|
||||
metabase_dashboard_next_row,
|
||||
metabase_dashboard_append_row,
|
||||
metabase_viz_column_format,
|
||||
metabase_smartscalar_anothercolumn_viz,
|
||||
)
|
||||
```
|
||||
|
||||
#### `metabase_mbql_from_source_card`
|
||||
|
||||
Construye `dataset_query` MBQL sobre una saved-card (`source-card`), con aggregations + joins + filters + breakouts + segunda stage de expressions. Genera `lib/uuid` automatico en cada nodo.
|
||||
|
||||
```python
|
||||
dq = metabase_mbql_from_source_card(
|
||||
database_id=6,
|
||||
source_card_id=5305,
|
||||
aggregations=[
|
||||
{"op": "sum", "field": "PrecioVenta", "base_type": "type/Decimal"},
|
||||
{"op": "sum", "field": "PrecioCompra", "base_type": "type/Decimal"},
|
||||
{"op": "sum", "field": "PrecioTasas", "base_type": "type/Float"},
|
||||
],
|
||||
joins=[
|
||||
{"alias": "Centros - idCentro", "source_card_id": 4076,
|
||||
"fields": "none", "local_field": "idCentro", "local_base_type": "type/Text",
|
||||
"foreign_field_id": 17316, "foreign_base_type": "type/Text"},
|
||||
],
|
||||
filters=[["not-empty", {}, ["field", {"base-type": "type/Text"},
|
||||
"Centros - idCentro__Companies__name"]]],
|
||||
expressions=[
|
||||
{"name": "MasadeMargen", "expr":
|
||||
{"op": "-", "args": [{"field": "sum"},
|
||||
{"op": "+", "args": [{"field": "sum_2"}, {"field": "sum_3", "base_type": "type/Float"}]}]}},
|
||||
{"name": "Margen", "expr":
|
||||
{"op": "coalesce", "args": [
|
||||
{"op": "/", "args": [
|
||||
{"op": "-", "args": [{"field": "sum"},
|
||||
{"op": "+", "args": [{"field": "sum_2"}, {"field": "sum_3", "base_type": "type/Float"}]}]},
|
||||
{"field": "sum"}]},
|
||||
0]}},
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
Ops soportadas en expressions: `+`, `-`, `*`, `/`, `coalesce`, `case`. Referencia a otra expresion en la misma stage: `{"ref": "Margen"}`. Aliases de aggregations son posicionales: `sum`, `sum_2`, `sum_3`... (orden = declaracion).
|
||||
|
||||
#### `metabase_copy_dashcard_mappings`
|
||||
|
||||
Copia los `parameter_mappings` de un dashcard "donante" a un card nuevo. Devuelve lista lista para pegar en `dashcards_add`.
|
||||
|
||||
```python
|
||||
mappings = metabase_copy_dashcard_mappings(
|
||||
client,
|
||||
dashboard_id=734,
|
||||
source_card_id=9918, # card donante con 18 filtros mapeados
|
||||
dest_card_id=9947, # card destino nueva
|
||||
)
|
||||
# Devuelve [{"parameter_id","card_id","target"}, ...] con card_id=9947
|
||||
```
|
||||
|
||||
#### `metabase_dashboard_next_row`
|
||||
|
||||
Calcula el primer `row` libre al final de un tab.
|
||||
|
||||
```python
|
||||
row = metabase_dashboard_next_row(client, dashboard_id=734, tab_id=191)
|
||||
# row=12 si el ultimo card termina en row+size_y=12
|
||||
# tab_id=0 → dashboards sin tabs
|
||||
```
|
||||
|
||||
#### `metabase_dashboard_append_row`
|
||||
|
||||
Combo: append N cards en una fila horizontal al final del tab, copiando mappings de un donante. Una sola llamada hace `next_row` + grid math + `copy_mappings` + `update_dashboard_safe`.
|
||||
|
||||
```python
|
||||
metabase_dashboard_append_row(
|
||||
client,
|
||||
dashboard_id=734,
|
||||
tab_id=191,
|
||||
card_ids=[9947, 9948, 9949],
|
||||
height=4,
|
||||
donor_card_id=9918, # mismos 18 filtros del dashboard
|
||||
grid_width=24, # default Metabase v0.59
|
||||
)
|
||||
# Coloca 3 cards de size_x=8 en row=next, cols 0/8/16, con mappings copiados
|
||||
```
|
||||
|
||||
#### `metabase_viz_column_format`
|
||||
|
||||
Construye una entrada de `column_settings` con la clave JSON-escaped (`'["name","Margen"]'`) sin tener que recordar el formato exacto.
|
||||
|
||||
```python
|
||||
metabase_viz_column_format("Margen", number_style="percent", decimals=2)
|
||||
# {'["name","Margen"]': {"number_style": "percent", "decimals": 2}}
|
||||
|
||||
metabase_viz_column_format("MasadeMargen", number_style="currency",
|
||||
currency="EUR", decimals=0, currency_in_header=False)
|
||||
# {'["name","MasadeMargen"]': {...}}
|
||||
```
|
||||
|
||||
Mergea varios resultados en `column_settings` de las visualization_settings.
|
||||
|
||||
#### `metabase_smartscalar_anothercolumn_viz`
|
||||
|
||||
Construye `visualization_settings` completo para `display=smartscalar` con comparativa tipo `anotherColumn` (compara dos columnas de la misma fila — no requiere breakout temporal).
|
||||
|
||||
```python
|
||||
viz = metabase_smartscalar_anothercolumn_viz(
|
||||
main_column="Margen",
|
||||
compare_column="Margen_N1",
|
||||
label="vs N-1",
|
||||
number_style="percent",
|
||||
decimals=2,
|
||||
)
|
||||
# Setear en /api/card via PUT visualization_settings=viz
|
||||
```
|
||||
|
||||
**⚠ Gotcha smartscalar Metabase v0.59:** el visualization solo acepta `type: "anotherColumn"` cuando la query NO produce filas multiples. Si Metabase muestra el error *"Agrupa solo por un campo de tiempo para ver como ha cambiado con el tiempo"*, hace falta un **breakout temporal** en la MBQL (ej. `breakouts=[{"field":"fecha","base_type":"type/Date","temporal_unit":"month"}]`) y usar el comparison `previousValue` en lugar de `anotherColumn`. Alternativa: `metabase_smartscalar_kpi_sql` + `metabase_smartscalar_kpi_payload` (patron 2-row nativo) si la card es SQL nativo.
|
||||
|
||||
#### Patron canonico — anadir 3 KPI cards a tab existente
|
||||
|
||||
```python
|
||||
import os, sys
|
||||
sys.path.insert(0, "python/functions")
|
||||
from metabase import (
|
||||
MetabaseClient, metabase_create_card, metabase_mbql_from_source_card,
|
||||
metabase_dashboard_append_row, metabase_viz_column_format,
|
||||
metabase_smartscalar_anothercolumn_viz,
|
||||
)
|
||||
|
||||
c = MetabaseClient("https://reports.autingo.es", os.environ["MB_API_KEY"])
|
||||
|
||||
# 1) MBQL reusando una saved-card como source
|
||||
def query():
|
||||
return metabase_mbql_from_source_card(
|
||||
database_id=6, source_card_id=5305,
|
||||
aggregations=[
|
||||
{"op":"sum","field":"PrecioVenta","base_type":"type/Decimal"},
|
||||
{"op":"sum","field":"PrecioCompra","base_type":"type/Decimal"},
|
||||
{"op":"sum","field":"PrecioTasas","base_type":"type/Float"},
|
||||
],
|
||||
# joins/filters/expressions ...
|
||||
)
|
||||
|
||||
# 2) Crear cards
|
||||
card1 = metabase_create_card(c, "Masa de Margen", query(),
|
||||
display="scalar", collection_id=500)
|
||||
viz1 = {"scalar.field": "MasadeMargen",
|
||||
"column_settings": metabase_viz_column_format(
|
||||
"MasadeMargen", number_style="currency", currency="EUR", decimals=0)}
|
||||
c._http.request("PUT", f"/api/card/{card1['id']}", json={"visualization_settings": viz1})
|
||||
|
||||
card2 = metabase_create_card(c, "Margen", query(), display="smartscalar", collection_id=500)
|
||||
viz2 = metabase_smartscalar_anothercolumn_viz(
|
||||
main_column="Margen", compare_column="Margen_N1", number_style="percent", decimals=2)
|
||||
c._http.request("PUT", f"/api/card/{card2['id']}", json={"visualization_settings": viz2})
|
||||
|
||||
# 3) Append fila al tab con mappings copiados del donante
|
||||
metabase_dashboard_append_row(
|
||||
c, dashboard_id=734, tab_id=191,
|
||||
card_ids=[card1["id"], card2["id"]],
|
||||
height=4, donor_card_id=9918,
|
||||
)
|
||||
```
|
||||
|
||||
### Documents (ProseMirror)
|
||||
|
||||
Los "documents" son páginas narrativas editables con texto rico y cards embebidas. **No hay helpers en fn_registry todavía** — usa el endpoint REST directamente a través de `client._http`.
|
||||
|
||||
Reference in New Issue
Block a user