"""CRUD de dashboards de Metabase.""" from .client import MetabaseClient def metabase_list_dashboards( client: MetabaseClient, filter: str = "", ) -> list[dict]: """Lista dashboards de Metabase con filtro opcional. Endpoint: GET /api/dashboard. Retorna dashboards resumidos (sin dashcards). Args: client: Cliente autenticado. filter: "all", "mine" o "archived". Vacio = todas. Returns: Lista de dicts con: id, name, description, collection_id, creator_id, archived, created_at. Example: >>> dashboards = metabase_list_dashboards(client, filter="mine") >>> for d in dashboards: ... print(d["id"], d["name"]) """ params = {} if filter: params["f"] = filter return client.request("GET", "/api/dashboard", params=params) def metabase_get_dashboard(client: MetabaseClient, dashboard_id: int) -> dict: """Obtiene un dashboard completo incluyendo sus cards. Endpoint: GET /api/dashboard/:id. Args: client: Cliente autenticado. dashboard_id: ID del dashboard. Returns: Dict con: id, name, description, dashcards (lista de cards posicionadas), parameters (filtros), tabs, collection_id, archived. Cada dashcard tiene: id, card_id, card (objeto completo), size_x, size_y, col, row, dashboard_tab_id, parameter_mappings, visualization_settings. Example: >>> dash = metabase_get_dashboard(client, 1) >>> for dc in dash["dashcards"]: ... print(f"Card {dc['card_id']} at ({dc['col']}, {dc['row']})") """ return client.request("GET", f"/api/dashboard/{dashboard_id}") def metabase_create_dashboard( client: MetabaseClient, name: str, description: str = "", collection_id: int = 0, ) -> dict: """Crea un nuevo dashboard vacio en Metabase. Endpoint: POST /api/dashboard. Para agregar cards usar metabase_update_dashboard con dashcards. Args: client: Cliente autenticado. name: Nombre del dashboard. description: Descripcion opcional. collection_id: Coleccion destino. 0 = root. Returns: Dict con el dashboard creado. Example: >>> dash = metabase_create_dashboard(client, "Sales Overview", "KPIs de ventas") >>> # Agregar cards: >>> metabase_update_dashboard(client, dash["id"], dashcards=[ ... {"id": -1, "card_id": 42, "size_x": 6, "size_y": 4, "col": 0, "row": 0}, ... ]) """ body: dict = {"name": name} if description: body["description"] = description if collection_id > 0: body["collection_id"] = collection_id return client.request("POST", "/api/dashboard", json=body) def metabase_update_dashboard(client: MetabaseClient, dashboard_id: int, **fields) -> dict: """Actualiza un dashboard incluyendo metadata, cards y tabs. Endpoint: PUT /api/dashboard/:id. El campo dashcards representa el ESTADO COMPLETO DESEADO del dashboard: - Agregar card: incluirla con ID negativo (-1, -2, etc.) - Actualizar card existente: incluirla con su ID positivo - Eliminar card: omitirla del array Args: client: Cliente autenticado. dashboard_id: ID del dashboard. **fields: Campos a actualizar. Validos: name (str), description (str), archived (bool), dashcards (list[dict]), tabs (list[dict]), parameters (list[dict]), collection_id (int). Returns: Dict con el dashboard actualizado. Example: >>> # Cambiar nombre >>> metabase_update_dashboard(client, 1, name="Updated Name") >>> >>> # Agregar card (primero obtener existentes) >>> dash = metabase_get_dashboard(client, 1) >>> cards = list(dash["dashcards"]) >>> cards.append({"id": -1, "card_id": 55, "size_x": 6, "size_y": 4, "col": 0, "row": 0}) >>> metabase_update_dashboard(client, 1, dashcards=cards) >>> >>> # Archivar (soft-delete) >>> metabase_update_dashboard(client, 1, archived=True) """ return client.request("PUT", f"/api/dashboard/{dashboard_id}", json=fields) def metabase_delete_dashboard(client: MetabaseClient, dashboard_id: int) -> None: """Elimina permanentemente un dashboard. Endpoint: DELETE /api/dashboard/:id. IRREVERSIBLE. Para soft-delete preferir: metabase_update_dashboard(client, id, archived=True) Args: client: Cliente autenticado. dashboard_id: ID del dashboard a eliminar. Example: >>> metabase_delete_dashboard(client, 1) >>> # Preferir: metabase_update_dashboard(client, 1, archived=True) """ client.request("DELETE", f"/api/dashboard/{dashboard_id}") def metabase_copy_dashboard( client: MetabaseClient, dashboard_id: int, name: str | None = None, collection_id: int | None = None, description: str | None = None, is_deep_copy: bool = False, ) -> dict: """Crea una copia de un dashboard existente en Metabase. Endpoint: POST /api/dashboard/:id/copy. Usa el endpoint nativo de Metabase para duplicar el dashboard junto con su layout de dashcards y parametros. Con is_deep_copy=True tambien clona las cards referenciadas. Args: client: Cliente autenticado. dashboard_id: ID del dashboard a copiar. name: Nombre para la copia. None = Metabase asigna "Copy of ". collection_id: Coleccion destino. None = misma coleccion que el original. description: Descripcion de la copia. None = misma que el original. is_deep_copy: Si True, clona tambien todas las cards referenciadas por el dashboard (deep copy). Si False, la copia referencia las cards originales. Returns: Dict con el dashboard nuevo creado por Metabase. Incluye el campo `id` asignado a la copia y el layout de dashcards copiado. Example: >>> copy = metabase_copy_dashboard(client, 1) >>> print(copy["id"], copy["name"]) # "Copy of ..." >>> # Deep copy a otra coleccion: >>> copy = metabase_copy_dashboard(client, 1, name="Sales Q2", collection_id=7, is_deep_copy=True) """ body: dict = {"is_deep_copy": is_deep_copy} if name is not None: body["name"] = name if collection_id is not None: body["collection_id"] = collection_id if description is not None: body["description"] = description return client.request("POST", f"/api/dashboard/{dashboard_id}/copy", json=body) def metabase_move_dashboard( client: MetabaseClient, dashboard_id: int, collection_id: int | None, ) -> dict: """Mueve un dashboard a otra coleccion. Wrapper thin sobre PUT /api/dashboard/:id que solo actualiza collection_id. Equivalente a metabase_update_dashboard(client, id, collection_id=...) pero con intencion explicita y soporte para mover a root con None. Endpoint: PUT /api/dashboard/:id. Args: client: Cliente autenticado. dashboard_id: ID del dashboard a mover. collection_id: ID de la coleccion destino. None mueve a "Our analytics" (root). Returns: Dict con el dashboard actualizado, incluyendo el nuevo collection_id. Example: >>> dash = metabase_move_dashboard(client, 1, collection_id=7) >>> print(dash["collection_id"]) # 7 >>> # Mover a root: >>> dash = metabase_move_dashboard(client, 1, collection_id=None) """ return client.request("PUT", f"/api/dashboard/{dashboard_id}", json={"collection_id": collection_id}) def metabase_create_dashboard_raw(client: MetabaseClient, payload: dict) -> dict: """Crea un dashboard en Metabase con payload completo ya construido por el caller. Version raw de metabase_create_dashboard. El caller es responsable de construir el payload completo — no se realiza validacion ni transformacion local. Util para flujos "Metabase as code" donde el YAML define todos los campos del dashboard tal como los espera la API. COMPORTAMIENTO EN DOS PASOS (limitacion de la API de Metabase): El endpoint POST /api/dashboard NO acepta `dashcards` en el body inicial; solo crea el dashboard vacio. Para añadir cards es necesario un PUT posterior. Esta funcion maneja esa limitacion automaticamente: 1. Si el payload contiene `dashcards`, se extraen antes del POST. 2. POST /api/dashboard crea el dashboard vacio (sin dashcards). 3. Si habia dashcards, PUT /api/dashboard/:id con {"dashcards": [...]} las añade al dashboard recien creado. 4. Retorna la respuesta del PUT (con dashcards pobladas), o la del POST si el payload original no contenia dashcards. Endpoint inicial: POST /api/dashboard. Endpoint secundario (condicional): PUT /api/dashboard/:id. El payload puede incluir: - name (str, requerido): nombre del dashboard. - description (str): descripcion. - collection_id (int): ID de coleccion destino. - parameters (list[dict]): filtros/parametros del dashboard. - dashcards (list[dict]): cards posicionadas. Cada dashcard necesita: id (int negativo para nuevas, ej: -1, -2), card_id (int), size_x (int), size_y (int), col (int), row (int). Campos opcionales: visualization_settings, parameter_mappings. - tabs (list[dict]): pestañas del dashboard. - enable_embedding (bool): habilitar embedding publico. - embedding_params (dict): configuracion de embedding. Si Metabase devuelve 4xx/5xx en cualquier paso, httpx lanza HTTPStatusError. Args: client: Cliente autenticado con sesion activa. payload: Dict con el payload completo del dashboard tal como lo espera la API de Metabase. Puede incluir dashcards. Returns: Dict con el dashboard creado. Si habia dashcards, la respuesta es la del PUT final e incluye el campo `dashcards` con las cards posicionadas. Si no habia dashcards, es la respuesta del POST inicial. Example: >>> dash = metabase_create_dashboard_raw(client, { ... "name": "Sales Overview", ... "description": "KPIs de ventas mensuales", ... "collection_id": 5, ... "parameters": [], ... "dashcards": [ ... { ... "id": -1, ... "card_id": 42, ... "size_x": 6, ... "size_y": 4, ... "col": 0, ... "row": 0, ... "visualization_settings": {}, ... "parameter_mappings": [], ... }, ... ], ... }) >>> print(dash["id"]) # ID asignado por Metabase >>> print(len(dash["dashcards"])) # 1 """ body = {k: v for k, v in payload.items() if k != "dashcards"} dashcards = payload.get("dashcards") created = client.request("POST", "/api/dashboard", json=body) if dashcards: dashboard_id = created["id"] return client.request( "PUT", f"/api/dashboard/{dashboard_id}", json={"dashcards": dashcards}, ) return created