Harumi

Datasets, métricas e o clock do dashboard

Declare o formato de um dataset uma única vez, defina novos KPIs como SQL em vez de um widget, e adicione playback controlado por clock a um dashboard.

Três camadas opcionais abaixo de [[widgets]]

Todo dashboard.toml tem [[widgets]] — veja Widgets para a referência completa e sempre atualizada de cada tipo de widget e seus campos. Três seções de nível superior são opcionais, e se apoiam uma na outra:

  • [[datasets]] — nomeia o formato de um array no output.json do seu run uma única vez, em vez de repeti-lo em cada widget que o lê.
  • [[metrics]] — uma única instrução SQL somente leitura sobre seus datasets. Um novo KPI é uma query, não um novo widget.
  • [clock] — transforma um dataset intervals em uma barra de play/pause/scrub acima do dashboard.

Nenhuma das três é obrigatória. Um dashboard.toml com apenas [[widgets]] — o template inicial de todo projeto, e todo dashboard escrito antes dessas seções existirem — continua sendo renderizado exatamente como antes.

Datasets

[[datasets]]
id = "schedule"
kind = "intervals"
source_key = "schedule"
time_unit = "h"

[datasets.roles]
resource = "machine"
label = "task"
start = "start_h"
end = "end_h"
  • id — único dentro do dashboard. É como a cláusula FROM de uma métrica, e a seção [clock], se referem a ele.
  • kind — um de intervals, timeline, records ou scalars. Define quais roles são obrigatórios (abaixo) e como uma métrica pode consultá-lo.
  • source_key — dot-path até o array no output.json que este dataset lê.
  • time_unit — como os valores de start/end/duration/at (abaixo) são lidos: um sufixo de exibição simples (ex. "h" ou "min", a leitura padrão — números simples com essa unidade), ou um marcador de wall-clock — "epoch" (segundos desde a epoch), "epoch_ms" (milissegundos), ou "iso" (uma string ISO 8601) — renderizado como uma data/hora real e usado para escalar a velocidade de playback de uma seção [clock].
  • [datasets.roles] — qual coluna desempenha qual papel. Cada role é o nome de um campo dentro de uma linha do array de origem, não um dot-path para o payload inteiro.
KindPrecisa deOpcional
intervalsstart, e end ou durationresource, label, category
timelinevalue, e at ou startlabel, category
records—qualquer role; uma tabela se liga a este kind sem nenhum
scalars—value

Um dataset sem um role que seu kind exige é registrado em config.errors e descartado, em vez de gerar uma view que renderiza em branco silenciosamente — a mesma regra de "falha visível" que o resto de um dashboard spec segue.

Todo widget já tem um

Declarar [[datasets]] é opcional porque todo tipo de widget já existente (table, gantt-chart, chart, …) já implica um, derivado das suas próprias chaves internas — as chaves tasks_key/resource_key/start_key/… de um gantt-chart se tornam automaticamente um dataset intervals. Declarar um explicitamente só importa quando você quer que uma query [[metrics]] ou um [clock] o referencie pelo nome — as chaves internas de um widget continuam funcionando sozinhas.

Métricas

[[metrics]]
id = "per_machine"
title = "Load per machine"
sql = """
SELECT machine, count(*) AS tasks, sum(end_h - start_h) AS busy_hours
FROM schedule
GROUP BY machine
ORDER BY machine
"""

Cada dataset declarado é carregado como uma tabela nomeada pelo seu id — schedule acima — então o SQL de uma métrica lê seus datasets como leria qualquer outra tabela. Isso roda em um engine DuckDB no próprio navegador de quem está vendo o dashboard (carregado sob demanda apenas quando um spec declara ao menos uma métrica), não em um servidor.

  • id — único dentro do dashboard. É a ele que um widget se liga (veja abaixo).
  • sql — uma única instrução somente leitura.
  • title — rótulo de exibição opcional; usa o id como alternativa quando uma view precisa de um.

Um dataset cujo id não é um identificador SQL válido (letras, números, underscore, sem começar com um dígito) ainda pode alimentar um widget pelas suas próprias chaves internas, mas não pode ser consultado — o dashboard sinaliza isso explicitamente em vez de deixar a query falhar com um simples "table does not exist".

O que é permitido no SQL

