Pitch: Eu não fiz um dashboard. Eu fiz um DAG que cospe gráficos

Quando comecei a mexer na feature de analytics, achei que estava construindo "mais uma área com gráficos".
Não era isso.
O problema real era outro: toda vez que você adiciona mais um gráfico hardcoded, você também adiciona mais uma combinação de filtro, mais uma exceção de layout, mais uma decisão de produto enterrada em if.
Depois de alguns ciclos, o dashboard vira um cemitério de endpoints com nomes criativos.
Eu queria fugir de dois extremos:
- um dashboard estático demais, onde cada gráfico novo exige código novo
- um query builder genérico demais, que vira planilha com skin e ninguém usa
A saída foi tratar cada gráfico customizado como um grafo executável.
A feature final até parece um dashboard normal por fora: painéis, gráficos simples, comparação de métricas e um editor visual em tela cheia.
Por dentro, a história é outra.
A ideia
Em vez de salvar "um gráfico de barras sobre top apps", eu salvo algo mais próximo disso:
{
"version": 1,
"nodes": [
{ "id": "a", "type": "source", "data": { "slug": "top-apps" } },
{ "id": "b", "type": "filter", "data": { "minValue": 10 } },
{ "id": "c", "type": "sort", "data": { "by": "value", "order": "desc" } },
{ "id": "d", "type": "limit", "data": { "value": 10 } },
{ "id": "e", "type": "chart", "data": { "chartType": "bar", "horizontal": true } }
],
"edges": [
{ "source": "a", "target": "b" },
{ "source": "b", "target": "c" },
{ "source": "c", "target": "d" },
{ "source": "d", "target": "e" }
]
}
O builder visual basicamente monta isso com drag-and-drop.
Os tipos de nó são bem pequenos e bem explícitos:
sourcefilteraggregatesortlimitchart
Na prática, o editor virou uma DSL visual de analytics.
O detalhe que deixou a coisa interessante
O source não aponta para SQL livre.
Ele aponta para fontes de dados conhecidas do produto, como:
top-appstrending-by-intentplatform-distributionmy-export-timeline
Isso mudou tudo.
Se eu abrisse a porta para query arbitrária, eu estaria construindo um BI.
Como eu restringi o sistema a fontes confiáveis + transformações controladas, eu ganhei flexibilidade sem transformar a feature em um monstro impossível de manter.
É menos "faça qualquer coisa".
E mais "componha bem as coisas que já fazem sentido no domínio".
Como o gráfico executa
Quando o usuário monta o fluxo, o frontend converte isso em um plano de execução.
Mentalmente, funciona assim:
source -> filter -> aggregate -> sort -> limit -> chart
O motor percorre cada branch até encontrar a source, monta um pipeline de transformações e executa em cima dos dados carregados pela API.
O preview em tempo real não é mock.
Ele já executa o grafo em cima das fontes reais e renderiza o resultado na hora.
Se o chart recebe uma entrada, o resultado é um gráfico simples.
Se ele recebe várias entradas, entra em compare mode.
Aí aparece a parte legal: eu posso convergir até 4 fontes no mesmo chart, mesclar séries e renderizar comparação em bar, line ou area.
pie deixa de fazer sentido nesse cenário, então o sistema automaticamente rebaixa para bar.
Esse tipo de regra parece detalhe de UI.
Mas na real é regra de modelagem.
O caos que eu precisei impedir
Builder visual sem regra vira editor de bug.
Então além do preview em tempo real, eu coloquei validação dos dois lados: frontend e backend.
Algumas invariantes:
- o grafo precisa ter exatamente 1
chart sourcenão pode ter entradachartnão pode ter saída- nós que não são
chartpodem ter no máximo 1 entrada - o grafo não pode ter ciclo
- o
chartprecisa ser alcançável a partir de umasource - nós desconectados são rejeitados
- compare mode só funciona quando todas as entradas produzem o mesmo shape de dados
Esse espelhamento foi importante por um motivo simples:
se a regra existe só na UI, ela é sugestão.
Se existe também no servidor, ela vira contrato.
O efeito colateral bom
Quando o gráfico é salvo como dado, o produto muda de categoria.
Antes, cada novo insight exigiria mais uma tela ou mais um endpoint dedicado.
Agora eu tenho uma engine pequena que recompõe relatórios existentes.
O que antes era:
"precisamos criar um gráfico novo"
virou:
"precisamos expor uma nova source ou uma nova transformação"
Essa troca parece sutil, mas muda bastante a velocidade do produto.
A parte mais engraçada
No começo eu achei que estava construindo analytics.
No meio do caminho eu percebi que estava construindo:
- uma DSL visual
- um executor de pipeline
- um conjunto de invariantes para DAG
- um jeito de transformar relatório em bloco composável
O gráfico foi quase o output menos interessante da história.
O takeaway
Tem muita feature que parece "só UI" até você modelar direito.
Nesse caso, o gráfico não é o centro.
O centro é o formato intermediário.
Quando você acerta esse formato, a interface, a persistência, o preview e a validação começam a se alinhar quase sozinhos.
E foi aí que a feature de analytics ficou divertida.
Se eu fosse resumir em uma linha:
eu não construí um dashboard com filtros.
eu construí um DAG pequeno o suficiente para pessoas usarem, e rígido o suficiente para não virar um pesadelo.
Curiosidade técnica: vocês tratariam isso como analytics visual, query builder disfarçado, ou uma mini linguagem de produto?
O link do site está logo abaixo (feature beta para usuários PRO)!
Fonte: https://refcat.app