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 nooutput.jsondo 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 datasetintervalsem 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áusulaFROMde uma métrica, e a seção[clock], se referem a ele.kind— um deintervals,timeline,recordsouscalars. Define quais roles são obrigatórios (abaixo) e como uma métrica pode consultá-lo.source_key— dot-path até o array nooutput.jsonque este dataset lê.time_unit— como os valores destart/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.
| Kind | Precisa de | Opcional |
|---|---|---|
intervals | start, e end ou duration | resource, label, category |
timeline | value, e at ou start | label, 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 oidcomo 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 = 60Declarar [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 datasetintervalsdeclarado (ou derivado de um widget). Obrigatório — um datasetrecordsouscalarsnã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; assume60(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]]— umkindausente ou desconhecido, umsource_keyausente, ou um column role obrigatório ausente em[datasets.roles]para aquele kind (por exemplo, um datasetintervalsprecisa destartmaisendouduration).[[metrics]]— umidousqlausente, e o mesmo guard de SQL somente leitura que o próprio dashboard executa: semINSERT/UPDATE/ATTACH, sem múltiplas instruções.[clock]—datasetdeve nomear um datasetintervalsdeclarado (ou o dataset sintetizado de um widgettimeline/gantt-chart);speeddeve ser um número positivo e finito; otime_keyde 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.