"""Añade N cards como fila horizontal al final de una tab de dashboard.""" from __future__ import annotations from .client import MetabaseClient from .metabase_dashboard_next_row import metabase_dashboard_next_row from .metabase_copy_dashcard_mappings import metabase_copy_dashcard_mappings from .metabase_update_dashboard_safe import metabase_update_dashboard_safe def metabase_dashboard_append_row( client: MetabaseClient, *, dashboard_id: int, tab_id: int, card_ids: list[int], height: int = 4, donor_card_id: int = 0, grid_width: int = 24, ) -> dict: """Añade N cards como fila horizontal al final de una tab de dashboard. Calcula la primera fila libre de la tab indicada, distribuye las cards horizontalmente con ancho uniforme (``grid_width // len(card_ids)``) y opcionalmente copia los ``parameter_mappings`` de una card donante a cada nueva card. Internamente delega en ``metabase_dashboard_next_row``, ``metabase_copy_dashcard_mappings`` y ``metabase_update_dashboard_safe``. Args: client: MetabaseClient autenticado con sesion activa. dashboard_id: ID del dashboard donde se añaden las cards. tab_id: ID de la tab donde se insertan las cards. Usar ``0`` para dashboards sin tabs o para la tab raiz. card_ids: Lista de IDs de cards a colocar como fila. El orden de la lista determina el orden de izquierda a derecha. height: Altura en filas del grid para cada card. Default ``4``. donor_card_id: Si distinto de ``0``, copia los ``parameter_mappings`` de esa card (que debe existir ya en el dashboard) a cada nueva card. Util para replicar los 18 filtros de dashboard sin configurar manualmente cada mapping. Default ``0`` (sin mappings). grid_width: Anchura total del grid de Metabase. Default ``24`` (estandar de Metabase). Cada card recibe ``grid_width // len(card_ids)`` columnas. Returns: Dict con el resumen de la operacion devuelto por ``metabase_update_dashboard_safe``:: { "added": [lista de IDs negativos asignados], "updated": int, # dashcards existentes conservados "removed": int, # dashcards eliminados (0 en este caso) "response": dict, # respuesta raw de PUT /api/dashboard/:id } Raises: ValueError: Si ``card_ids`` esta vacio, o si ``donor_card_id != 0`` y esa card no existe en el dashboard. httpx.HTTPStatusError: Si la API de Metabase devuelve 4xx/5xx. Example: >>> # Añadir 4 KPIs al final de la tab 7, copiando filtros de card 42 >>> result = metabase_dashboard_append_row( ... client, ... dashboard_id=10, ... tab_id=7, ... card_ids=[101, 102, 103, 104], ... height=4, ... donor_card_id=42, ... ) >>> print(result["added"]) # [-1, -2, -3, -4] >>> # Sin filtros, 2 cards en dashboard sin tabs >>> result = metabase_dashboard_append_row( ... client, ... dashboard_id=10, ... tab_id=0, ... card_ids=[200, 201], ... height=6, ... ) """ if not card_ids: raise ValueError("card_ids no puede estar vacio") # 1. Calcular siguiente fila libre next_row = metabase_dashboard_next_row( client, dashboard_id=dashboard_id, tab_id=tab_id ) # 2. Ancho de cada card size_x = grid_width // len(card_ids) # 3. Construir dashcards new_dashcards: list[dict] = [] for i, cid in enumerate(card_ids): # Copiar mappings del donor si se especifico if donor_card_id != 0: mappings = metabase_copy_dashcard_mappings( client, dashboard_id=dashboard_id, source_card_id=donor_card_id, dest_card_id=cid, ) else: mappings = [] dashcard: dict = { "card_id": cid, "dashboard_tab_id": tab_id, "row": next_row, "col": i * size_x, "size_x": size_x, "size_y": height, "parameter_mappings": mappings, "visualization_settings": {}, } new_dashcards.append(dashcard) # 4. Actualizar el dashboard return metabase_update_dashboard_safe( client, dashboard_id, dashcards_add=new_dashcards, )