Pular para conteúdo

0014 — Analytics BI (Metabase): auto-provisão via API REST

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-03 · Decidido em: 2026-07-03

Contexto

A feature analytics da Central de Apps provisiona o Aptabase (eventos — decisão 0013). A 0013 descopou o Metabase ("BI independente; o app não o consome"), então a estrutura de BI (collection do cliente, dashboard do app, cards com queries ClickHouse sobre a tabela events) era criada na mão — repetitivo, propenso a erro e a divergência entre apps.

Diferente do Aptabase (sem API de provisão), o Metabase tem API REST real — dá pra provisionar de forma robusta e idempotente. Esta decisão reverte o descopo e implementa o passo Metabase.

Decisão

  1. Metabase re-escopado para dentro de analytics. FEATURE_PROVIDERS["analytics"] = ["aptabase", "metabase"]; a resposta honesta (0.17.6) reporta os dois sub-estados (state/last_error por provider); a falha de um não impede o outro.
  2. MetabaseProvider garante (procura-antes-de-cria §7, organização em 2 níveis): Collection do grupo/cliente (nome = cliente_id; sem cliente → "X-Adm"; match por nome na raiz + não-archived, pois nome de collection não é único) → sub-pasta do app (nome = app_id, parent_id = a do grupo/cliente) → Dashboard do app (por app_id) → cards baseline (query nativa ClickHouse, db METABASE_CLICKHOUSE_DB_ID default 2, <APP_ID> substituído), tudo dentro da sub-pasta do app (X-Adm|cliente → app → dashboard + cards).
  3. Cards com nome canônico, sem sufixo — já isolados na sub-pasta do app, então não colidem entre apps (a sub-pasta substitui o antigo sufixo · app_id). Idempotência pelos dashcards do dashboard + reuso por nome na collection (adota cards órfãos de uma falha parcial, sem duplicar); ligados por read-modify-write (GET /api/dashboard/:id → mescla → PUT <!-- checa-rotas: ignorar --> /api/dashboard/:id com dashcards de id negativo único — a versão do Metabase rejeita id:-1 repetido; o POST /:id/cards é deprecado ≥ v0.47).
  4. Baseline 5 sempre (Usuários Ativos, Sessões Ativas, Usuários Ativos por Dia, Versão do App, Ações Mais Usadas — que já agrega todo evento custom via NOT IN). + Telas Mais Usadas só se screen_view ∈ eventos (o app manda os eventos na provisão; ausente/vazio → inclui). O App carrega eventos como campo transiente (não persiste).
  5. Período = duas variáveis data_inicio/data_fim (type:date) — não field filter (evita lookup de field_id e dependência do sync do ClickHouse no Metabase).
  6. BI = só link: devolve metabase_dashboard_id (client-grade → app.json); o app não embeda (sem signed embedding). Idempotência entre runs = reuso-persistido (allowlist {aptabase, metabase} no ProvisioningService).

    Superado em parte pela decisão 0017: o "não embeda / sem signed embedding" foi revertido — agora o auth habilita enable_embedding e minta a URL de embed assinada (AppLinks.metabase_embed), mantendo o link como fallback. O resto da 0014 segue.

Alternativas consideradas

  • Manter o Metabase fora (0013): provisão manual não escala; a API existe → automatizável.
  • Field filter de range ({{periodo}} → um widget): exige field_id de events.timestamp + ClickHouse sincronizado — mais frágil. Preterido pelas duas variáveis.
  • Criar os 6 cards incondicionalmente: simples, mas Telas viria vazia sem screen_view. Preterido — o app já informa os eventos.
  • Cards bespoke por evento custom: deferido (editorial, não automatizável; o Ações Mais Usadas já cobre os custom).
  • Signed embedding no app: fora de escopo (mais superfície; o link basta).

Consequências / riscos

  • Versão mínima do Metabase (guarda): API keys (x-api-key) exigem ≥ 0.49; PUT com dashcards exige ≥ 0.47/0.48. Versão fixada (reconferir em upgrade); instância mais velha → 401/404 → falha honesta (fail-fast, nunca id falso).
  • Match de collection por nome (não-único): mitigado por raiz + não-archived + collection_id persistido (reuso); risco residual aceito.
  • visualization_settings mínimo por tipo — card pode renderizar pobre (não quebra); ajuste fino é operação.

Operar (config no recurso auth)

Provisão automática via /xadm-setup (feature analytics). Sem runbook separado (provider automagico não tem procedimento manual). Envs a setar uma vez no recurso auth (Coolify):

Variável Valor Secret?
METABASE_URL https://metabase.xadm.biz não
METABASE_API_KEY API key de escrita (Admin → API Keys; grupo com escrita) sim ✅
METABASE_CLICKHOUSE_DB_ID id do DB ClickHouse "Aptabase Clickhouse" (default 2) não

Ausência de METABASE_URL/METABASE_API_KEY → o provider recusa (boot ok). Diagnóstico: o /provision responde providers[].last_error (ex.: Metabase POST /api/card HTTP 401 = API key inválida/sem escrita; HTTP 404 = versão do Metabase antiga ou db id errado).

Lado do app (fora do auth)

O /xadm-setup manda os eventos do app no request de provisão e grava features.analytics.metabase_dashboard_id no app.json (link pro dashboard). Ver decisão 0013 para o lado Aptabase da mesma feature.