Buscar K
Aparência
Aparência
Um app é uma Aplicação do DataViz. O arquivo apps/<slug>--<id>.yaml guarda tudo dela: tabelas, relacionamentos, expressões, indicadores, filtros de abertura e os Dashboards com os Widgets. Não há arquivo separado de Dashboard; cada item de dashboards: segue a referência de app-dashboard.
Os conceitos ficam nas suítes: modelo de dados e relacionamentos, linguagem de expressões, filtros e indicadores.
Crie com lumo new app --name "Comercial". Para os ids de coluna usados em indicadores, filtros e Widgets, rode lumo info app:<id> --full.
fileName^[a-z0-9-]+--\d+\.yaml$headerheader.idheader.kindapp (fixo)header.lumov2 (fixo)header.tenantIdheader.appIdheader.dashboardIdbodybody.criado_em^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$body.criado_porbody.publicado_em^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$body.publicado_porbody.nomebody._statedraft, published, inconsistentbody.descricaobody.iconbody.color^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$body.deskIdbody.versionbody.configbody.config.show_last_loadedatbody.config.show_last_loadedat_tablesbody.config.tablesbody.filtrosjsonValue por compatibilidade com o que já circula em produção).body.filter_modedefault, per_dashboardbody.allow_new_filtersfiltros. null = herda o padrão (true); só false explícito bloqueia. Sem efeito no modo 'per_dashboard' (cada aba tem o seu próprio, em dashboards[].body.allow_new_filters).body.tablesbody.tables[].tableIdbody.tables[].guidbody.tables[].nome_visualizacaobody.tables[].tipodimension, n2nbody.tables[].appTableIdbody.tables[].columnsvisible: false hides the column from the END-USER report generator / field picker - the column stays fully usable in expressions and widgets (declutter, NOT access control). label overrides the column's display name for THIS app only (other apps and the DW table are unaffected). Omit a table's columns to leave its overlay untouched; {} clears every override. Draft apps only.body.tables[].columns.<chave>.visiblebody.tables[].columns.<chave>.labelbody.expressionsbody.expressions[].human_descriptiondocumentation, que não tem limite e não é renderizada na lista.body.expressions[].descriptiondocumentation.body.expressions[].typesql, daxexpression. Ausente = mantém a da expressão salva (sql na expressão nova). dax só para medida (data_type Number, behavior measure) e pede a feature flag habilitar-dax de quem cria ou edita; o push valida a fórmula e recusa com linha e coluna. Exemplos: lumo scaffold expression dax. Referência: https://docs.horusbi.com.br/dataviz/construir/dax/referencia-rapidabody.relationshipsbody.relationships[].idbody.relationships[].column_left_idbody.relationships[].column_right_idbody.relationships[].from_columnbody.relationships[].from_table_idbody.relationships[].to_columnbody.relationships[].to_table_idbody.relationships[].relationshipTypeONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE, MANY_TO_MANYbody.relationships[].ignoreTimebody.indicatorslumo scaffold indicator.body.indicators[].idbody.indicators[].nomebody.indicators[].primaryColumn^X?\d+(:[A-Za-z0-9_]+)+$body.indicators[].alternativeColumnsbody.indicators[].dateColumnIdbody.indicators[].defaultDatePeriodToday, CurrentMonth, MonthToDate, PreviousMonth, Yesterday, CurrentYear, NextYear, DayBeforeYesterday, AllTime, PreviousYear, ThisWeek, PreviousWeek, NextWeek, Last7Daysbody.indicators[].filtersbody.indicators[].promptbody.indicators[].descriptionbody.indicators[].apresentacaobody.indicators[].apresentacao.iconebody.indicators[].apresentacao.corbody.indicators[].apresentacao.formatocurrency, percent, integer, decimalbody.indicators[].apresentacao.direcaoup_good, down_good, neutralbody.indicators[].monitoramentobody.indicators[].monitoramento.graohour, day, week, monthbody.indicators[].monitoramento.janelaDiasbody.indicators[].monitoramento.farolautobody.indicators[].monitoramento.farol.verdebody.indicators[].monitoramento.farol.ambarbody.indicators[].principalbody.indicators[].metabody.factsindicators (mesma lista). Aceito com aviso; o pull grava indicators.body.facts[].idbody.facts[].nomebody.facts[].primaryColumn^X?\d+(:[A-Za-z0-9_]+)+$body.facts[].alternativeColumnsbody.facts[].dateColumnIdbody.facts[].defaultDatePeriodToday, CurrentMonth, MonthToDate, PreviousMonth, Yesterday, CurrentYear, NextYear, DayBeforeYesterday, AllTime, PreviousYear, ThisWeek, PreviousWeek, NextWeek, Last7Daysbody.facts[].filtersbody.facts[].promptbody.facts[].descriptionbody.facts[].apresentacaobody.facts[].apresentacao.iconebody.facts[].apresentacao.corbody.facts[].apresentacao.formatocurrency, percent, integer, decimalbody.facts[].apresentacao.direcaoup_good, down_good, neutralbody.facts[].monitoramentobody.facts[].monitoramento.graohour, day, week, monthbody.facts[].monitoramento.janelaDiasbody.facts[].monitoramento.farolautobody.facts[].monitoramento.farol.verdebody.facts[].monitoramento.farol.ambarbody.facts[].principalbody.facts[].metabody.reportslumo scaffold report.body.reports[].internalIdbody.reports[].namebody.reports[].descriptionbody.reports[].columnsbody.reports[].filterbody.reports[].filter[].columnbody.reports[].filter[].column.column_idbody.reports[].filter[].column.expressionbody.reports[].filter[].operatorbody.reports[].filter[].valuefalse é o marcador de campo declarado vazio (vaga): a aba assume a coluna como filtrável mas sem valor próprio, e herda o que estava aplicado na aba anterior. Qualquer outro valor (inclusive array, para múltipla escolha) é o valor efetivamente aplicado.body.reports[].filter[].comparativeValuesbody.reports[].filter[].required0, 1, 2, 3body.reports[].filter[].mergeStrategybody.reports[].filter[].ask_on_openfalse.body.reports[].filter[].hintbody.reports[].sortbody.reports[].sort[].column_idbody.reports[].sort[].expressionbody.reports[].sort[].directionASC, DESCbody.reports[].breaksbody.reports[].breaks[].column_idbody.reports[].breaks[].expressionbody.reports[].matrixAxisbody.reports[].matrixAxis.column_idbody.reports[].matrixAxis.expressionbody.reports[].scenariosbody.dynamic_dimensionsbody.tagsbody.dashboardsbody.dashboards[].headerlumo push fills id:0 (create) plus kind/lumo/tenantId/appId from the app's own header before sending. header: {} and an absent header key are the same intent.body.dashboards[].header.idbody.dashboards[].header.kindapp-dashboard (fixo)body.dashboards[].header.lumov2 (fixo)body.dashboards[].header.tenantIdbody.dashboards[].header.appIdbody.dashboards[].bodybody.dashboards[].body.criado_em^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$body.dashboards[].body.criado_porbody.dashboards[].body.publicado_em^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$body.dashboards[].body.publicado_porbody.dashboards[].body.nomebody.dashboards[].body.ordembody.dashboards[].body.widget_columnsbody.dashboards[].body.widget_floatbody.dashboards[].body.versionbody.dashboards[].body.filters[] com allow_new_filters liberado, e é o que o CLI emite para toda aba que nunca usou o recurso (o hash de drift descarta chave null, então essa aba não muda de hash entre versões do CLI). Ver shared.schema.json#/$defs/filterDefinition para o contrato de cada item, inclusive value: false como campo declarado vazio.body.dashboards[].body.filters[].columnbody.dashboards[].body.filters[].column.column_idbody.dashboards[].body.filters[].column.expressionbody.dashboards[].body.filters[].operatorbody.dashboards[].body.filters[].valuefalse é o marcador de campo declarado vazio (vaga): a aba assume a coluna como filtrável mas sem valor próprio, e herda o que estava aplicado na aba anterior. Qualquer outro valor (inclusive array, para múltipla escolha) é o valor efetivamente aplicado.body.dashboards[].body.filters[].comparativeValuesbody.dashboards[].body.filters[].required0, 1, 2, 3body.dashboards[].body.filters[].mergeStrategybody.dashboards[].body.allow_new_filtersfilters. null = herda o padrão (true); só false explícito bloqueia.body.dashboards[].body.widgetsbody.dashboards[].body.widgets[].idbody.dashboards[].body.widgets[].guidbody.dashboards[].body.widgets[].nomebody.dashboards[].body.widgets[].typeKpi, Barline, Pie, Table, Matrix, ComposedMatrix, Map, Gauge, Punchcard, Sankey, Forecast, Calendar, ListBox, Text, Diagram, Svelte, ParametricSvelte, Appbody.dashboards[].body.widgets[].display_nomebody.dashboards[].body.widgets[].xbody.dashboards[].body.widgets[].ybody.dashboards[].body.widgets[].wbody.dashboards[].body.widgets[].hbody.dashboards[].body.widgets[].mobileYbody.dashboards[].body.widgets[].mobileHbody.dashboards[].body.widgets[].mobileVisiblebody.dashboards[].body.widgets[].chart# apps/comercial--7302.yaml
# yaml-language-server: $schema=https://docs.horusbi.com.br/schemas/v2/app.schema.json
id: 7302
kind: app
lumo: v2
tenantId: 1200
---
nome: Comercial
descricao: Vendas por filial e período.
icon: mdi:chart-bar
color: "#3B82F6"
config: {}
tables:
- tableId: 6230 # fato_vendas
tipo: dimension # vale para tabela fato e dimensão; n2n só em tabela ponte
- tableId: 6105 # dim_filiais
tipo: dimension
relationships:
- from_table_id: 6105 # lado "1": a chave não se repete
to_table_id: 6230 # lado "N"
from_column: ID_FILIAL
to_column: FILIAL_ID
indicators:
- nome: Faturamento
primaryColumn: "70011:SUM" # coluna VALOR_TOTAL, somada (lumo info app:7302 --full)
dateColumnId: 70012 # DATA_EMISSAO
defaultDatePeriod: CurrentMonth
description: Soma do valor dos pedidos, pela data de emissão.
filtros:
- column: { column_id: "70012", expression: "DATE" }
operator: RELATIVE
value: currentMonth
dashboards:
- header:
id: 0 # 0 = criar no próximo push; mantenha o id dos que já existem
body:
nome: Visão geral
ordem: 0
widget_columns: 12
widgets:
- nome: Faturamento
type: Kpi
w: 3
h: 1
x: 0
y: 0
chart:
primaryData:
dataSource: "70011:SUM"
title:
value: Faturamento
filter: []indicators é o nome atual da lista de indicadores. O nome antigo, facts, ainda é aceito com aviso do lint, e o pull grava indicators. Um arquivo com as duas chaves é recusado. No indicador, description substitui o antigo prompt.expressions (expressões calculadas) tem o formato comentado em lumo scaffold app, na seção EXPRESSIONS. O esquema não detalha os campos de cada expressão, então o lint não os confere.relationshipType: só ONE_TO_ONE muda algo, e exige chave única dos dois lados. Qualquer outro valor vira um-para-muitos, com o lado "1" em from_table_id e o lado "N" em to_table_id. Se a chave do lado "1" se repete, o push avisa (RELATIONSHIP_KEY_REPEATS); lumo info app:<id> --key-check lista as relações que já estão assim.push, então reordenar troca qual relacionamento manda. Para usar o outro numa expressão só, use use() na expressão.ignoreTime: true, ou a hora impede o filtro de passar.tipo aceita dimension (toda tabela comum, fato ou dimensão) e n2n (tabela ponte de muitos-para-muitos). O servidor recusa outro valor.nome_visualizacao renomeia a tabela só nesta Aplicação, e expressões e Widgets passam a usar o nome novo. Tirar a chave desfaz o nome no próximo push.columns dentro de tables[] com visible: false só esconde a coluna do gerador de relatórios. Ela continua usável em expressões e Widgets: isso arruma a tela e não restringe acesso.dashboards: substitui a lista inteira. Um Dashboard que está no servidor e falta no array é apagado. Edite no lugar e mantenha o header.id de cada um.filtros só vale com filter_mode ausente ou default. Com filter_mode: per_dashboard, cada Dashboard usa o próprio filters.push só em nome, descricao, icon, color, indicators, reports, filtros, filter_mode e allow_new_filters, e no filters/allow_new_filters de cada Dashboard. O resto pede lumo app clone e lumo app publish.monitoramento.farol manual pede verde menor que ambar, e o lint não confere a ordem. Um monitoramento sem farol mantém o farol gravado.