@bluemarble/bi 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DashboardDevtools-CSRt9lib.js → DashboardDevtools-I00Iz0Z8.js} +424 -240
- package/dist/DashboardDevtools-I00Iz0Z8.js.map +1 -0
- package/dist/{ModelDictionary-Dib4Vk3i.js → ModelDictionary-DUGFHBo-.js} +123 -6
- package/dist/ModelDictionary-DUGFHBo-.js.map +1 -0
- package/dist/cli.js +167 -11
- package/dist/cli.js.map +1 -1
- package/dist/client/index.d.ts +65 -7
- package/dist/client/index.js +222 -161
- package/dist/client/index.js.map +1 -1
- package/dist/{codegen-DkfI6UJr.js → codegen-LoQzFAle.js} +3 -1
- package/dist/codegen-LoQzFAle.js.map +1 -0
- package/dist/{layout-CoGyGWYM.d.ts → formatRules-DTKbov47.d.ts} +272 -207
- package/dist/{layout-nViz3MdU.js → layout-CrZ00tB0.js} +187 -15
- package/dist/layout-CrZ00tB0.js.map +1 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/DashboardDevtools-CSRt9lib.js.map +0 -1
- package/dist/ModelDictionary-Dib4Vk3i.js.map +0 -1
- package/dist/codegen-DkfI6UJr.js.map +0 -1
- package/dist/layout-nViz3MdU.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen-LoQzFAle.js","names":[],"sources":["../src/star.ts","../src/model.ts","../src/codegen.ts"],"sourcesContent":["/**\n * Modelo estrela (`tables` + `relationships`): tabelas fato e dimensão ligadas por relacionamentos\n * N:1, como no modelo do Power BI. Aqui ficam as regras do grafo — quem alcança quem e por qual\n * caminho —, usadas pela validação, pelo compilador e pelo diagrama do devtools.\n *\n * O filtro \"desce\" do lado 1 para o lado N: uma medida da fato `fPesagem` enxerga a dimensão\n * `dOperacao` se existe um caminho `fPesagem -> ... -> dOperacao` seguindo os relacionamentos no\n * sentido N -> 1 (snowflake vale: `fPesagem -> dOperacao -> dNavio`).\n */\n\nimport type { ModelDef, RelationshipDef } from './model'\n\nexport type ColumnRef = { table: string; column: string }\n\nexport type Edge = { from: ColumnRef; to: ColumnRef; index: number; inner: boolean }\n\n/** `'tabela.coluna'` -> `{ table, column }` (nulo se não tiver esse formato). */\nexport function parseColumnRef(ref: unknown): ColumnRef | null {\n if (typeof ref !== 'string') return null\n const match = /^([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(ref.trim())\n return match ? { table: match[1], column: match[2] } : null\n}\n\n/** Relacionamentos válidos do modelo, como arestas N -> 1. */\nexport function modelEdges(model: Pick<ModelDef, 'relationships'>): Edge[] {\n return (model.relationships ?? []).flatMap((relationship: RelationshipDef, index) => {\n const from = parseColumnRef(relationship?.from)\n const to = parseColumnRef(relationship?.to)\n return from && to ? [{ from, to, index, inner: relationship.inner === true }] : []\n })\n}\n\nconst escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Tabelas citadas no SQL como `tabela.coluna`. O que está entre aspas simples é ignorado, para\n * `'a.b'` num literal não contar como referência.\n */\nexport function referencedTables(sql: string, tables: string[]): string[] {\n const code = sql.replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n return tables.filter((name) => new RegExp(`(?<![\\\\w.\\`])${escapeRegex(name)}\\\\.`).test(code))\n}\n\n/** Passo de um caminho: entra em `table` pela aresta `edge` (de `edge.from.table`). */\nexport type PathStep = { table: string; edge: Edge }\n\nexport type Reach = {\n /** Tabela -> caminho mais curto a partir da origem (vazio para a própria origem). */\n paths: Map<string, PathStep[]>\n /**\n * Tabelas alcançadas por mais de um caminho (de qualquer tamanho): o filtro ficaria ambíguo. É a\n * mesma regra do Power BI, que só deixa um caminho ativo entre duas tabelas.\n */\n ambiguous: string[]\n}\n\n/** Busca em largura a partir de `start`, seguindo os relacionamentos no sentido N -> 1. */\nexport function reachFrom(start: string, edges: Edge[]): Reach {\n const paths = new Map<string, PathStep[]>([[start, []]])\n let frontier = [start]\n while (frontier.length) {\n const next: string[] = []\n for (const table of frontier) {\n for (const edge of edges) {\n if (edge.from.table !== table || paths.has(edge.to.table)) continue\n paths.set(edge.to.table, [...(paths.get(table) ?? []), { table: edge.to.table, edge }])\n next.push(edge.to.table)\n }\n }\n frontier = next\n }\n // Cada aresta saindo de uma tabela alcançada é um caminho até o destino dela\n const incoming = new Map<string, number>()\n for (const edge of edges)\n if (paths.has(edge.from.table))\n incoming.set(edge.to.table, (incoming.get(edge.to.table) ?? 0) + 1)\n const ambiguous = [...incoming]\n .filter(([table, count]) => table !== start && count > 1)\n .map(([table]) => table)\n return { paths, ambiguous }\n}\n\n/**\n * Tabelas que precisam entrar na consulta, na ordem do JOIN, para que a partir de `home` se chegue\n * a todas as `targets`. Como cada tabela é alcançada por um caminho só (o modelo valida ambiguidade),\n * a união dos caminhos é uma árvore: cada tabela entra uma vez, ligada pela aresta de quem veio antes.\n */\nexport function joinPlan(home: string, targets: Iterable<string>, edges: Edge[]): PathStep[] {\n const { paths } = reachFrom(home, edges)\n const plan: PathStep[] = []\n const added = new Set([home])\n const include = (target: string) => {\n for (const step of paths.get(target) ?? []) {\n if (added.has(step.table)) continue\n added.add(step.table)\n plan.push(step)\n }\n }\n for (const target of targets) include(target)\n // Relacionamento `inner` entra sempre que o lado N está na consulta: ele filtra mesmo sem\n // dimensão pedida (repete até não entrar mais nada, porque cada tabela nova pode ter os seus)\n for (let grew = true; grew;) {\n grew = false\n for (const edge of edges)\n if (edge.inner && added.has(edge.from.table) && !added.has(edge.to.table)) {\n include(edge.to.table)\n grew = true\n }\n }\n // Pai antes do filho: ordena pela profundidade do caminho\n const depth = (table: string) => paths.get(table)?.length ?? 0\n return plan.sort((a, b) => depth(a.table) - depth(b.table))\n}\n\n/**\n * Tabela de origem de uma medida `sum`: entre as tabelas que o SQL cita, a que alcança todas as\n * outras (ex.: `fPesagem.peso * dProduto.fator` agrega em `fPesagem`). Nulo se nenhuma alcança.\n */\nexport function measureHome(tables: string[], edges: Edge[]): string | null {\n if (!tables.length) return null\n return (\n tables.find((candidate) => {\n const { paths } = reachFrom(candidate, edges)\n return tables.every((table) => paths.has(table))\n }) ?? null\n )\n}\n","import { measureHome, modelEdges, parseColumnRef, reachFrom, referencedTables } from './star'\n\n/**\n * Tipos e validação do modelo semântico. Fica fora de `server/` porque o devtools (client) também\n * valida o rascunho editado antes de mandá-lo pro servidor.\n */\n\n/**\n * Dicionário: nome de negócio e explicação de cada campo. Os componentes usam como padrão (rótulo,\n * formatação) e o `ModelDictionary` vira a documentação do modelo para quem usa o dashboard.\n */\nexport type FieldMeta = {\n label?: string\n description?: string\n}\n\nexport const VALUE_FORMATS = ['number', 'integer', 'currency', 'percent'] as const\nexport type ValueFormat = (typeof VALUE_FORMATS)[number]\n\nexport type MeasureMeta = FieldMeta & {\n /** `percent` espera a fração (0.25 = 25%). */\n format?: ValueFormat\n /** Casas decimais; sem isso, vale o padrão do componente. */\n decimals?: number\n}\n\nexport type DimensionDef = FieldMeta & {\n sql: string\n type: 'number' | 'string'\n}\n\n/**\n * Agregações sobre uma expressão SQL (`tabela.coluna`). Todas valem nos dois formatos; no estrela,\n * agregam no grão da tabela de origem, como a soma.\n */\nexport const AGGREGATIONS = ['sum', 'count', 'countDistinct', 'avg', 'min', 'max'] as const\nexport type Aggregation = (typeof AGGREGATIONS)[number]\n\nexport const AGGREGATION_LABEL: Record<Aggregation, string> = {\n sum: 'Soma',\n count: 'Contagem',\n countDistinct: 'Contagem distinta',\n avg: 'Média',\n min: 'Mínimo',\n max: 'Máximo'\n}\n\n/** `COUNT` conta os valores não nulos; para contar linhas, use uma coluna que nunca é nula. */\nexport type AggregateMeasure = MeasureMeta & { type: Aggregation; sql: string }\nexport type SumMeasure = AggregateMeasure & { type: 'sum' }\nexport type RatioMeasure = MeasureMeta & { type: 'ratio'; numerator: string; denominator: string }\n/**\n * Combinação de outras medidas, já agregadas: `{nome}` vira o valor da medida (como `[Medida]` no\n * DAX). É o jeito de juntar medidas de fatos diferentes: `COALESCE({custoMmo}, 0) + COALESCE({custoAppa}, 0)`.\n */\nexport type FormulaMeasure = MeasureMeta & { type: 'formula'; expression: string }\nexport type MeasureDef = AggregateMeasure | RatioMeasure | FormulaMeasure\n\nexport const isAggregate = (measure: { type: unknown }): measure is AggregateMeasure =>\n AGGREGATIONS.includes(measure.type as Aggregation)\n\n/** O SQL agregado da medida: `SUM(x)`, `COUNT(DISTINCT x)`, `AVG(x)`... */\nexport function aggregateSql(measure: Pick<AggregateMeasure, 'type' | 'sql'>): string {\n if (measure.type === 'countDistinct') return `COUNT(DISTINCT ${measure.sql})`\n const fn = { sum: 'SUM', count: 'COUNT', avg: 'AVG', min: 'MIN', max: 'MAX' }[measure.type]\n return `${fn}(${measure.sql})`\n}\n\n/**\n * Tabela do modelo: o SQL é a \"consulta de origem\" (tabela, view ou SELECT). Não há tipo fato ou\n * dimensão: o papel sai dos relacionamentos (quem está no lado 1 filtra quem está no lado N) e de\n * onde ficam as medidas.\n */\nexport type TableDef = FieldMeta & {\n sql: string\n /** Coluna única da tabela; o diagrama usa ela para decidir o lado 1 ao criar relacionamentos. */\n key?: string\n}\n\n/** Relacionamento N:1 — o filtro do lado 1 (`to`) chega às linhas do lado N (`from`). */\nexport type RelationshipDef = {\n /** `tabela.coluna` do lado N (os lançamentos). */\n from: string\n /** `tabela.coluna` do lado 1 (a chave do cadastro). */\n to: string\n /**\n * \"Pressupor integridade referencial\" do Power BI: INNER JOIN em vez de LEFT JOIN, e a tabela do\n * lado 1 entra sempre na consulta da fato. Linhas do lado N sem par no lado 1 ficam de fora (em\n * vez de virar um grupo \"em branco\") — é o jeito de um filtro fixo da dimensão valer para a fato.\n */\n inner?: boolean\n}\n\n/**\n * Modelo estrela: `tables` (fato/dimensão) + `relationships`. Dimensões e medidas citam as colunas\n * como `tabela.coluna`; cada medida de agregação agrega no grão da própria tabela e chega às\n * dimensões seguindo os relacionamentos.\n */\nexport type ModelDef = {\n name: string\n tables: Record<string, TableDef>\n relationships?: RelationshipDef[]\n dimensions: Record<string, DimensionDef>\n measures: Record<string, MeasureDef>\n /**\n * Posição de cada card no diagrama do devtools (tabela -> canto superior esquerdo) e, se foi\n * redimensionado, o tamanho. Só visual: não muda nenhuma consulta. Card sem posição entra no\n * layout automático; sem tamanho, tem a largura padrão e a altura do conteúdo.\n */\n diagram?: Record<string, DiagramPosition>\n}\n\nexport type DiagramPosition = { x: number; y: number; width?: number; height?: number }\n\n/** Nomes citados como `{nome}` numa fórmula. */\nexport const formulaRefs = (expression: string) => [\n ...new Set([...expression.matchAll(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g)].map((match) => match[1]))\n]\n\n/** Medidas que uma medida usa diretamente (razão e fórmula). */\nexport function measureDeps(measure: MeasureDef): string[] {\n if (measure.type === 'ratio') return [measure.numerator, measure.denominator]\n if (measure.type === 'formula') return formulaRefs(measure.expression)\n return []\n}\n\nconst IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nconst isText = (value: unknown): value is string => typeof value === 'string' && !!value.trim()\n\nfunction metaErrors(context: string, field: Record<string, unknown>): string[] {\n const errors: string[] = []\n for (const key of ['label', 'description'] as const) {\n if (field[key] !== undefined && typeof field[key] !== 'string')\n errors.push(`${context}: \\`${key}\\` precisa ser texto`)\n }\n if (field.format !== undefined && !VALUE_FORMATS.includes(field.format as ValueFormat))\n errors.push(`${context}: formato inválido \"${field.format}\"`)\n const { decimals } = field\n if (\n decimals !== undefined &&\n (typeof decimals !== 'number' || !Number.isInteger(decimals) || decimals < 0 || decimals > 10)\n )\n errors.push(`${context}: \\`decimals\\` precisa ser um inteiro entre 0 e 10`)\n return errors\n}\n\n/** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */\nexport type ModelMeta = {\n name: string\n /** `table`: tabela do modelo de onde a dimensão sai (o nome, sem o SQL). */\n dimensions: Record<string, FieldMeta & { type: DimensionDef['type']; table?: string }>\n measures: Record<\n string,\n MeasureMeta & {\n type: MeasureDef['type']\n numerator?: string\n denominator?: string\n /** Medidas que a fórmula combina (a expressão em si fica no servidor). */\n refs?: string[]\n }\n >\n}\n\nexport function modelMeta(model: ModelDef): ModelMeta {\n const tableNames = Object.keys(model.tables)\n const pick = <T extends FieldMeta & MeasureMeta>(field: T) => ({\n ...(field.label !== undefined && { label: field.label }),\n ...(field.description !== undefined && { description: field.description }),\n ...(field.format !== undefined && { format: field.format }),\n ...(field.decimals !== undefined && { decimals: field.decimals })\n })\n return {\n name: model.name,\n dimensions: Object.fromEntries(\n Object.entries(model.dimensions).map(([name, dim]) => [\n name,\n { type: dim.type, table: referencedTables(dim.sql, tableNames)[0], ...pick(dim) }\n ])\n ),\n measures: Object.fromEntries(\n Object.entries(model.measures).map(([name, measure]) => [\n name,\n {\n type: measure.type,\n // Contagem sem formato: inteiro (sem casas decimais)\n ...((measure.type === 'count' || measure.type === 'countDistinct') && {\n format: 'integer' as const\n }),\n ...pick(measure),\n ...(measure.type === 'ratio' && {\n numerator: measure.numerator,\n denominator: measure.denominator\n }),\n ...(measure.type === 'formula' && { refs: formulaRefs(measure.expression) })\n }\n ])\n )\n }\n}\n\n/**\n * Lista os problemas do modelo (vazio = válido). Confere também a forma do objeto, porque o rascunho\n * do devtools chega como JSON da request.\n */\nexport function validateModel(model: ModelDef): string[] {\n if (!isRecord(model)) return ['modelo inválido']\n if (!isRecord(model.dimensions) || !isRecord(model.measures))\n return ['`dimensions` e `measures` são obrigatórios']\n // Rascunho/arquivo antigo: o formato base + joins foi removido\n if ('base' in model || 'joins' in model)\n return ['o formato `base` + `joins` não existe mais: use `tables` + `relationships`']\n if (!isRecord(model.tables)) return ['`tables` é obrigatório']\n if (model.relationships !== undefined && !Array.isArray(model.relationships))\n return ['`relationships` precisa ser uma lista']\n\n if (model.diagram !== undefined && !diagramValid(model.diagram))\n return ['`diagram` precisa ser { nome: { x, y, width?, height? } } com números (tamanho > 0)']\n\n const errors: string[] = []\n const { tables } = model\n const names = [...Object.keys(model.dimensions), ...Object.keys(model.measures)]\n\n for (const name of [...names, ...Object.keys(tables)]) {\n if (!IDENTIFIER.test(name)) errors.push(`nome inválido: \"${name}\"`)\n else if (name.startsWith('__')) errors.push(`nome reservado: \"${name}\" (começa com \"__\")`)\n }\n\n const duplicated = names.find((name, index) => names.indexOf(name) !== index)\n if (duplicated) errors.push(`nome repetido: \"${duplicated}\"`)\n\n for (const [name, table] of Object.entries(tables)) {\n if (!isRecord(table) || !isText(table.sql)) {\n errors.push(`tabela \"${name}\" sem SQL`)\n continue\n }\n if (table.key !== undefined && (typeof table.key !== 'string' || !IDENTIFIER.test(table.key)))\n errors.push(`tabela \"${name}\": key inválida`)\n errors.push(...metaErrors(`tabela \"${name}\"`, table))\n }\n\n const tableNames = Object.keys(tables)\n errors.push(...relationshipErrors(model, tableNames))\n\n for (const [name, dimension] of Object.entries(model.dimensions)) {\n if (!isRecord(dimension) || !isText(dimension.sql)) errors.push(`dimensão \"${name}\" sem SQL`)\n else if (dimension.type !== 'number' && dimension.type !== 'string')\n errors.push(`dimensão \"${name}\" com tipo inválido`)\n else {\n errors.push(...metaErrors(`dimensão \"${name}\"`, dimension))\n if (referencedTables(dimension.sql, tableNames).length !== 1)\n errors.push(`dimensão \"${name}\" precisa usar colunas de uma tabela só (\\`tabela.coluna\\`)`)\n }\n }\n\n const edges = modelEdges(model)\n for (const [name, measure] of Object.entries(model.measures)) {\n if (!isRecord(measure)) {\n errors.push(`medida \"${name}\" inválida`)\n } else if (isAggregate(measure)) {\n if (!isText(measure.sql)) errors.push(`medida \"${name}\" sem SQL`)\n else {\n const used = referencedTables(measure.sql, tableNames)\n if (!used.length)\n errors.push(`medida \"${name}\" precisa usar colunas de uma tabela (\\`tabela.coluna\\`)`)\n else if (!measureHome(used, edges))\n errors.push(\n `medida \"${name}\" usa ${used.join(', ')}, mas nenhuma delas chega às outras pelos relacionamentos`\n )\n }\n } else if (measure.type === 'ratio' || measure.type === 'formula') {\n if (measure.type === 'formula' && !isText(measure.expression))\n errors.push(`medida \"${name}\" sem expressão`)\n else if (measure.type === 'formula' && !formulaRefs(measure.expression).length)\n errors.push(`medida \"${name}\": a fórmula precisa citar medidas como {nome}`)\n for (const ref of measureDeps(measure)) {\n if (typeof ref !== 'string' || !model.measures[ref])\n errors.push(`medida \"${name}\" referencia \"${ref}\", que não existe`)\n }\n } else {\n errors.push(`medida \"${name}\" com tipo inválido`)\n }\n if (isRecord(measure)) errors.push(...metaErrors(`medida \"${name}\"`, measure))\n }\n if (errors.length) return errors\n\n // Medidas circulares (a -> b -> a) só estourariam na hora de compilar a consulta\n const visit = (name: string, stack: string[]): string | null => {\n if (stack.includes(name)) return [...stack, name].join(' -> ')\n for (const dep of measureDeps(model.measures[name])) {\n const cycle = visit(dep, [...stack, name])\n if (cycle) return cycle\n }\n return null\n }\n for (const name of Object.keys(model.measures)) {\n const cycle = visit(name, [])\n if (cycle) {\n errors.push(`medida circular: ${cycle}`)\n break\n }\n }\n\n return errors\n}\n\nconst diagramValid = (diagram: unknown) =>\n isRecord(diagram) &&\n Object.values(diagram).every(\n (point) =>\n isRecord(point) &&\n Number.isFinite(point.x) &&\n Number.isFinite(point.y) &&\n [point.width, point.height].every(\n (size) => size === undefined || (Number.isFinite(size) && (size as number) > 0)\n )\n )\n\nfunction relationshipErrors(model: ModelDef, tableNames: string[]): string[] {\n const errors: string[] = []\n const seen = new Set<string>()\n for (const [index, relationship] of (model.relationships ?? []).entries()) {\n const context = `relacionamento ${index + 1}`\n if (!isRecord(relationship)) {\n errors.push(`${context} inválido`)\n continue\n }\n const from = parseColumnRef(relationship.from)\n const to = parseColumnRef(relationship.to)\n if (!from || !to) {\n errors.push(`${context}: use \\`tabela.coluna\\` em \\`from\\` e \\`to\\``)\n continue\n }\n for (const ref of [from, to])\n if (!tableNames.includes(ref.table))\n errors.push(`${context}: tabela \"${ref.table}\" não existe`)\n if (from.table === to.table)\n errors.push(`${context}: liga a tabela \"${from.table}\" a ela mesma`)\n if (relationship.inner !== undefined && typeof relationship.inner !== 'boolean')\n errors.push(`${context}: \\`inner\\` precisa ser true/false`)\n const pair = `${from.table}->${to.table}`\n if (seen.has(pair))\n errors.push(`${context}: já existe um relacionamento de \"${from.table}\" para \"${to.table}\"`)\n seen.add(pair)\n }\n if (errors.length) return errors\n\n // Dois caminhos até a mesma tabela deixariam o filtro ambíguo (o Power BI deixa um inativo)\n const edges = modelEdges(model)\n for (const table of tableNames) {\n const { ambiguous, paths } = reachFrom(table, edges)\n if (ambiguous.length) {\n errors.push(\n `caminho ambíguo de \"${table}\" até \"${ambiguous[0]}\": há mais de um relacionamento levando lá`\n )\n break\n }\n // Ciclo: a tabela alcança a si mesma\n if (edges.some((edge) => edge.to.table === table && paths.has(edge.from.table))) {\n errors.push(`relacionamentos em ciclo passando por \"${table}\"`)\n break\n }\n }\n return errors\n}\n","import {\n type DiagramPosition,\n type FieldMeta,\n isAggregate,\n type MeasureDef,\n type MeasureMeta,\n type ModelDef,\n type TableDef\n} from './model'\n\n/**\n * Gera o código TS de um modelo (`defineModel({...})`). Usado pelo \"Copiar TS\" do devtools e pelo\n * \"Salvar no arquivo\" do servidor, que troca só a chamada do `defineModel` dentro do arquivo.\n */\n\nconst diagramCode = ({ x, y, width, height }: DiagramPosition) =>\n `{ x: ${x}, y: ${y}${width === undefined ? '' : `, width: ${width}`}${height === undefined ? '' : `, height: ${height}`} }`\n\n/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */\nexport function canonicalJson(value: unknown): string {\n return JSON.stringify(value, (_, item) =>\n item && typeof item === 'object' && !Array.isArray(item)\n ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n : item\n )\n}\n\nexport function literal(text: string) {\n if (text.includes('\\n'))\n return `\\`${text.replaceAll('\\\\', '\\\\\\\\').replaceAll('`', '\\\\`').replaceAll('${', '\\\\${')}\\``\n return `'${text.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\")}'`\n}\n\nfunction metaEntries(field: FieldMeta & MeasureMeta) {\n const entries: string[] = []\n if (field.label !== undefined) entries.push(`label: ${literal(field.label)}`)\n if (field.description !== undefined) entries.push(`description: ${literal(field.description)}`)\n if (field.format !== undefined) entries.push(`format: '${field.format}'`)\n if (field.decimals !== undefined) entries.push(`decimals: ${field.decimals}`)\n return entries\n}\n\nfunction measureCode(measure: MeasureDef) {\n const meta = metaEntries(measure)\n const args = isAggregate(measure)\n ? [literal(measure.sql)]\n : measure.type === 'formula'\n ? [literal(measure.expression)]\n : [literal(measure.numerator), literal(measure.denominator)]\n if (meta.length) args.push(`{ ${meta.join(', ')} }`)\n return `${measure.type}(${args.join(', ')})`\n}\n\nfunction tableCode(table: TableDef) {\n const fields = [\n ...(table.key !== undefined ? [`key: ${literal(table.key)}`] : []),\n `sql: ${literal(table.sql)}`,\n ...metaEntries(table)\n ]\n return `{ ${fields.join(', ')} }`\n}\n\nconst entries = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.entries(record)\n .map(([name, value]) => ` ${name}: ${render(value)}`)\n .join(',\\n')\n\n/** `{ ... }` com uma entrada por linha, ou `{}` quando vazio (modelo recém-criado). */\nconst block = <T>(record: Record<string, T>, render: (value: T) => string) =>\n Object.keys(record).length ? `{\\n${entries(record, render)}\\n }` : '{}'\n\n/** O objeto passado ao `defineModel`, começando em `{` (sem indentação na primeira linha). */\nexport function modelObjectCode(model: ModelDef): string {\n const lines = ['{', ` name: ${literal(model.name)},`]\n lines.push(` tables: ${block(model.tables, tableCode)},`)\n if (model.relationships?.length)\n lines.push(\n ` relationships: [\\n${model.relationships\n .map(\n (relationship) =>\n ` { from: ${literal(relationship.from)}, to: ${literal(relationship.to)}${relationship.inner ? ', inner: true' : ''} }`\n )\n .join(',\\n')}\\n ],`\n )\n lines.push(\n ` dimensions: ${block(model.dimensions, (dim) =>\n [`{ sql: ${literal(dim.sql)}`, `type: '${dim.type}'`, ...metaEntries(dim)]\n .join(', ')\n .concat(' }')\n )},`,\n ` measures: ${block(model.measures, measureCode)}${model.diagram ? ',' : ''}`\n )\n if (model.diagram) lines.push(` diagram: {\\n${entries(model.diagram, diagramCode)}\\n }`)\n lines.push('}')\n return lines.join('\\n')\n}\n\n/** Helpers do `@bluemarble/bi/server` que o código do modelo usa. */\nexport function modelImports(model: ModelDef) {\n // O helper tem o nome do tipo (`sum(...)`, `countDistinct(...)`, `formula(...)`)\n const types = new Set(Object.values(model.measures).map((measure) => measure.type))\n return ['defineModel', ...types].sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }))\n}\n\n/** Arquivo completo, pronto para colar em `src/dashboard/models/<nome>.ts`. */\nexport function toModelCode(model: ModelDef): string {\n return [\n `import { ${modelImports(model).join(', ')} } from '@bluemarble/bi/server'`,\n '',\n `export const ${model.name} = defineModel(${modelObjectCode(model)})`,\n ''\n ].join('\\n')\n}\n"],"mappings":"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"}
|
|
@@ -101,8 +101,10 @@ type DiagramPosition = {
|
|
|
101
101
|
/** Só os metadados (sem SQL): é o que o client recebe fora do devtools. */
|
|
102
102
|
type ModelMeta = {
|
|
103
103
|
name: string;
|
|
104
|
+
/** `table`: tabela do modelo de onde a dimensão sai (o nome, sem o SQL). */
|
|
104
105
|
dimensions: Record<string, FieldMeta & {
|
|
105
106
|
type: DimensionDef['type'];
|
|
107
|
+
table?: string;
|
|
106
108
|
}>;
|
|
107
109
|
measures: Record<string, MeasureMeta & {
|
|
108
110
|
type: MeasureDef['type'];
|
|
@@ -119,209 +121,6 @@ declare function modelMeta(model: ModelDef): ModelMeta;
|
|
|
119
121
|
*/
|
|
120
122
|
declare function validateModel(model: ModelDef): string[];
|
|
121
123
|
//#endregion
|
|
122
|
-
//#region src/types.d.ts
|
|
123
|
-
type FilterValue = string | number;
|
|
124
|
-
/** Filtros por dimensão; lista vazia (ou ausente) = sem filtro ("Todos"). */
|
|
125
|
-
type DashboardFilters = Record<string, FilterValue[]>;
|
|
126
|
-
/**
|
|
127
|
-
* Pedidos do devtools. O servidor só aceita quando o `runQuery` recebe `devtools` (em dev); fora
|
|
128
|
-
* disso `debug` é ignorado e o resto é recusado com 400.
|
|
129
|
-
*/
|
|
130
|
-
type DashboardDevtoolsRequest = {
|
|
131
|
-
/** Devolve SQL compilado, parâmetros e tempos em `result.devtools.debug`. */
|
|
132
|
-
debug?: boolean;
|
|
133
|
-
/** Rascunho do modelo editado no devtools: substitui o modelo registrado só nesta consulta. */
|
|
134
|
-
model?: ModelDef;
|
|
135
|
-
/** Devolve a definição do modelo registrado e o que o servidor permite (`capabilities`). */
|
|
136
|
-
describe?: boolean;
|
|
137
|
-
/** Esvazia o cache de resultados do processo antes de executar. */
|
|
138
|
-
clearCache?: boolean;
|
|
139
|
-
/**
|
|
140
|
-
* Só compila: devolve o SQL e os parâmetros em `result.devtools.debug`, sem executar (o devtools
|
|
141
|
-
* usa para mostrar o SQL de uma consulta que deu erro).
|
|
142
|
-
*/
|
|
143
|
-
compile?: boolean;
|
|
144
|
-
/** Devolve o `EXPLAIN` da consulta em vez das linhas. */
|
|
145
|
-
explain?: boolean;
|
|
146
|
-
/** Mede a agregação de cada fato usada pela consulta, isoladamente. */
|
|
147
|
-
profile?: boolean;
|
|
148
|
-
/** Tabelas e colunas do banco (`information_schema`), para o autocomplete do editor. */
|
|
149
|
-
schema?: boolean;
|
|
150
|
-
/** Colunas de cada tabela (uma linha de cada), para o diagrama do modelo. */
|
|
151
|
-
columns?: boolean;
|
|
152
|
-
/** Executa cada pedaço do modelo (`devtools.model` ou o registrado) contra o banco. */
|
|
153
|
-
validate?: boolean;
|
|
154
|
-
/** Grava o rascunho no arquivo do modelo (`preview` só devolve o conteúdo novo). */
|
|
155
|
-
save?: 'preview' | 'write';
|
|
156
|
-
/** Grava o layout de gráficos em `<layoutsDir>/<id>.json` (`preview` só devolve o conteúdo novo). */
|
|
157
|
-
saveLayout?: {
|
|
158
|
-
layout: ChartsLayout;
|
|
159
|
-
mode: 'preview' | 'write';
|
|
160
|
-
};
|
|
161
|
-
/** Grava o resultado da consulta (modelo registrado) como snapshot de regressão. */
|
|
162
|
-
snapshot?: {
|
|
163
|
-
name: string;
|
|
164
|
-
overwrite?: boolean;
|
|
165
|
-
};
|
|
166
|
-
/** Lista os snapshots salvos deste modelo. */
|
|
167
|
-
snapshots?: boolean;
|
|
168
|
-
};
|
|
169
|
-
type DashboardQuery = {
|
|
170
|
-
model: string;
|
|
171
|
-
/** Dimensões agrupadas (viram GROUP BY). */
|
|
172
|
-
dimensions?: string[];
|
|
173
|
-
/** Medidas calculadas para cada grupo. */
|
|
174
|
-
measures?: string[];
|
|
175
|
-
filters?: DashboardFilters;
|
|
176
|
-
/** Devolve só os metadados do modelo (rótulos, descrições, formatos; sem SQL) em `result.meta`. */
|
|
177
|
-
meta?: boolean;
|
|
178
|
-
/**
|
|
179
|
-
* Várias consultas do mesmo modelo num request (o client manda assim as consultas de um render).
|
|
180
|
-
* O servidor junta numa SQL só as que têm as mesmas dimensões e filtros e vêm das mesmas fatos, e
|
|
181
|
-
* devolve uma resposta por consulta em `result.results`, na mesma ordem. Do `devtools`, só
|
|
182
|
-
* `debug` e `model` (rascunho) valem aqui.
|
|
183
|
-
*/
|
|
184
|
-
queries?: DashboardBatchQuery[];
|
|
185
|
-
devtools?: DashboardDevtoolsRequest;
|
|
186
|
-
};
|
|
187
|
-
/** Uma consulta de um lote (`DashboardQuery.queries`): o modelo e o devtools são os do lote. */
|
|
188
|
-
type DashboardBatchQuery = Pick<DashboardQuery, 'dimensions' | 'measures' | 'filters'>;
|
|
189
|
-
/** Resposta de uma consulta do lote: as linhas ou o erro (de consulta) dela. */
|
|
190
|
-
type DashboardBatchResult = {
|
|
191
|
-
rows: DashboardRow[];
|
|
192
|
-
error?: string;
|
|
193
|
-
/** Quantas consultas do lote saíram da mesma SQL (contando esta); o `debug` é o dessa SQL. */
|
|
194
|
-
batched?: number;
|
|
195
|
-
devtools?: {
|
|
196
|
-
debug?: DashboardQueryDebug;
|
|
197
|
-
};
|
|
198
|
-
};
|
|
199
|
-
type DashboardRow = Record<string, string | number | null>;
|
|
200
|
-
type DashboardQueryDebug = {
|
|
201
|
-
sql: string;
|
|
202
|
-
params: (string | number)[];
|
|
203
|
-
/** Tempo da execução no banco (num cache hit, o da execução original). */
|
|
204
|
-
durationMs: number;
|
|
205
|
-
cached: boolean;
|
|
206
|
-
/** A consulta rodou contra o rascunho do devtools, não contra o modelo registrado. */
|
|
207
|
-
draft: boolean;
|
|
208
|
-
};
|
|
209
|
-
type ProfileStep = {
|
|
210
|
-
/** `fato:<tabela>` ou `consulta` (a consulta inteira). */
|
|
211
|
-
name: string;
|
|
212
|
-
durationMs: number;
|
|
213
|
-
/** Linhas da agregação da fato (grupos) com os filtros aplicados. */
|
|
214
|
-
rows: number | null;
|
|
215
|
-
};
|
|
216
|
-
type SchemaTable = {
|
|
217
|
-
name: string;
|
|
218
|
-
type: 'table' | 'view';
|
|
219
|
-
columns: {
|
|
220
|
-
name: string;
|
|
221
|
-
type: string;
|
|
222
|
-
}[];
|
|
223
|
-
};
|
|
224
|
-
/** Colunas devolvidas por cada pedaço do modelo (vazio quando não veio nenhuma linha). */
|
|
225
|
-
type ModelColumns = {
|
|
226
|
-
/** Colunas de cada tabela. */
|
|
227
|
-
tables: Record<string, string[]>;
|
|
228
|
-
/** `tabela.coluna` com valor numérico na linha lida. */
|
|
229
|
-
numeric: string[];
|
|
230
|
-
};
|
|
231
|
-
type ModelValidation = {
|
|
232
|
-
/**
|
|
233
|
-
* Erro do banco por pedaço: `table:<nome>`, `relationship:<índice>`, `dimension:<nome>`,
|
|
234
|
-
* `measure:<nome>`.
|
|
235
|
-
*/
|
|
236
|
-
errors: Record<string, string>;
|
|
237
|
-
warnings: Record<string, string>;
|
|
238
|
-
columns: ModelColumns;
|
|
239
|
-
};
|
|
240
|
-
type SaveModelResult = {
|
|
241
|
-
path: string;
|
|
242
|
-
previous: string;
|
|
243
|
-
content: string;
|
|
244
|
-
written: boolean;
|
|
245
|
-
warnings: string[];
|
|
246
|
-
};
|
|
247
|
-
type DashboardSnapshot = {
|
|
248
|
-
name: string;
|
|
249
|
-
createdAt: string;
|
|
250
|
-
query: Omit<DashboardQuery, 'devtools' | 'meta'>;
|
|
251
|
-
rows: DashboardRow[];
|
|
252
|
-
};
|
|
253
|
-
type DevtoolsCapabilities = {
|
|
254
|
-
/** O endpoint sabe onde ficam os arquivos dos modelos (`modelsDir`). */
|
|
255
|
-
saveModel: boolean;
|
|
256
|
-
/** O endpoint sabe onde gravar snapshots (`snapshotsDir`). */
|
|
257
|
-
snapshots: boolean;
|
|
258
|
-
/** O endpoint sabe onde ficam os arquivos de layout de gráficos (`layoutsDir`). */
|
|
259
|
-
saveLayout: boolean;
|
|
260
|
-
/** Tipos de gráfico do app que o endpoint conhece (`chartTypes`): só esses ele aceita salvar. */
|
|
261
|
-
chartTypes: string[];
|
|
262
|
-
};
|
|
263
|
-
type DashboardResult = {
|
|
264
|
-
rows: DashboardRow[];
|
|
265
|
-
/** Respostas de um lote (`query.queries`), na ordem das consultas. */
|
|
266
|
-
results?: DashboardBatchResult[];
|
|
267
|
-
/**
|
|
268
|
-
* Quantas consultas saíram da mesma SQL que esta (contando ela), quando foi num lote. O
|
|
269
|
-
* `devtools.debug` é o dessa SQL.
|
|
270
|
-
*/
|
|
271
|
-
batched?: number;
|
|
272
|
-
meta?: ModelMeta;
|
|
273
|
-
devtools?: {
|
|
274
|
-
model?: ModelDef;
|
|
275
|
-
capabilities?: DevtoolsCapabilities;
|
|
276
|
-
debug?: DashboardQueryDebug;
|
|
277
|
-
explain?: DashboardRow[];
|
|
278
|
-
profile?: ProfileStep[];
|
|
279
|
-
schema?: SchemaTable[];
|
|
280
|
-
validation?: ModelValidation;
|
|
281
|
-
columns?: ModelColumns;
|
|
282
|
-
/** Pedaços que o banco recusou ao ler as colunas (mesmas chaves da validação). */
|
|
283
|
-
columnErrors?: Record<string, string>;
|
|
284
|
-
save?: SaveModelResult;
|
|
285
|
-
snapshot?: {
|
|
286
|
-
path: string;
|
|
287
|
-
};
|
|
288
|
-
snapshots?: DashboardSnapshot[];
|
|
289
|
-
};
|
|
290
|
-
};
|
|
291
|
-
//#endregion
|
|
292
|
-
//#region src/formatRules.d.ts
|
|
293
|
-
declare const COLUMN_ALIGNS: readonly ['left', 'center', 'right'];
|
|
294
|
-
type ColumnAlign = (typeof COLUMN_ALIGNS)[number];
|
|
295
|
-
declare const COLUMN_VERTICAL_ALIGNS: readonly ['top', 'middle', 'bottom'];
|
|
296
|
-
type ColumnVerticalAlign = (typeof COLUMN_VERTICAL_ALIGNS)[number];
|
|
297
|
-
declare const RULE_OPERATORS: readonly ['>', '>=', '<', '<=', '=', '!=', 'between', 'contains', 'empty', 'notEmpty'];
|
|
298
|
-
type RuleOperator = (typeof RULE_OPERATORS)[number];
|
|
299
|
-
type FormatRule = {
|
|
300
|
-
/**
|
|
301
|
-
* Dimensão ou medida avaliada. Pode estar fora da tabela: entra na consulta sem coluna (uma
|
|
302
|
-
* dimensão de fora divide as linhas se tiver mais de um valor por linha).
|
|
303
|
-
*/
|
|
304
|
-
field: string;
|
|
305
|
-
operator: RuleOperator;
|
|
306
|
-
/** Valor comparado com o valor bruto (percentual como fração: 10% = 0.1). */
|
|
307
|
-
value?: number | string;
|
|
308
|
-
/** Fim do intervalo do `between` (inclusivo, como o `value`). */
|
|
309
|
-
to?: number;
|
|
310
|
-
/** `cell` (padrão) pinta uma célula; `row`, a linha inteira. */
|
|
311
|
-
target?: 'cell' | 'row';
|
|
312
|
-
/** Coluna pintada quando `target` é `cell`. Padrão: o próprio `field`. */
|
|
313
|
-
column?: string;
|
|
314
|
-
/** Cor do texto. */
|
|
315
|
-
color?: string;
|
|
316
|
-
/** Cor de fundo. */
|
|
317
|
-
background?: string;
|
|
318
|
-
bold?: boolean;
|
|
319
|
-
/** Alinhamento horizontal da célula (ou das células da linha). */
|
|
320
|
-
align?: ColumnAlign;
|
|
321
|
-
/** Alinhamento vertical da célula (ou das células da linha). */
|
|
322
|
-
verticalAlign?: ColumnVerticalAlign;
|
|
323
|
-
};
|
|
324
|
-
//#endregion
|
|
325
124
|
//#region src/layout.d.ts
|
|
326
125
|
declare const CHART_TYPES: readonly ['kpi', 'combo', 'bar', 'donut', 'table'];
|
|
327
126
|
type ChartType = (typeof CHART_TYPES)[number];
|
|
@@ -396,8 +195,32 @@ type BarChartDef = BaseChartDef & ColumnsDisplay & {
|
|
|
396
195
|
formatDimension?: string;
|
|
397
196
|
clickToFilter?: boolean;
|
|
398
197
|
hideEmpty?: boolean;
|
|
198
|
+
/** Todos os valores da dimensão no eixo, com ou sem dado (sobrepõe `hideEmpty`). */
|
|
199
|
+
showEmpty?: boolean;
|
|
399
200
|
showValues?: boolean;
|
|
201
|
+
/** Onde fica o valor de cada barra. Padrão: `outside` (na ponta, fora da barra). */
|
|
202
|
+
valuePosition?: BarValuePosition;
|
|
203
|
+
/** Cor do valor. Padrão: escuro fora da barra, branco dentro. */
|
|
204
|
+
valueColor?: string;
|
|
205
|
+
/** Tamanho do valor, em px. Padrão: 12. */
|
|
206
|
+
valueSize?: number;
|
|
207
|
+
/** Empilhado: total da pilha na ponta. */
|
|
208
|
+
showTotal?: boolean;
|
|
209
|
+
/** Só mostra o valor (e o total) que atende à regra, ex.: `{ operator: '!=', value: 0 }`. */
|
|
210
|
+
valueRule?: ValueRule;
|
|
211
|
+
/**
|
|
212
|
+
* Com `clickToFilter`, o filtro da própria dimensão (e da série) esconde o resto em vez de só
|
|
213
|
+
* apagá-lo. Padrão: `false` (apaga).
|
|
214
|
+
*/
|
|
215
|
+
hideUnselected?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Cor por série: `{ medida: '#rrggbb' }` ou, com `series`, `{ valor: '#rrggbb' }` (também
|
|
218
|
+
* "(em branco)" e "Outros"). O que faltar segue a paleta.
|
|
219
|
+
*/
|
|
220
|
+
colors?: Record<string, string>;
|
|
400
221
|
};
|
|
222
|
+
declare const BAR_VALUE_POSITIONS: readonly ['outside', 'insideEnd', 'center', 'insideBase'];
|
|
223
|
+
type BarValuePosition = (typeof BAR_VALUE_POSITIONS)[number];
|
|
401
224
|
type DonutChartDef = BaseChartDef & ColumnsDisplay & {
|
|
402
225
|
type: 'donut';
|
|
403
226
|
dimension: string;
|
|
@@ -408,7 +231,19 @@ type DonutChartDef = BaseChartDef & ColumnsDisplay & {
|
|
|
408
231
|
percentDecimals?: number;
|
|
409
232
|
formatDimension?: string;
|
|
410
233
|
clickToFilter?: boolean;
|
|
234
|
+
/** Lado da legenda. Padrão: `right` (com nome, % e valor; em cima/embaixo, só o nome). */
|
|
235
|
+
legendPosition?: DonutLegendPosition;
|
|
236
|
+
/** Valor de cada fatia do lado de fora da rosca. */
|
|
237
|
+
showValues?: boolean;
|
|
238
|
+
/** O que o valor de fora mostra. Padrão: `both` (`44.981 (51%)`). */
|
|
239
|
+
valueContent?: DonutValueContent;
|
|
240
|
+
/** Total no furo da rosca. Padrão: `true`. */
|
|
241
|
+
showTotal?: boolean;
|
|
411
242
|
};
|
|
243
|
+
declare const DONUT_LEGEND_POSITIONS: readonly ['right', 'left', 'top', 'bottom'];
|
|
244
|
+
type DonutLegendPosition = (typeof DONUT_LEGEND_POSITIONS)[number];
|
|
245
|
+
declare const DONUT_VALUE_CONTENTS: readonly ['both', 'value', 'percent'];
|
|
246
|
+
type DonutValueContent = (typeof DONUT_VALUE_CONTENTS)[number];
|
|
412
247
|
type TableChartDef = BaseChartDef & ColumnsDisplay & {
|
|
413
248
|
type: 'table';
|
|
414
249
|
dimensions?: string[];
|
|
@@ -505,7 +340,11 @@ type CardStyle = {
|
|
|
505
340
|
};
|
|
506
341
|
/** Cards de todos os gráficos do layout: estilo padrão e espaço entre eles. */
|
|
507
342
|
type CardsStyle = CardStyle & {
|
|
508
|
-
/** Espaço entre
|
|
343
|
+
/** Espaço entre as linhas de cards, em px. */
|
|
344
|
+
rowGap?: number;
|
|
345
|
+
/** Espaço entre as colunas de cards, em px. */
|
|
346
|
+
columnGap?: number;
|
|
347
|
+
/** Espaço entre linhas e colunas, em px (formato antigo; `rowGap`/`columnGap` têm prioridade). */
|
|
509
348
|
gap?: number;
|
|
510
349
|
};
|
|
511
350
|
type ChartsLayout = {
|
|
@@ -523,7 +362,7 @@ type FieldSpec = {
|
|
|
523
362
|
label: string;
|
|
524
363
|
hint?: string;
|
|
525
364
|
/** Agrupamento no formulário do devtools. */
|
|
526
|
-
section: 'Gráfico' | 'Dados' | 'Aparência' | 'Rótulos e formatos' | 'Formatação condicional' | 'Filtro' | 'Tela' | 'Card';
|
|
365
|
+
section: 'Gráfico' | 'Dados' | 'Aparência' | 'Valores' | 'Comportamento' | 'Rótulos e formatos' | 'Formatação condicional' | 'Filtro' | 'Tela' | 'Card';
|
|
527
366
|
required?: boolean;
|
|
528
367
|
/** Valor que o componente usa quando o campo não está no JSON. */
|
|
529
368
|
default?: unknown;
|
|
@@ -602,6 +441,18 @@ type FieldSpec = {
|
|
|
602
441
|
/** Regras de formatação condicional (`FormatRule[]`). */
|
|
603
442
|
{
|
|
604
443
|
kind: 'formatRules';
|
|
444
|
+
} |
|
|
445
|
+
/**
|
|
446
|
+
* `{ série: '#rrggbb' }`: uma cor por medida do gráfico ou, quando o campo `dimension` em
|
|
447
|
+
* `values` está preenchido, por valor dessa dimensão (o devtools não lista os valores; digita).
|
|
448
|
+
*/
|
|
449
|
+
{
|
|
450
|
+
kind: 'colors';
|
|
451
|
+
values?: string;
|
|
452
|
+
} |
|
|
453
|
+
/** `ValueRule` (`{ operator, value, to? }`): ausente = sem regra (o devtools mostra uma checkbox). */
|
|
454
|
+
{
|
|
455
|
+
kind: 'valueRule';
|
|
605
456
|
});
|
|
606
457
|
/**
|
|
607
458
|
* Tipo de gráfico: nome no devtools e campos do JSON. Para um tipo do app, `title`, `span` e
|
|
@@ -646,5 +497,219 @@ declare function validateLayout(layout: ChartsLayout, types?: ChartTypes): strin
|
|
|
646
497
|
*/
|
|
647
498
|
declare function layoutModelWarnings(layout: ChartsLayout, model: Pick<ModelDef, 'dimensions' | 'measures'>, types?: ChartTypes): string[];
|
|
648
499
|
//#endregion
|
|
649
|
-
|
|
650
|
-
|
|
500
|
+
//#region src/types.d.ts
|
|
501
|
+
type FilterValue = string | number;
|
|
502
|
+
/** Filtros por dimensão; lista vazia (ou ausente) = sem filtro ("Todos"). */
|
|
503
|
+
type DashboardFilters = Record<string, FilterValue[]>;
|
|
504
|
+
/**
|
|
505
|
+
* Pedidos do devtools. O servidor só aceita quando o `runQuery` recebe `devtools` (em dev); fora
|
|
506
|
+
* disso `debug` é ignorado e o resto é recusado com 400.
|
|
507
|
+
*/
|
|
508
|
+
type DashboardDevtoolsRequest = {
|
|
509
|
+
/** Devolve SQL compilado, parâmetros e tempos em `result.devtools.debug`. */
|
|
510
|
+
debug?: boolean;
|
|
511
|
+
/** Rascunho do modelo editado no devtools: substitui o modelo registrado só nesta consulta. */
|
|
512
|
+
model?: ModelDef;
|
|
513
|
+
/** Devolve a definição do modelo registrado e o que o servidor permite (`capabilities`). */
|
|
514
|
+
describe?: boolean;
|
|
515
|
+
/** Esvazia o cache de resultados do processo antes de executar. */
|
|
516
|
+
clearCache?: boolean;
|
|
517
|
+
/**
|
|
518
|
+
* Só compila: devolve o SQL e os parâmetros em `result.devtools.debug`, sem executar (o devtools
|
|
519
|
+
* usa para mostrar o SQL de uma consulta que deu erro).
|
|
520
|
+
*/
|
|
521
|
+
compile?: boolean;
|
|
522
|
+
/** Devolve o `EXPLAIN` da consulta em vez das linhas. */
|
|
523
|
+
explain?: boolean;
|
|
524
|
+
/** Mede a agregação de cada fato usada pela consulta, isoladamente. */
|
|
525
|
+
profile?: boolean;
|
|
526
|
+
/** Tabelas e colunas do banco (`information_schema`), para o autocomplete do editor. */
|
|
527
|
+
schema?: boolean;
|
|
528
|
+
/** Colunas de cada tabela (uma linha de cada), para o diagrama do modelo. */
|
|
529
|
+
columns?: boolean;
|
|
530
|
+
/** Executa cada pedaço do modelo (`devtools.model` ou o registrado) contra o banco. */
|
|
531
|
+
validate?: boolean;
|
|
532
|
+
/** Grava o rascunho no arquivo do modelo (`preview` só devolve o conteúdo novo). */
|
|
533
|
+
save?: 'preview' | 'write';
|
|
534
|
+
/** Grava o layout de gráficos em `<layoutsDir>/<id>.json` (`preview` só devolve o conteúdo novo). */
|
|
535
|
+
saveLayout?: {
|
|
536
|
+
layout: ChartsLayout;
|
|
537
|
+
mode: 'preview' | 'write';
|
|
538
|
+
};
|
|
539
|
+
/** Grava o resultado da consulta (modelo registrado) como snapshot de regressão. */
|
|
540
|
+
snapshot?: {
|
|
541
|
+
name: string;
|
|
542
|
+
overwrite?: boolean;
|
|
543
|
+
};
|
|
544
|
+
/** Lista os snapshots salvos deste modelo. */
|
|
545
|
+
snapshots?: boolean;
|
|
546
|
+
};
|
|
547
|
+
type DashboardQuery = {
|
|
548
|
+
model: string;
|
|
549
|
+
/** Dimensões agrupadas (viram GROUP BY). */
|
|
550
|
+
dimensions?: string[];
|
|
551
|
+
/** Medidas calculadas para cada grupo. */
|
|
552
|
+
measures?: string[];
|
|
553
|
+
filters?: DashboardFilters;
|
|
554
|
+
/** Devolve só os metadados do modelo (rótulos, descrições, formatos; sem SQL) em `result.meta`. */
|
|
555
|
+
meta?: boolean;
|
|
556
|
+
/**
|
|
557
|
+
* Várias consultas do mesmo modelo num request (o client manda assim as consultas de um render).
|
|
558
|
+
* O servidor junta numa SQL só as que têm as mesmas dimensões e filtros e vêm das mesmas fatos, e
|
|
559
|
+
* devolve uma resposta por consulta em `result.results`, na mesma ordem. Do `devtools`, só
|
|
560
|
+
* `debug` e `model` (rascunho) valem aqui.
|
|
561
|
+
*/
|
|
562
|
+
queries?: DashboardBatchQuery[];
|
|
563
|
+
devtools?: DashboardDevtoolsRequest;
|
|
564
|
+
};
|
|
565
|
+
/** Uma consulta de um lote (`DashboardQuery.queries`): o modelo e o devtools são os do lote. */
|
|
566
|
+
type DashboardBatchQuery = Pick<DashboardQuery, 'dimensions' | 'measures' | 'filters'>;
|
|
567
|
+
/** Resposta de uma consulta do lote: as linhas ou o erro (de consulta) dela. */
|
|
568
|
+
type DashboardBatchResult = {
|
|
569
|
+
rows: DashboardRow[];
|
|
570
|
+
error?: string;
|
|
571
|
+
/** Quantas consultas do lote saíram da mesma SQL (contando esta); o `debug` é o dessa SQL. */
|
|
572
|
+
batched?: number;
|
|
573
|
+
devtools?: {
|
|
574
|
+
debug?: DashboardQueryDebug;
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
type DashboardRow = Record<string, string | number | null>;
|
|
578
|
+
type DashboardQueryDebug = {
|
|
579
|
+
sql: string;
|
|
580
|
+
params: (string | number)[];
|
|
581
|
+
/** Tempo da execução no banco (num cache hit, o da execução original). */
|
|
582
|
+
durationMs: number;
|
|
583
|
+
cached: boolean;
|
|
584
|
+
/** A consulta rodou contra o rascunho do devtools, não contra o modelo registrado. */
|
|
585
|
+
draft: boolean;
|
|
586
|
+
};
|
|
587
|
+
type ProfileStep = {
|
|
588
|
+
/** `fato:<tabela>` ou `consulta` (a consulta inteira). */
|
|
589
|
+
name: string;
|
|
590
|
+
durationMs: number;
|
|
591
|
+
/** Linhas da agregação da fato (grupos) com os filtros aplicados. */
|
|
592
|
+
rows: number | null;
|
|
593
|
+
};
|
|
594
|
+
type SchemaTable = {
|
|
595
|
+
name: string;
|
|
596
|
+
type: 'table' | 'view';
|
|
597
|
+
columns: {
|
|
598
|
+
name: string;
|
|
599
|
+
type: string;
|
|
600
|
+
}[];
|
|
601
|
+
};
|
|
602
|
+
/** Colunas devolvidas por cada pedaço do modelo (vazio quando não veio nenhuma linha). */
|
|
603
|
+
type ModelColumns = {
|
|
604
|
+
/** Colunas de cada tabela. */
|
|
605
|
+
tables: Record<string, string[]>;
|
|
606
|
+
/** `tabela.coluna` com valor numérico na linha lida. */
|
|
607
|
+
numeric: string[];
|
|
608
|
+
};
|
|
609
|
+
type ModelValidation = {
|
|
610
|
+
/**
|
|
611
|
+
* Erro do banco por pedaço: `table:<nome>`, `relationship:<índice>`, `dimension:<nome>`,
|
|
612
|
+
* `measure:<nome>`.
|
|
613
|
+
*/
|
|
614
|
+
errors: Record<string, string>;
|
|
615
|
+
warnings: Record<string, string>;
|
|
616
|
+
columns: ModelColumns;
|
|
617
|
+
};
|
|
618
|
+
type SaveModelResult = {
|
|
619
|
+
path: string;
|
|
620
|
+
previous: string;
|
|
621
|
+
content: string;
|
|
622
|
+
written: boolean;
|
|
623
|
+
warnings: string[];
|
|
624
|
+
};
|
|
625
|
+
type DashboardSnapshot = {
|
|
626
|
+
name: string;
|
|
627
|
+
createdAt: string;
|
|
628
|
+
query: Omit<DashboardQuery, 'devtools' | 'meta'>;
|
|
629
|
+
rows: DashboardRow[];
|
|
630
|
+
};
|
|
631
|
+
type DevtoolsCapabilities = {
|
|
632
|
+
/** O endpoint sabe onde ficam os arquivos dos modelos (`modelsDir`). */
|
|
633
|
+
saveModel: boolean;
|
|
634
|
+
/** O endpoint sabe onde gravar snapshots (`snapshotsDir`). */
|
|
635
|
+
snapshots: boolean;
|
|
636
|
+
/** O endpoint sabe onde ficam os arquivos de layout de gráficos (`layoutsDir`). */
|
|
637
|
+
saveLayout: boolean;
|
|
638
|
+
/** Tipos de gráfico do app que o endpoint conhece (`chartTypes`): só esses ele aceita salvar. */
|
|
639
|
+
chartTypes: string[];
|
|
640
|
+
};
|
|
641
|
+
type DashboardResult = {
|
|
642
|
+
rows: DashboardRow[];
|
|
643
|
+
/** Respostas de um lote (`query.queries`), na ordem das consultas. */
|
|
644
|
+
results?: DashboardBatchResult[];
|
|
645
|
+
/**
|
|
646
|
+
* Quantas consultas saíram da mesma SQL que esta (contando ela), quando foi num lote. O
|
|
647
|
+
* `devtools.debug` é o dessa SQL.
|
|
648
|
+
*/
|
|
649
|
+
batched?: number;
|
|
650
|
+
meta?: ModelMeta;
|
|
651
|
+
devtools?: {
|
|
652
|
+
model?: ModelDef;
|
|
653
|
+
capabilities?: DevtoolsCapabilities;
|
|
654
|
+
debug?: DashboardQueryDebug;
|
|
655
|
+
explain?: DashboardRow[];
|
|
656
|
+
profile?: ProfileStep[];
|
|
657
|
+
schema?: SchemaTable[];
|
|
658
|
+
validation?: ModelValidation;
|
|
659
|
+
columns?: ModelColumns;
|
|
660
|
+
/** Pedaços que o banco recusou ao ler as colunas (mesmas chaves da validação). */
|
|
661
|
+
columnErrors?: Record<string, string>;
|
|
662
|
+
save?: SaveModelResult;
|
|
663
|
+
snapshot?: {
|
|
664
|
+
path: string;
|
|
665
|
+
};
|
|
666
|
+
snapshots?: DashboardSnapshot[];
|
|
667
|
+
};
|
|
668
|
+
};
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/formatRules.d.ts
|
|
671
|
+
declare const COLUMN_ALIGNS: readonly ['left', 'center', 'right'];
|
|
672
|
+
type ColumnAlign = (typeof COLUMN_ALIGNS)[number];
|
|
673
|
+
declare const COLUMN_VERTICAL_ALIGNS: readonly ['top', 'middle', 'bottom'];
|
|
674
|
+
type ColumnVerticalAlign = (typeof COLUMN_VERTICAL_ALIGNS)[number];
|
|
675
|
+
declare const RULE_OPERATORS: readonly ['>', '>=', '<', '<=', '=', '!=', 'between', 'contains', 'empty', 'notEmpty'];
|
|
676
|
+
type RuleOperator = (typeof RULE_OPERATORS)[number];
|
|
677
|
+
type FormatRule = {
|
|
678
|
+
/**
|
|
679
|
+
* Dimensão ou medida avaliada. Pode estar fora da tabela: entra na consulta sem coluna (uma
|
|
680
|
+
* dimensão de fora divide as linhas se tiver mais de um valor por linha).
|
|
681
|
+
*/
|
|
682
|
+
field: string;
|
|
683
|
+
operator: RuleOperator;
|
|
684
|
+
/** Valor comparado com o valor bruto (percentual como fração: 10% = 0.1). */
|
|
685
|
+
value?: number | string;
|
|
686
|
+
/** Fim do intervalo do `between` (inclusivo, como o `value`). */
|
|
687
|
+
to?: number;
|
|
688
|
+
/** `cell` (padrão) pinta uma célula; `row`, a linha inteira. */
|
|
689
|
+
target?: 'cell' | 'row';
|
|
690
|
+
/** Coluna pintada quando `target` é `cell`. Padrão: o próprio `field`. */
|
|
691
|
+
column?: string;
|
|
692
|
+
/** Cor do texto. */
|
|
693
|
+
color?: string;
|
|
694
|
+
/** Cor de fundo. */
|
|
695
|
+
background?: string;
|
|
696
|
+
bold?: boolean;
|
|
697
|
+
/** Alinhamento horizontal da célula (ou das células da linha). */
|
|
698
|
+
align?: ColumnAlign;
|
|
699
|
+
/** Alinhamento vertical da célula (ou das células da linha). */
|
|
700
|
+
verticalAlign?: ColumnVerticalAlign;
|
|
701
|
+
};
|
|
702
|
+
/**
|
|
703
|
+
* Regra de um valor só (sem campo nem estilo), ex.: mostrar o rótulo da barra só quando o valor é
|
|
704
|
+
* diferente de zero: `{ "operator": "!=", "value": 0 }`.
|
|
705
|
+
*/
|
|
706
|
+
type ValueRule = {
|
|
707
|
+
operator: ValueRuleOperator;
|
|
708
|
+
value: number;
|
|
709
|
+
to?: number;
|
|
710
|
+
};
|
|
711
|
+
declare const VALUE_RULE_OPERATORS: readonly ['>', '>=', '<', '<=', '=', '!=', 'between'];
|
|
712
|
+
type ValueRuleOperator = (typeof VALUE_RULE_OPERATORS)[number];
|
|
713
|
+
//#endregion
|
|
714
|
+
export { ModelMeta as $, ChartTypeSpec as A, KpiChartDef as B, BuiltinChartDef as C, ChartDef as D, CardsStyle as E, DonutChartDef as F, AggregateMeasure as G, layoutModelWarnings as H, DonutLegendPosition as I, FieldMeta as J, Aggregation as K, DonutValueContent as L, ChartsLayout as M, ComboChartDef as N, ChartNumberFormat as O, CustomChartDef as P, ModelDef as Q, FieldSpec as R, BarValuePosition as S, CardStyle as T, resolveChartTypes as U, TableChartDef as V, validateLayout as W, MeasureDef as X, FormulaMeasure as Y, MeasureMeta as Z, ModelValidation as _, DashboardBatchQuery as a, modelMeta as at, SchemaTable as b, DashboardFilters as c, DashboardResult as d, RatioMeasure as et, DashboardRow as f, ModelColumns as g, FilterValue as h, ValueRule as i, ValueFormat as it, ChartTypes as j, ChartType as k, DashboardQuery as l, DevtoolsCapabilities as m, ColumnVerticalAlign as n, SumMeasure as nt, DashboardBatchResult as o, validateModel as ot, DashboardSnapshot as p, DimensionDef as q, FormatRule as r, TableDef as rt, DashboardDevtoolsRequest as s, ColumnAlign as t, RelationshipDef as tt, DashboardQueryDebug as u, ProfileStep as v, CanvasDef as w, BarChartDef as x, SaveModelResult as y, FilterDef as z };
|
|
715
|
+
//# sourceMappingURL=formatRules-DTKbov47.d.ts.map
|