A instrução precisa ser exatamente uma query SELECT, WITH, ou que comece com FROM (um DESCRIBE/SUMMARIZE também é aceito, para iterar em uma query). Rejeitados em qualquer ponto da instrução, não só no início:

  • Escritas e mudanças de schema — INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, e similares.
  • Qualquer coisa que alcance fora dos datasets carregados — ATTACH, COPY, LOAD, INSTALL, EXPORT/IMPORT, e funções de tabela que recebem um caminho ou URL (read_csv, read_parquet, postgres_scan, …). O SQL de um dashboard consulta o próprio output do run, nunca um endereço externo — para dados externos, conecte-os como uma fonte de dados em vez disso.
  • Mudar o comportamento da sessão/engine — PRAGMA, SET, RESET, CALL, e palavras-chave de prepared statement (PREPARE/EXECUTE).

Uma query rejeitada é reportada com a palavra-chave ou função específica que disparou a rejeição, em config.errors — o mesmo banner que um widget com uma chave não resolvida mostra.

Lendo o resultado de uma métrica em um widget

As linhas de uma métrica são expostas sob um namespace reservado metrics, resolvido por dot-path exatamente como o output de um run:

[[widgets]]
type = "table"
id = "load-table"
title = "Load per machine"
rows_key = "metrics.per_machine"
columns = [
  { key = "machine", label = "Machine" },
  { key = "tasks", label = "Tasks" },
  { key = "busy_hours", label = "Busy hours" },
]

value_key = "metrics.per_machine.0.busy_hours" leria o busy_hours da primeira linha para um tile metric — o dot-path indexa o array com .0., igual a qualquer outro segmento numérico. Qualquer tipo de widget pode se ligar ao resultado de uma métrica desse jeito; uma métrica não precisou de um novo tipo de widget próprio.

Uma métrica roda uma única vez por run, sobre os datasets já carregados — não uma vez por tick do [clock]. Ela não tem acesso à posição atual do clock.

Clock

[clock]
dataset = "schedule"
speed = 60

Declarar [clock] adiciona uma barra de play/pause/scrub, controlada pelas próprias colunas de início/fim de um dataset intervals. A barra fica no topo do card de cada timeline e gantt-chart do dashboard, e todos compartilham um único clock, então dar play em um move os outros. Um [clock] em um dashboard sem nenhum desses widgets não tem onde mostrar a barra, e o dashboard o reporta como um erro de configuração.

  • dataset — o id de um dataset intervals declarado (ou derivado de um widget). Obrigatório — um dataset records ou scalars não tem um eixo de tempo para percorrer, e é rejeitado com um erro que nomeia seu kind real.
  • speed — unidades de tempo simulado por segundo real. Um número positivo e finito; assume 60 (então uma hora de um schedule passa em um minuto real) quando omitido.

Não há, de propósito, um step size ou taxa de amostragem para configurar: o playback se move entre os próprios pontos de mudança dos dados (onde um intervalo começa ou termina), que são exatos, em vez de amostrar em uma grade fixa que poderia perder ou borrar uma mudança entre amostras.

Validando um spec antes de commitar

harumi dashboard validate (veja comandos da CLI) verifica as quatro seções, não só [[widgets]]:

  • [[datasets]] — um kind ausente ou desconhecido, um source_key ausente, ou um column role obrigatório ausente em [datasets.roles] para aquele kind (por exemplo, um dataset intervals precisa de start mais end ou duration).
  • [[metrics]] — um id ou sql ausente, e o mesmo guard de SQL somente leitura que o próprio dashboard executa: sem INSERT/UPDATE/ ATTACH, sem múltiplas instruções.
  • [clock] — dataset deve nomear um dataset intervals declarado (ou o dataset sintetizado de um widget timeline/gantt-chart); speed deve ser um número positivo e finito; o time_key de uma métrica é sinalizado se não existir uma seção [clock] para lhe dar um eixo de tempo onde se posicionar.

Essa é uma validação estrutural — ela detecta uma seção malformada antes de você commitar, do mesmo jeito que já fazia com [[widgets]]. Ela não executa o SQL das suas [[metrics]] contra um output.json real, então uma query que é SQL válido mas referencia uma coluna que seu dataset não tem de fato ainda só aparece como um banner de erro em tempo de execução na próxima vez que o dashboard for aberto.

On this page