Pular para o conteúdo principal

Indicadores embutidos — SmartMetabase

O SFA não desenha gráfico. Ele hospeda painéis do SmartMetabase em iframe e assina, no servidor, o token que define de quem são os dados. Decisão e alternativas descartadas em ADR-037 (.claude/docs/decisions.md).

Consequência prática: indicador novo é trabalho de BI, não release do SFA. Quem monta a pergunta é o analista, na ferramenta; no SFA se registra apenas um ponteiro (tipo + id do item).

Como o recorte por organização é garantido​

O embedding do SmartMetabase tem parâmetros em três estados; o que interessa aqui é o locked: o valor vem dentro do token e o servidor recusa qualquer tentativa de sobrescrevê-lo pela URL ou pela requisição.

Navegador API do SFA SmartMetabase
│ GET /analytics/panels/:key/embed │ │
│──────────────────────────────────►│ │
│ │ assina HS256: │
│ │ { resource: {dashboard: 3}, │
│ │ params: { org_id: <sessão> },
│ │ exp: agora+600s } │
│◄──────────────────────────────────│ │
│ { url, expiresAt, ttlSec } │ │
│ │ │
│ <iframe src=".../embed/dashboard/<token>"> ────────────────────►│
│ valida assinatura,
│ aplica org_id travado

A chave de assinatura nunca sai do backend: ela assina token para qualquer item com qualquer recorte, então no navegador seria a base inteira exposta. O token é curto (SMARTMETABASE_EMBED_TTL_SEC, default 600s) e a tela renova sozinha antes de vencer.

Configuração​

1. No SmartMetabase​

  1. Conecte o PostgreSQL do SFA com um usuário somente leitura.
  2. Monte a pergunta/dashboard.
  3. Crie um filtro de dashboard que recorte pela organização (a coluna orgId, presente em toda entidade do SFA) e mapeie-o nos cards.
  4. Publique o item para embedding e marque esse filtro como locked.
  5. Copie a chave de embedding (Administração · Links públicos).

2. No ambiente da API do SFA​

SMARTMETABASE_URL=http://localhost:3070 # URL que o NAVEGADOR alcança
SMARTMETABASE_EMBED_SECRET=<chave de embedding>
SMARTMETABASE_EMBED_TTL_SEC=600

GET /analytics/status diz o que falta — e a tela mostra isso em vez de um iframe quebrado.

3. No SFA​

Em Indicadores, "Novo painel": key, nome, tipo (dashboard/pergunta), id do item no SmartMetabase, e o slug do parâmetro travado que recebe a organização (default org_id). Marque "Mostrar também no dashboard inicial" para ele subir para a home.

O parâmetro travado precisa existir do outro lado

Se o slug informado aqui não estiver locked no SmartMetabase, o embed responde erro — de propósito. A alternativa (seguir sem o filtro) mostraria a base inteira dentro da organização errada, que é o pior resultado possível de um bug de integração.

Mandando vários valores ao painel (ADR-038)​

O painel raramente quer só a organização. Ele quer o vendedor logado, a carteira, o período que a pessoa escolheu. Esses valores não são todos iguais, e por isso vão por dois canais diferentes:

CanalQuem defineQuem vê pode mudar?
Recorte — quem está perguntandoparams do token assinadoa sessão, no servidor do SFAnão
Filtro — o que se está olhandoquery da URL do iframea página, ou quem vêsim

Se errar significa mostrar dados demais, vai no token. Se errar significa mostrar outra tela, vai na URL.

Recorte: travados com placeholders da sessão​

Em Indicadores → Parâmetros travados (extras), o valor aceita placeholders resolvidos no servidor a cada carregamento:

PlaceholderVira
{{orgId}}id da organização ativa
{{orgName}}nome da organização ativa
{{userId}}id de quem está logado
{{userEmail}}e-mail de quem está logado
{{userName}}nome de quem está logado

Assim, "este painel é sempre recortado pelo vendedor logado" vira configuração: basta um travado vendedor = {{userEmail}} (com o filtro vendedor marcado como locked no SmartMetabase e mapeado na coluna certa). Sem isso, cada recorte novo seria campo novo no modelo e deploy.

Placeholder inventado é erro

{{naoexiste}}, ou um placeholder que a sessão atual não sabe responder, devolve 400 com a lista dos válidos. Mandar o texto cru — ou vazio — produziria um gráfico vazio com cara de "não há dados", que é o tipo de falha que ninguém investiga.

O org_id não vai aqui: ele é somado por último, na hora de assinar. Um travado que redefinisse o slug da organização estaria desligando o isolamento multiorg por configuração de tela.

Filtro: valores iniciais na query​

GET /analytics/panels/visao-geral/embed?f.periodo=previous-6-month&f.categoria=A&f.categoria=B

devolve a URL do iframe já com ?periodo=previous-6-month&categoria=A&categoria=B. Repetir a chave manda vários valores. No front, o <EmbeddedPanel filtros={{ periodo: "previous-6-month" }} /> faz o mesmo.

Esses filtros precisam estar enabled no SmartMetabase — e quem vê pode mudá-los, porque a URL do iframe está no HTML. Use-os para o que se está olhando, nunca para recorte.

Contrato​

RotaPermissãoO que faz
GET /analytics/statusanalytics:readSe está configurado e quais variáveis faltam.
GET /analytics/panelsanalytics:readPainéis da organização (?enabled=true filtra).
POST /analytics/panelsanalytics:configureRegistra um painel.
PATCH /analytics/panels/:idanalytics:configureEdita (a key é imutável).
DELETE /analytics/panels/:idanalytics:configureRemove do SFA — não apaga nada no SmartMetabase.
GET /analytics/panels/:key/embedanalytics:readURL assinada + validade. ?f.<slug>= pré-preenche filtros editáveis.

O modelo AnalyticsPanel guarda resource (DASHBOARD/QUESTION), resourceId (id lá), lockedParams (travados estáticos extras), orgParamSlug, themeSlug, height, showOnHome, position e enabled — sempre por orgId.

A aparência do painel​

O campo Tema guarda o identificador de um tema do SmartMetabase — o mesmo que aparece lá em Administração · Temas, ao lado de cada tema (ex.: wessel-escuro). Ele vira ?theme=<slug> na URL do iframe.

Vazio usa o tema padrão do produto, e um identificador que não existe do outro lado também: a regra é a mesma que separa os dois canais — errar o tema muda a cara do painel, nunca os dados, e por isso ele viaja na URL e não no token. Aparência não é motivo para uma tela ficar em branco.

Se o painel tiver um filtro editável chamado theme, quem vence é o tema do painel: quem o configurou escolheu a aparência, e a query de quem vê não decide isso.

O que ainda não existe​

  • Escolher o painel por uma lista: hoje se digita o id do item. Listar os dashboards exigiria uma credencial de sessão do SmartMetabase guardada no SFA — outra decisão, com outro risco.
  • Escolher o tema por uma lista: mesmo motivo. GET /api/themes do SmartMetabase exige sessão; a rota anônima só serve um tema pelo identificador, e não a lista deles.
  • UI de filtros no SFA. O canal existe (?f.<slug>=, e a prop filtros do EmbeddedPanel), mas nenhuma tela ainda oferece os controles — hoje quem preenche é código.
  • Recorte numa pergunta avulsa. Filtro é do dashboard, não do card: um gráfico só, recortado por organização, precisa estar dentro de um dashboard (ainda que de um card).
  • Cache/telemetria de visualização no lado do SFA. O SmartMetabase já conta visualizações por item embutido.