@bluemarble/bi 0.6.2 → 0.6.3
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-BT9mJrmJ.js → DashboardDevtools-Osu5V30U.js} +8 -2
- package/dist/{DashboardDevtools-BT9mJrmJ.js.map → DashboardDevtools-Osu5V30U.js.map} +1 -1
- package/dist/{ModelDictionary-BlDeDyAE.js → ModelDictionary-Dc3_LKqr.js} +62 -8
- package/dist/ModelDictionary-Dc3_LKqr.js.map +1 -0
- package/dist/client/index.d.ts +10 -3
- package/dist/client/index.js +1 -1
- package/dist/{layout-B7eYgNyR.d.ts → layout-Bn1OiRov.d.ts} +28 -2
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +161 -4
- package/dist/server/index.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/ModelDictionary-BlDeDyAE.js.map +0 -1
|
@@ -115,6 +115,58 @@ function createLayoutStore() {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
//#endregion
|
|
118
|
+
//#region src/client/batchQueries.ts
|
|
119
|
+
/**
|
|
120
|
+
* Manda num request só (`{ model, queries }`) as consultas de um modelo feitas no mesmo ciclo — as
|
|
121
|
+
* de um render da página. O servidor junta numa SQL só as que dão para juntar (mesmas dimensões,
|
|
122
|
+
* filtros e fatos; ver `runBatch`): os KPIs viram uma consulta com todas as medidas, e "Vol. Total"
|
|
123
|
+
* (fórmula de "Vol. Convencional" + "Vol. Esteira") reaproveita as agregações dos outros dois.
|
|
124
|
+
*
|
|
125
|
+
* Consultas do devtools (`query.devtools`) e de metadados passam direto. Se o lote falhar (ou o
|
|
126
|
+
* servidor for de uma versão sem lote), cada consulta é refeita sozinha.
|
|
127
|
+
*/
|
|
128
|
+
function batchQueries(fetcher) {
|
|
129
|
+
const batches = /* @__PURE__ */ new Map();
|
|
130
|
+
const alone = ({ query, resolve, reject }) => fetcher(query).then(resolve, reject);
|
|
131
|
+
const flush = (model) => {
|
|
132
|
+
const members = batches.get(model) ?? [];
|
|
133
|
+
batches.delete(model);
|
|
134
|
+
if (members.length === 1) return alone(members[0]);
|
|
135
|
+
fetcher({
|
|
136
|
+
model,
|
|
137
|
+
queries: members.map(({ query: { dimensions, measures, filters } }) => ({
|
|
138
|
+
dimensions,
|
|
139
|
+
measures,
|
|
140
|
+
filters
|
|
141
|
+
}))
|
|
142
|
+
}).then(({ results }) => {
|
|
143
|
+
if (results?.length !== members.length) return members.forEach(alone);
|
|
144
|
+
members.forEach(({ resolve, reject }, index) => {
|
|
145
|
+
const { error, ...result } = results[index];
|
|
146
|
+
if (error) reject(new Error(error));
|
|
147
|
+
else resolve(result);
|
|
148
|
+
});
|
|
149
|
+
}, () => members.forEach(alone));
|
|
150
|
+
};
|
|
151
|
+
return (query) => {
|
|
152
|
+
if (query.meta || query.devtools || query.queries) return fetcher(query);
|
|
153
|
+
return new Promise((resolve, reject) => {
|
|
154
|
+
const members = batches.get(query.model);
|
|
155
|
+
if (members) return void members.push({
|
|
156
|
+
query,
|
|
157
|
+
resolve,
|
|
158
|
+
reject
|
|
159
|
+
});
|
|
160
|
+
batches.set(query.model, [{
|
|
161
|
+
query,
|
|
162
|
+
resolve,
|
|
163
|
+
reject
|
|
164
|
+
}]);
|
|
165
|
+
setTimeout(() => flush(query.model), 0);
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
118
170
|
//#region src/devtools/store.ts
|
|
119
171
|
const MAX_HISTORY = 100;
|
|
120
172
|
/** `STORM_RUNS` execuções da mesma instância dentro de `STORM_WINDOW_MS` = consulta em loop. */
|
|
@@ -193,10 +245,11 @@ function createDevtoolsStore() {
|
|
|
193
245
|
emit();
|
|
194
246
|
return run.runId;
|
|
195
247
|
},
|
|
196
|
-
succeed: (runId, rows, debug) => update(runId, {
|
|
248
|
+
succeed: (runId, rows, debug, batched) => update(runId, {
|
|
197
249
|
status: "success",
|
|
198
250
|
rows,
|
|
199
|
-
debug
|
|
251
|
+
debug,
|
|
252
|
+
batched
|
|
200
253
|
}),
|
|
201
254
|
fail: (runId, error) => update(runId, {
|
|
202
255
|
status: "error",
|
|
@@ -230,7 +283,7 @@ function createDevtoolsStore() {
|
|
|
230
283
|
//#endregion
|
|
231
284
|
//#region src/client/DashboardProvider.tsx
|
|
232
285
|
const DashboardContext = createContext(null);
|
|
233
|
-
const DashboardDevtools = lazy(() => import("./DashboardDevtools-
|
|
286
|
+
const DashboardDevtools = lazy(() => import("./DashboardDevtools-Osu5V30U.js"));
|
|
234
287
|
const DEVTOOLS_DEFAULT = process.env.NODE_ENV === "development";
|
|
235
288
|
const draftStorageKey = (model) => `bm-dashboard-devtools:draft:${model}`;
|
|
236
289
|
function loadDraft(model) {
|
|
@@ -245,7 +298,7 @@ function loadDraft(model) {
|
|
|
245
298
|
* Guarda os filtros do dashboard. Todo componente dentro dele (filtros, gráficos, tabelas) consulta o
|
|
246
299
|
* mesmo modelo com os mesmos filtros — é o que faz um slicer filtrar a página inteira, como no BI.
|
|
247
300
|
*/
|
|
248
|
-
function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEVTOOLS_DEFAULT, charts, children }) {
|
|
301
|
+
function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEVTOOLS_DEFAULT, charts, batch = true, children }) {
|
|
249
302
|
const chartTypes = useMemo(() => resolveChartTypes(charts), [charts]);
|
|
250
303
|
const chartComponents = useMemo(() => Object.fromEntries(Object.entries(charts ?? {}).map(([name, spec]) => [name, spec.component])), [charts]);
|
|
251
304
|
const [filters, setFilters] = useState(defaultFilters);
|
|
@@ -287,6 +340,7 @@ function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEV
|
|
|
287
340
|
devtools,
|
|
288
341
|
draft
|
|
289
342
|
]);
|
|
343
|
+
const queryFetcher = useMemo(() => batch ? batchQueries(effectiveFetcher) : effectiveFetcher, [batch, effectiveFetcher]);
|
|
290
344
|
const rawFetcher = useMemo(() => (query) => fetcher({
|
|
291
345
|
...query,
|
|
292
346
|
devtools: {
|
|
@@ -326,7 +380,7 @@ function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEV
|
|
|
326
380
|
model,
|
|
327
381
|
filters,
|
|
328
382
|
setFilter,
|
|
329
|
-
fetcher:
|
|
383
|
+
fetcher: queryFetcher,
|
|
330
384
|
meta,
|
|
331
385
|
refreshKey,
|
|
332
386
|
chartTypes,
|
|
@@ -336,7 +390,7 @@ function DashboardProvider({ model, fetcher, defaultFilters = {}, devtools = DEV
|
|
|
336
390
|
model,
|
|
337
391
|
filters,
|
|
338
392
|
setFilter,
|
|
339
|
-
|
|
393
|
+
queryFetcher,
|
|
340
394
|
meta,
|
|
341
395
|
refreshKey,
|
|
342
396
|
chartTypes,
|
|
@@ -463,7 +517,7 @@ function useModelQuery({ dimensions, measures, ignoreFilters, label, skip = fals
|
|
|
463
517
|
if (cancelado) return;
|
|
464
518
|
setRows(result.rows);
|
|
465
519
|
setError(void 0);
|
|
466
|
-
if (runId) store?.succeed(runId, result.rows, result.devtools?.debug);
|
|
520
|
+
if (runId) store?.succeed(runId, result.rows, result.devtools?.debug, result.batched);
|
|
467
521
|
}).catch((err) => {
|
|
468
522
|
if (cancelado) return;
|
|
469
523
|
setError(err);
|
|
@@ -572,4 +626,4 @@ function ModelDictionary() {
|
|
|
572
626
|
//#endregion
|
|
573
627
|
export { formatCurrency as a, measureFormat as c, useDashboard as d, errorMessage as f, parseLayoutItemKey as h, fieldLabel as i, measureFormatter as l, layoutItemKey as m, INSPECT_ATTRIBUTE as n, formatMeasure as o, LAYOUT_ITEM_ATTRIBUTE as p, useModelQuery as r, formatNumber as s, ModelDictionary as t, DashboardProvider as u };
|
|
574
628
|
|
|
575
|
-
//# sourceMappingURL=ModelDictionary-
|
|
629
|
+
//# sourceMappingURL=ModelDictionary-Dc3_LKqr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelDictionary-Dc3_LKqr.js","names":["loadDraft"],"sources":["../src/devtools/layoutStore.ts","../src/client/batchQueries.ts","../src/devtools/store.ts","../src/client/DashboardProvider.tsx","../src/client/format.ts","../src/client/useModelQuery.ts","../src/client/ModelDictionary.tsx"],"sourcesContent":["import { canonicalJson } from '../codegen'\nimport type { ChartsLayout } from '../layout'\n\n/**\n * Layouts montados na página (`<DashboardCharts>` / `<DashboardFilters>`) e o rascunho de cada um,\n * editado na aba Layout do devtools. Store externo como o de consultas: editar um gráfico\n * re-renderiza só os componentes daquele layout e o painel.\n */\n\n/** Parte do layout que um componente renderiza. */\nexport type LayoutPart = 'charts' | 'filters' | 'canvas'\n\nexport type LayoutEntry = {\n /** O layout que veio do arquivo (o que a página passou ao `<DashboardCharts>`). */\n registered: ChartsLayout\n /** Rascunho editado no devtools; `null` = página usando o registrado. */\n draft: ChartsLayout | null\n /**\n * Partes montadas na página e os formatters que cada uma recebeu (`formatDimension`,\n * `formatLabel`). Parte ausente = a página não renderiza (ex.: sem `<DashboardFilters>`).\n */\n parts: Partial<Record<LayoutPart, string[]>>\n}\n\n/**\n * Marca o elemento de cada item do layout com `<layoutId>/chart:<id>` ou\n * `<layoutId>/filter:<dimensão>` (destaque e inspetor do devtools).\n */\nexport const LAYOUT_ITEM_ATTRIBUTE = 'data-bm-dashboard-layout-item'\n\nexport const layoutItemKey = (layoutId: string, item: LayoutItem) =>\n `${layoutId}/${item.kind}:${item.key}`\n\nexport type LayoutItem = { kind: 'chart' | 'filter'; key: string }\n\nexport function parseLayoutItemKey(value: string): { layoutId: string; item: LayoutItem } | null {\n const match = /^([^/]+)\\/(chart|filter):(.+)$/.exec(value)\n return match\n ? {\n layoutId: match[1] as string,\n item: { kind: match[2] as LayoutItem['kind'], key: match[3] as string }\n }\n : null\n}\n\nconst draftKey = (id: string) => `bm-dashboard-devtools:layout:${id}`\n\nconst sameLayout = (a: ChartsLayout | null, b: ChartsLayout | null) =>\n canonicalJson(a) === canonicalJson(b)\n\nfunction loadDraft(id: string): ChartsLayout | null {\n try {\n const saved = sessionStorage.getItem(draftKey(id))\n return saved ? (JSON.parse(saved) as ChartsLayout) : null\n } catch {\n return null\n }\n}\n\nfunction storeDraft(id: string, draft: ChartsLayout | null) {\n try {\n if (draft) sessionStorage.setItem(draftKey(id), JSON.stringify(draft))\n else sessionStorage.removeItem(draftKey(id))\n } catch {}\n}\n\nexport function createLayoutStore() {\n const entries = new Map<string, LayoutEntry>()\n // Montagens por layout e parte (`<id>\\u0000<parte>`)\n const mounts = new Map<string, number>()\n let snapshot: LayoutEntry[] = []\n const listeners = new Set<() => void>()\n // Edições da aba Layout ainda não aplicadas: sobrevivem a trocar de aba (o painel desmonta)\n const pending = new Map<string, ChartsLayout>()\n\n const emit = () => {\n snapshot = [...entries.values()]\n for (const listener of listeners) listener()\n }\n\n const setDraft = (id: string, next: ChartsLayout | null) => {\n const entry = entries.get(id)\n if (!entry) return\n // Rascunho igual ao arquivo não é rascunho (ex.: desfez tudo à mão)\n const draft = next && !sameLayout(next, entry.registered) ? next : null\n storeDraft(id, draft)\n entries.set(id, { ...entry, draft })\n emit()\n }\n\n return {\n subscribe(listener: () => void) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n getSnapshot: () => snapshot,\n get: (id: string) => entries.get(id) ?? null,\n /** Rascunho do layout, mesmo antes de registrar (evita renderizar o registrado e trocar). */\n draftOf(layout: ChartsLayout) {\n const entry = entries.get(layout.id)\n if (entry) return entry.draft\n const draft = loadDraft(layout.id)\n return draft && !sameLayout(draft, layout) ? draft : null\n },\n\n /** Chamado pelo `<DashboardCharts>`/`<DashboardFilters>` ao montar e quando o arquivo muda (HMR). */\n register(layout: ChartsLayout, part: LayoutPart, formatters: string[]) {\n const current = entries.get(layout.id)\n let draft = current ? current.draft : loadDraft(layout.id)\n // Depois de \"Salvar no arquivo\" o Next recarrega o JSON: o rascunho virou o registrado\n if (draft && sameLayout(draft, layout)) {\n draft = null\n storeDraft(layout.id, null)\n }\n const parts = { ...current?.parts, [part]: formatters }\n entries.set(layout.id, { registered: layout, draft, parts })\n const mountKey = `${layout.id}\\u0000${part}`\n mounts.set(mountKey, (mounts.get(mountKey) ?? 0) + 1)\n emit()\n return () => {\n const count = (mounts.get(mountKey) ?? 1) - 1\n mounts.set(mountKey, count)\n const entry = entries.get(layout.id)\n if (count > 0 || !entry) return\n const { [part]: _, ...rest } = entry.parts\n // O rascunho continua no sessionStorage e volta na próxima montagem\n if (Object.keys(rest).length) entries.set(layout.id, { ...entry, parts: rest })\n else entries.delete(layout.id)\n emit()\n }\n },\n setDraft,\n /** Edições ainda não aplicadas do layout (só em memória). */\n pendingOf: (id: string) => pending.get(id) ?? null,\n setPending(id: string, layout: ChartsLayout | null) {\n if (layout) pending.set(id, layout)\n else pending.delete(id)\n }\n }\n}\n\nexport type LayoutStore = ReturnType<typeof createLayoutStore>\n","import type { DashboardQuery, DashboardResult } from '../types'\n\ntype Fetcher = (query: DashboardQuery) => Promise<DashboardResult>\n\ntype Pending = {\n query: DashboardQuery\n resolve: (result: DashboardResult) => void\n reject: (error: unknown) => void\n}\n\n/**\n * Manda num request só (`{ model, queries }`) as consultas de um modelo feitas no mesmo ciclo — as\n * de um render da página. O servidor junta numa SQL só as que dão para juntar (mesmas dimensões,\n * filtros e fatos; ver `runBatch`): os KPIs viram uma consulta com todas as medidas, e \"Vol. Total\"\n * (fórmula de \"Vol. Convencional\" + \"Vol. Esteira\") reaproveita as agregações dos outros dois.\n *\n * Consultas do devtools (`query.devtools`) e de metadados passam direto. Se o lote falhar (ou o\n * servidor for de uma versão sem lote), cada consulta é refeita sozinha.\n */\nexport function batchQueries(fetcher: Fetcher): Fetcher {\n const batches = new Map<string, Pending[]>()\n\n const alone = ({ query, resolve, reject }: Pending) => fetcher(query).then(resolve, reject)\n\n const flush = (model: string) => {\n const members = batches.get(model) ?? []\n batches.delete(model)\n if (members.length === 1) return alone(members[0])\n const queries = members.map(({ query: { dimensions, measures, filters } }) => ({\n dimensions,\n measures,\n filters\n }))\n fetcher({ model, queries }).then(\n ({ results }) => {\n if (results?.length !== members.length) return members.forEach(alone)\n members.forEach(({ resolve, reject }, index) => {\n const { error, ...result } = results[index]\n if (error) reject(new Error(error))\n else resolve(result)\n })\n },\n () => members.forEach(alone)\n )\n }\n\n return (query) => {\n if (query.meta || query.devtools || query.queries) return fetcher(query)\n return new Promise((resolve, reject) => {\n const members = batches.get(query.model)\n if (members) return void members.push({ query, resolve, reject })\n batches.set(query.model, [{ query, resolve, reject }])\n // As consultas de uma renderização disparam todas nos efeitos do mesmo commit\n setTimeout(() => flush(query.model), 0)\n })\n }\n}\n","import type { DashboardQuery, DashboardQueryDebug, DashboardRow } from '../types'\n\n/**\n * Registro das consultas feitas pelos componentes (`useModelQuery`), lido pelo painel do devtools.\n * É um store externo (`useSyncExternalStore`) para que registrar uma consulta não re-renderize a\n * página inteira — só o painel.\n */\n\nexport type RunStatus = 'loading' | 'success' | 'error' | 'cancelled'\n\nexport type QueryRun = {\n runId: number\n instanceId: string\n label: string\n query: DashboardQuery\n status: RunStatus\n startedAt: number\n /** Ida e volta medida no browser. */\n durationMs?: number\n rows?: DashboardRow[]\n error?: string\n debug?: DashboardQueryDebug\n /** Foi num request junto com outras consultas (quantas, contando ela); o `debug` é do conjunto. */\n batched?: number\n}\n\nexport type TrackedInstance = {\n instanceId: string\n label: string\n runs: number\n last: QueryRun\n /** Refeita muitas vezes em pouco tempo: sinal de re-render em loop (ex.: array novo a cada render). */\n storm: boolean\n}\n\nexport type DevtoolsSnapshot = {\n instances: TrackedInstance[]\n /** Execuções mais recentes primeiro. */\n history: QueryRun[]\n}\n\nconst MAX_HISTORY = 100\n/** `STORM_RUNS` execuções da mesma instância dentro de `STORM_WINDOW_MS` = consulta em loop. */\nconst STORM_RUNS = 6\nconst STORM_WINDOW_MS = 5000\n\nexport function errorMessage(error: unknown): string {\n const err = error as {\n response?: { data?: { error?: string; message?: string } }\n message?: string\n }\n return err?.response?.data?.error ?? err?.response?.data?.message ?? err?.message ?? String(error)\n}\n\nexport function createDevtoolsStore() {\n const instances = new Map<string, TrackedInstance>()\n let history: QueryRun[] = []\n // Execuções em andamento (o histórico é limitado e pode já ter descartado uma consulta lenta)\n const pending = new Map<number, QueryRun>()\n const recentStarts = new Map<string, number[]>()\n // Re-execuções pedidas pelo próprio devtools (refresh, aplicar rascunho) não contam como loop\n let expectedUntil = 0\n let snapshot: DevtoolsSnapshot = { instances: [], history: [] }\n let nextRunId = 1\n const listeners = new Set<() => void>()\n\n const emit = () => {\n snapshot = { instances: [...instances.values()], history }\n for (const listener of listeners) listener()\n }\n\n const update = (runId: number, patch: Partial<QueryRun>) => {\n const current = pending.get(runId)\n if (!current) return\n pending.delete(runId)\n const run = { ...current, ...patch, durationMs: Date.now() - current.startedAt }\n history = history.map((item) => (item.runId === runId ? run : item))\n const instance = instances.get(run.instanceId)\n if (instance?.last.runId === runId) instances.set(run.instanceId, { ...instance, last: run })\n emit()\n }\n\n return {\n subscribe(listener: () => void) {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n getSnapshot: () => snapshot,\n\n start(instanceId: string, label: string, query: DashboardQuery) {\n const run: QueryRun = {\n runId: nextRunId++,\n instanceId,\n label,\n query,\n status: 'loading',\n startedAt: Date.now()\n }\n pending.set(run.runId, run)\n history = [run, ...history].slice(0, MAX_HISTORY)\n const previous = instances.get(instanceId)\n const expected = run.startedAt <= expectedUntil\n const starts = [\n ...(recentStarts.get(instanceId) ?? []),\n ...(expected ? [] : [run.startedAt])\n ].filter((time) => run.startedAt - time <= STORM_WINDOW_MS)\n recentStarts.set(instanceId, starts)\n const storm = previous?.storm || starts.length >= STORM_RUNS\n if (storm && !previous?.storm)\n console.warn(\n `[@bluemarble/bi] \"${label}\" executou ${starts.length}x em ${STORM_WINDOW_MS / 1000}s. ` +\n 'Confira se o componente não recria dimensions/measures/filtros a cada render.'\n )\n instances.set(instanceId, {\n instanceId,\n label,\n runs: (previous?.runs ?? 0) + 1,\n last: run,\n storm\n })\n emit()\n return run.runId\n },\n succeed: (runId: number, rows: DashboardRow[], debug?: DashboardQueryDebug, batched?: number) =>\n update(runId, { status: 'success', rows, debug, batched }),\n fail: (runId: number, error: unknown) =>\n update(runId, { status: 'error', error: errorMessage(error) }),\n /** Consulta descartada antes de responder (filtro mudou, componente desmontou). */\n cancel: (runId: number) => update(runId, { status: 'cancelled' }),\n\n remove(instanceId: string) {\n recentStarts.delete(instanceId)\n if (instances.delete(instanceId)) emit()\n },\n /** Avisa que todas as consultas vão refazer agora, de propósito. */\n expectRefetch() {\n expectedUntil = Date.now() + 500\n },\n /** Zera os alertas de loop (depois de corrigir o componente, sem recarregar a página). */\n resetStorms() {\n recentStarts.clear()\n for (const [id, instance] of instances) instances.set(id, { ...instance, storm: false })\n emit()\n },\n clearHistory() {\n history = []\n emit()\n }\n }\n}\n\nexport type DevtoolsStore = ReturnType<typeof createDevtoolsStore>\n","import {\n type ComponentType,\n createContext,\n lazy,\n type ReactNode,\n Suspense,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState\n} from 'react'\nimport { createLayoutStore, type LayoutStore } from '../devtools/layoutStore'\nimport { batchQueries } from './batchQueries'\nimport { createDevtoolsStore, type DevtoolsStore } from '../devtools/store'\nimport { type ChartTypeSpec, type ChartTypes, resolveChartTypes } from '../layout'\nimport type { ModelDef, ModelMeta } from '../model'\nimport type { DashboardFilters, DashboardQuery, DashboardResult, FilterValue } from '../types'\n\nexport type DashboardFetcher = (query: DashboardQuery) => Promise<DashboardResult>\n\n/**\n * Tipo de gráfico do app: os campos do JSON (`ChartTypeSpec`) e o componente que os renderiza. O\n * componente recebe os campos como props (com os `default` aplicados e formatters já resolvidos) e\n * `queryLabel` — ver `ChartComponentProps`.\n */\n// biome-ignore lint/suspicious/noExplicitAny: cada tipo tem as próprias props, vindas do JSON\nexport type ChartComponentSpec = ChartTypeSpec & { component: ComponentType<any> }\n\n/** Props de um componente registrado em `charts`: os campos do tipo + `queryLabel`. */\nexport type ChartComponentProps<Fields extends object = Record<string, unknown>> = Fields & {\n /** Nome da consulta no devtools (`<título> · <tipo>`): passe ao `useModelQuery({ label })`. */\n queryLabel: string\n}\n\n/** Só existe com o devtools ligado. */\nexport type DashboardDevtoolsContext = {\n store: DevtoolsStore\n /** Layouts da página (`<DashboardCharts>`/`<DashboardFilters>`) e os rascunhos da aba Layout. */\n layouts: LayoutStore\n /** Consulta o modelo registrado, ignorando o rascunho (comparações, snapshots). */\n rawFetcher: DashboardFetcher\n /** Rascunho do modelo aplicado: todas as consultas da página passam a usá-lo. */\n draft: ModelDef | null\n setDraft: (draft: ModelDef | null) => void\n /** Refaz todas as consultas da página. */\n refresh: () => void\n defaultFilters: DashboardFilters\n setFilters: (filters: DashboardFilters) => void\n}\n\ntype DashboardContextValue = {\n model: string\n filters: DashboardFilters\n setFilter: (dimension: string, values: FilterValue[]) => void\n fetcher: DashboardFetcher\n /** Dicionário do modelo (rótulos, descrições, formatos); `null` até carregar. */\n meta: ModelMeta | null\n /** Muda quando o devtools pede para refazer as consultas. */\n refreshKey: number\n /** Tipos de gráfico do layout: os da lib e os do app (`charts`). */\n chartTypes: ChartTypes\n /** Componentes dos tipos do app, por nome. */\n chartComponents: Record<string, ChartComponentSpec['component']>\n devtools: DashboardDevtoolsContext | null\n}\n\nconst DashboardContext = createContext<DashboardContextValue | null>(null)\n\n// Lazy: o painel não entra no bundle da página, só é baixado quando o devtools está ligado\nconst DashboardDevtools = lazy(() => import('../devtools/DashboardDevtools'))\n\nconst DEVTOOLS_DEFAULT = process.env.NODE_ENV === 'development'\n\nconst draftStorageKey = (model: string) => `bm-dashboard-devtools:draft:${model}`\n\nfunction loadDraft(model: string): ModelDef | null {\n try {\n const saved = sessionStorage.getItem(draftStorageKey(model))\n return saved ? (JSON.parse(saved) as ModelDef) : null\n } catch {\n return null\n }\n}\n\ntype DashboardProviderProps = {\n /** Nome do modelo no servidor (chave do registro de modelos do app). */\n model: string\n /** Como chamar o endpoint do app (ex.: `(q) => api.post('/dashboard/query', q).then((r) => r.data)`). */\n fetcher: DashboardFetcher\n defaultFilters?: DashboardFilters\n /**\n * Painel de desenvolvimento (debug das consultas, edição do modelo). Padrão: ligado só com\n * `NODE_ENV=development`. O endpoint também precisa de `runQuery(..., { devtools: true })`.\n */\n devtools?: boolean\n /**\n * Tipos de gráfico do app, usáveis no layout JSON (`\"type\": \"<nome>\"`) e no editor do devtools.\n * Declare fora do componente (referência estável). O servidor precisa dos mesmos campos em\n * `devtools.chartTypes` para salvar o layout.\n */\n charts?: Record<string, ChartComponentSpec>\n /**\n * Manda as consultas de um render num request só; o servidor junta numa SQL as que têm as mesmas\n * dimensões, filtros e fatos (ex.: os KPIs). Padrão: ligado.\n */\n batch?: boolean\n children: ReactNode\n}\n\n/**\n * Guarda os filtros do dashboard. Todo componente dentro dele (filtros, gráficos, tabelas) consulta o\n * mesmo modelo com os mesmos filtros — é o que faz um slicer filtrar a página inteira, como no BI.\n */\nexport function DashboardProvider({\n model,\n fetcher,\n defaultFilters = {},\n devtools = DEVTOOLS_DEFAULT,\n charts,\n batch = true,\n children\n}: DashboardProviderProps) {\n const chartTypes = useMemo(() => resolveChartTypes(charts), [charts])\n const chartComponents = useMemo(\n () =>\n Object.fromEntries(\n Object.entries(charts ?? {}).map(([name, spec]) => [name, spec.component])\n ),\n [charts]\n )\n const [filters, setFilters] = useState<DashboardFilters>(defaultFilters)\n const defaultsRef = useRef(defaultFilters)\n\n const setFilter = useCallback(\n (dimension: string, values: FilterValue[]) =>\n setFilters((current) => ({ ...current, [dimension]: values })),\n []\n )\n\n // --- devtools ---\n const store = useMemo(() => (devtools ? createDevtoolsStore() : null), [devtools])\n const layouts = useMemo(() => (devtools ? createLayoutStore() : null), [devtools])\n // O rascunho aplicado sobrevive ao reload da aba (útil enquanto se edita o modelo)\n const [draft, setDraftState] = useState<ModelDef | null>(() =>\n devtools && typeof window !== 'undefined' ? loadDraft(model) : null\n )\n const [refreshKey, setRefreshKey] = useState(0)\n const [mounted, setMounted] = useState(false)\n useEffect(() => setMounted(true), [])\n\n const setDraft = useCallback(\n (next: ModelDef | null) => {\n store?.expectRefetch()\n setDraftState(next)\n try {\n if (next) sessionStorage.setItem(draftStorageKey(model), JSON.stringify(next))\n else sessionStorage.removeItem(draftStorageKey(model))\n } catch {}\n },\n [model, store]\n )\n\n const refresh = useCallback(() => {\n store?.expectRefetch()\n setRefreshKey((key) => key + 1)\n }, [store])\n\n // Com o devtools ligado, toda consulta pede debug e, se houver, roda contra o rascunho\n const effectiveFetcher = useMemo<DashboardFetcher>(() => {\n if (!devtools) return fetcher\n return (query) =>\n fetcher({\n ...query,\n devtools: { debug: true, ...(draft && { model: draft }), ...query.devtools }\n })\n }, [fetcher, devtools, draft])\n const queryFetcher = useMemo(\n () => (batch ? batchQueries(effectiveFetcher) : effectiveFetcher),\n [batch, effectiveFetcher]\n )\n\n const rawFetcher = useMemo<DashboardFetcher>(\n () => (query) => fetcher({ ...query, devtools: { debug: true, ...query.devtools } }),\n [fetcher]\n )\n\n // Dicionário: segue o rascunho, para os rótulos editados aparecerem na hora\n const [meta, setMeta] = useState<ModelMeta | null>(null)\n useEffect(() => {\n let cancelado = false\n effectiveFetcher({ model, meta: true })\n .then((result) => !cancelado && setMeta(result.meta ?? null))\n .catch(() => !cancelado && setMeta(null))\n return () => {\n cancelado = true\n }\n }, [effectiveFetcher, model])\n\n const devtoolsValue = useMemo(\n () =>\n store &&\n layouts && {\n store,\n layouts,\n rawFetcher,\n draft,\n setDraft,\n refresh,\n defaultFilters: defaultsRef.current,\n setFilters\n },\n [store, layouts, rawFetcher, draft, setDraft, refresh]\n )\n\n const value = useMemo(\n () => ({\n model,\n filters,\n setFilter,\n fetcher: queryFetcher,\n meta,\n refreshKey,\n chartTypes,\n chartComponents,\n devtools: devtoolsValue\n }),\n [\n model,\n filters,\n setFilter,\n queryFetcher,\n meta,\n refreshKey,\n chartTypes,\n chartComponents,\n devtoolsValue\n ]\n )\n\n return (\n <DashboardContext.Provider value={value}>\n {children}\n {/* Só depois de montar: o painel lê localStorage e não deve participar da hidratação */}\n {devtoolsValue && mounted && (\n <Suspense fallback={null}>\n <DashboardDevtools />\n </Suspense>\n )}\n </DashboardContext.Provider>\n )\n}\n\nexport function useDashboard() {\n const context = useContext(DashboardContext)\n if (!context) throw new Error('useDashboard precisa estar dentro de <DashboardProvider>')\n return context\n}\n","import type { ChartNumberFormat } from '../layout'\nimport type { ModelMeta } from '../model'\n\nconst formatters = new Map<string, Intl.NumberFormat>()\n\n/** `compact`: notação curta do pt-BR (1,2 mil / 3,4 mi / 5,6 bi). */\nexport function formatNumber(\n value: number | string | null | undefined,\n decimals = 2,\n compact = false\n) {\n if (value === null || value === undefined || value === '') return ''\n const number = Number(value)\n if (Number.isNaN(number)) return String(value)\n const key = `${decimals}:${compact}`\n let formatter = formatters.get(key)\n if (!formatter) {\n formatter = new Intl.NumberFormat('pt-BR', {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n ...(compact && { notation: 'compact' })\n })\n formatters.set(key, formatter)\n }\n return formatter.format(number)\n}\n\nexport const formatCurrency = (\n value: number | string | null | undefined,\n decimals = 2,\n compact = false\n) => {\n const text = formatNumber(value, decimals, compact)\n return text && `R$ ${text}`\n}\n\n/**\n * Formata pelo dicionário do modelo (`format`/`decimals` da medida) ou pelo formato do gráfico.\n * `fallbackDecimals` é o padrão do componente quando nenhum dos dois define `decimals`.\n */\nexport function formatMeasure(\n value: number | string | null | undefined,\n meta: ChartNumberFormat | undefined,\n fallbackDecimals = 2\n) {\n if (value === null || value === undefined || value === '') return ''\n const decimals = meta?.decimals ?? (meta?.format === 'integer' ? 0 : fallbackDecimals)\n const compact = meta?.compact ?? false\n let text: string\n switch (meta?.format) {\n case 'currency':\n text = formatCurrency(value, decimals, compact)\n break\n case 'percent': {\n const number = formatNumber(Number(value) * 100, decimals, compact)\n text = number && `${number}%`\n break\n }\n default:\n text = formatNumber(value, decimals, compact)\n }\n return text && `${meta?.prefix ?? ''}${text}${meta?.suffix ?? ''}`\n}\n\n/** Dicionário do modelo com o formato do gráfico por cima. */\nexport function measureFormat(\n meta: ModelMeta | null,\n measure: string,\n formats?: Record<string, ChartNumberFormat>\n): ChartNumberFormat | undefined {\n const fromModel = meta?.measures[measure]\n const fromChart = formats?.[measure]\n if (!fromChart) return fromModel\n return {\n ...(fromModel?.format !== undefined && { format: fromModel.format }),\n ...(fromModel?.decimals !== undefined && { decimals: fromModel.decimals }),\n ...fromChart\n }\n}\n\n/**\n * Formatter de uma medida: o `formats` do gráfico (se houver) por cima do dicionário do modelo; sem\n * `format`, número com `fallbackDecimals`.\n */\nexport function measureFormatter(\n meta: ModelMeta | null,\n measure: string,\n fallbackDecimals = 2,\n formats?: Record<string, ChartNumberFormat>\n): (value: number | string | null | undefined) => string {\n const format = measureFormat(meta, measure, formats)\n return (value) => formatMeasure(value, format, fallbackDecimals)\n}\n\n/** Rótulo de uma dimensão/medida: o `labels` do gráfico, o dicionário do modelo ou o nome. */\nexport function fieldLabel(\n meta: ModelMeta | null,\n name: string,\n labels?: Record<string, string>\n): string {\n return labels?.[name] || meta?.dimensions[name]?.label || meta?.measures[name]?.label || name\n}\n","import { useEffect, useId, useMemo, useState } from 'react'\nimport type { DashboardFilters, DashboardRow } from '../types'\nimport { useDashboard } from './DashboardProvider'\n\nexport const INSPECT_ATTRIBUTE = 'data-bm-dashboard-query'\n\ntype UseModelQueryParams = {\n dimensions?: string[]\n measures?: string[]\n /**\n * Filtros do dashboard que esta consulta NÃO deve respeitar. Um filtro ignora a si mesmo para\n * listar as opções (senão, ao escolher um navio, a lista mostraria só ele).\n */\n ignoreFilters?: string[]\n /** Nome da consulta no devtools (padrão: dimensões × medidas). */\n label?: string\n /** Não consulta (devolve `rows: []`); ex.: a linha de total de uma tabela que não mostra total. */\n skip?: boolean\n}\n\nexport function useModelQuery({\n dimensions,\n measures,\n ignoreFilters,\n label,\n skip = false\n}: UseModelQueryParams) {\n const { model, filters, fetcher, refreshKey, devtools } = useDashboard()\n const instanceId = useId()\n\n const applied: DashboardFilters = {}\n for (const [dimension, values] of Object.entries(filters)) {\n if (values.length && !ignoreFilters?.includes(dimension)) applied[dimension] = values\n }\n\n // Chave por valor: só refaz a consulta quando o que vai pro servidor muda (não a cada render,\n // nem quando muda um filtro que esta consulta ignora)\n const key = JSON.stringify([model, dimensions, measures, applied])\n const query = useMemo(() => ({ model, dimensions, measures, filters: applied }), [key])\n\n const [rows, setRows] = useState<DashboardRow[]>([])\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<unknown>()\n\n const store = devtools?.store\n const debugLabel =\n label ?? [dimensions?.join(', '), measures?.join(', ')].filter(Boolean).join(' × ')\n\n useEffect(() => {\n if (skip) {\n setRows([])\n setIsLoading(false)\n return\n }\n let cancelado = false\n setIsLoading(true)\n const runId = store?.start(instanceId, debugLabel, query)\n fetcher(query)\n .then((result) => {\n if (cancelado) return\n setRows(result.rows)\n setError(undefined)\n if (runId) store?.succeed(runId, result.rows, result.devtools?.debug, result.batched)\n })\n .catch((err) => {\n if (cancelado) return\n setError(err)\n if (runId) store?.fail(runId, err)\n })\n .finally(() => !cancelado && setIsLoading(false))\n return () => {\n cancelado = true\n if (runId) store?.cancel(runId)\n }\n }, [fetcher, query, refreshKey, skip])\n\n useEffect(() => (store ? () => store.remove(instanceId) : undefined), [store, instanceId, skip])\n\n // Espalhar no elemento raiz do componente: é como o inspetor do devtools acha o componente na tela\n const inspectProps = store && !skip ? { [INSPECT_ATTRIBUTE]: instanceId } : {}\n\n return { rows, isLoading, error, inspectProps }\n}\n","import {\n Box,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Typography\n} from '@mui/material'\nimport { AGGREGATION_LABEL, isAggregate, type ModelMeta, type ValueFormat } from '../model'\nimport { useDashboard } from './DashboardProvider'\n\nconst FORMAT_LABEL: Record<ValueFormat, string> = {\n number: 'Número',\n integer: 'Inteiro',\n currency: 'Moeda (R$)',\n percent: 'Percentual'\n}\n\n/** Como a medida é calculada, em termos de negócio (o SQL não sai do servidor). */\nfunction formula(meta: ModelMeta, name: string) {\n const measure = meta.measures[name]\n const label = (ref: string) => meta.measures[ref]?.label ?? ref\n if (measure.type === 'formula' && measure.refs?.length)\n return `Combina ${measure.refs.map(label).join(', ')}`\n if (isAggregate(measure)) return AGGREGATION_LABEL[measure.type]\n if (measure.type !== 'ratio' || !measure.numerator || !measure.denominator) return '—'\n return `${label(measure.numerator)} ÷ ${label(measure.denominator)}`\n}\n\n/**\n * Dicionário do modelo do `DashboardProvider`: o que cada filtro/eixo e cada número significa. É\n * montado a partir de `label`/`description` das dimensões e medidas — documentação que não\n * desatualiza, porque é a mesma definição que calcula os números.\n */\nexport function ModelDictionary() {\n const { meta } = useDashboard()\n if (!meta) return null\n\n const header = (labels: string[]) => (\n <TableHead>\n <TableRow>\n {labels.map((label) => (\n <TableCell key={label} sx={{ fontWeight: 700 }}>\n {label}\n </TableCell>\n ))}\n </TableRow>\n </TableHead>\n )\n\n return (\n <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>\n <Box>\n <Typography sx={{ fontWeight: 700, mb: 1 }}>Indicadores</Typography>\n <TableContainer>\n <Table size=\"small\">\n {header(['Indicador', 'Descrição', 'Cálculo', 'Formato'])}\n <TableBody>\n {Object.entries(meta.measures).map(([name, measure]) => (\n <TableRow key={name}>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>{measure.label ?? name}</TableCell>\n <TableCell>{measure.description ?? '—'}</TableCell>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>{formula(meta, name)}</TableCell>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>\n {measure.format ? FORMAT_LABEL[measure.format] : '—'}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n </Box>\n <Box>\n <Typography sx={{ fontWeight: 700, mb: 1 }}>Filtros e agrupamentos</Typography>\n <TableContainer>\n <Table size=\"small\">\n {header(['Campo', 'Descrição'])}\n <TableBody>\n {Object.entries(meta.dimensions).map(([name, dimension]) => (\n <TableRow key={name}>\n <TableCell sx={{ whiteSpace: 'nowrap' }}>{dimension.label ?? name}</TableCell>\n <TableCell>{dimension.description ?? '—'}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n </Box>\n </Box>\n )\n}\n"],"mappings":"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"}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as DashboardQueryDebug, C as ColumnVerticalAlign, F as FilterValue, J as ModelDef, M as DashboardRow, O as DashboardFilters$1, S as ColumnAlign, Y as ModelMeta, d as ChartsLayout, i as CardStyle, j as DashboardResult, k as DashboardQuery, l as ChartTypeSpec, o as ChartDef, s as ChartNumberFormat, u as ChartTypes, w as FormatRule } from "../layout-Bn1OiRov.js";
|
|
2
2
|
import { ComponentType, MouseEvent, ReactNode } from "react";
|
|
3
3
|
//#region src/client/BarChart.d.ts
|
|
4
4
|
type BarChartProps = {
|
|
@@ -249,6 +249,8 @@ type QueryRun = {
|
|
|
249
249
|
rows?: DashboardRow[];
|
|
250
250
|
error?: string;
|
|
251
251
|
debug?: DashboardQueryDebug;
|
|
252
|
+
/** Foi num request junto com outras consultas (quantas, contando ela); o `debug` é do conjunto. */
|
|
253
|
+
batched?: number;
|
|
252
254
|
};
|
|
253
255
|
type TrackedInstance = {
|
|
254
256
|
instanceId: string;
|
|
@@ -267,7 +269,7 @@ declare function createDevtoolsStore(): {
|
|
|
267
269
|
subscribe(listener: () => void): () => boolean;
|
|
268
270
|
getSnapshot: () => DevtoolsSnapshot;
|
|
269
271
|
start(instanceId: string, label: string, query: DashboardQuery): number;
|
|
270
|
-
succeed: (runId: number, rows: DashboardRow[], debug?: DashboardQueryDebug) => void;
|
|
272
|
+
succeed: (runId: number, rows: DashboardRow[], debug?: DashboardQueryDebug, batched?: number) => void;
|
|
271
273
|
fail: (runId: number, error: unknown) => void;
|
|
272
274
|
/** Consulta descartada antes de responder (filtro mudou, componente desmontou). */
|
|
273
275
|
cancel: (runId: number) => void;
|
|
@@ -342,13 +344,18 @@ type DashboardProviderProps = {
|
|
|
342
344
|
* `devtools.chartTypes` para salvar o layout.
|
|
343
345
|
*/
|
|
344
346
|
charts?: Record<string, ChartComponentSpec>;
|
|
347
|
+
/**
|
|
348
|
+
* Manda as consultas de um render num request só; o servidor junta numa SQL as que têm as mesmas
|
|
349
|
+
* dimensões, filtros e fatos (ex.: os KPIs). Padrão: ligado.
|
|
350
|
+
*/
|
|
351
|
+
batch?: boolean;
|
|
345
352
|
children: ReactNode;
|
|
346
353
|
};
|
|
347
354
|
/**
|
|
348
355
|
* Guarda os filtros do dashboard. Todo componente dentro dele (filtros, gráficos, tabelas) consulta o
|
|
349
356
|
* mesmo modelo com os mesmos filtros — é o que faz um slicer filtrar a página inteira, como no BI.
|
|
350
357
|
*/
|
|
351
|
-
declare function DashboardProvider({ model, fetcher, defaultFilters, devtools, charts, children }: DashboardProviderProps): import("react").JSX.Element;
|
|
358
|
+
declare function DashboardProvider({ model, fetcher, defaultFilters, devtools, charts, batch, children }: DashboardProviderProps): import("react").JSX.Element;
|
|
352
359
|
declare function useDashboard(): DashboardContextValue;
|
|
353
360
|
//#endregion
|
|
354
361
|
//#region src/client/DataTable.d.ts
|
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { T as rowRuleStyles, _ as tableColumns, f as isBuiltinChart, h as percentColumn, o as chartCardStyle, u as customChartProps } from "../layout-DAFeHIuP.js";
|
|
2
|
-
import { a as formatCurrency, c as measureFormat, d as useDashboard, i as fieldLabel, l as measureFormatter, m as layoutItemKey, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as LAYOUT_ITEM_ATTRIBUTE, r as useModelQuery, s as formatNumber, t as ModelDictionary, u as DashboardProvider } from "../ModelDictionary-
|
|
2
|
+
import { a as formatCurrency, c as measureFormat, d as useDashboard, i as fieldLabel, l as measureFormatter, m as layoutItemKey, n as INSPECT_ATTRIBUTE, o as formatMeasure, p as LAYOUT_ITEM_ATTRIBUTE, r as useModelQuery, s as formatNumber, t as ModelDictionary, u as DashboardProvider } from "../ModelDictionary-Dc3_LKqr.js";
|
|
3
3
|
import { Box, Checkbox, CircularProgress, FormControl, InputLabel, ListItemText, MenuItem, OutlinedInput, Paper, Select, Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel, Typography } from "@mui/material";
|
|
4
4
|
import { Bar, BarChart as BarChart$1, CartesianGrid, Cell, ComposedChart, LabelList, Line, Pie, PieChart, ResponsiveContainer, Tooltip as Tooltip$1, XAxis, YAxis } from "recharts";
|
|
5
5
|
import { createContext, useContext, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
@@ -175,8 +175,27 @@ type DashboardQuery = {
|
|
|
175
175
|
filters?: DashboardFilters;
|
|
176
176
|
/** Devolve só os metadados do modelo (rótulos, descrições, formatos; sem SQL) em `result.meta`. */
|
|
177
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[];
|
|
178
185
|
devtools?: DashboardDevtoolsRequest;
|
|
179
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
|
+
};
|
|
180
199
|
type DashboardRow = Record<string, string | number | null>;
|
|
181
200
|
type DashboardQueryDebug = {
|
|
182
201
|
sql: string;
|
|
@@ -243,6 +262,13 @@ type DevtoolsCapabilities = {
|
|
|
243
262
|
};
|
|
244
263
|
type DashboardResult = {
|
|
245
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;
|
|
246
272
|
meta?: ModelMeta;
|
|
247
273
|
devtools?: {
|
|
248
274
|
model?: ModelDef;
|
|
@@ -605,5 +631,5 @@ declare function validateLayout(layout: ChartsLayout, types?: ChartTypes): strin
|
|
|
605
631
|
*/
|
|
606
632
|
declare function layoutModelWarnings(layout: ChartsLayout, model: Pick<ModelDef, 'dimensions' | 'measures'>, types?: ChartTypes): string[];
|
|
607
633
|
//#endregion
|
|
608
|
-
export {
|
|
609
|
-
//# sourceMappingURL=layout-
|
|
634
|
+
export { TableDef as $, DashboardQueryDebug as A, SchemaTable as B, ColumnVerticalAlign as C, DashboardDevtoolsRequest as D, DashboardBatchResult as E, FilterValue as F, FormulaMeasure as G, Aggregation as H, ModelColumns as I, ModelDef as J, MeasureDef as K, ModelValidation as L, DashboardRow as M, DashboardSnapshot as N, DashboardFilters as O, DevtoolsCapabilities as P, SumMeasure as Q, ProfileStep as R, ColumnAlign as S, DashboardBatchQuery as T, DimensionDef as U, AggregateMeasure as V, FieldMeta as W, RatioMeasure as X, ModelMeta as Y, RelationshipDef as Z, KpiChartDef as _, CardsStyle as a, resolveChartTypes as b, ChartType as c, ChartsLayout as d, ValueFormat as et, ComboChartDef as f, FilterDef as g, FieldSpec as h, CardStyle as i, DashboardResult as j, DashboardQuery as k, ChartTypeSpec as l, DonutChartDef as m, BuiltinChartDef as n, validateModel as nt, ChartDef as o, CustomChartDef as p, MeasureMeta as q, CanvasDef as r, ChartNumberFormat as s, BarChartDef as t, modelMeta as tt, ChartTypes as u, TableChartDef as v, FormatRule as w, validateLayout as x, layoutModelWarnings as y, SaveModelResult as z };
|
|
635
|
+
//# sourceMappingURL=layout-Bn1OiRov.d.ts.map
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as TableDef, G as FormulaMeasure, H as Aggregation, J as ModelDef, K as MeasureDef, M as DashboardRow, Q as SumMeasure, U as DimensionDef, V as AggregateMeasure, W as FieldMeta, X as RatioMeasure, Z as RelationshipDef, b as resolveChartTypes, d as ChartsLayout, et as ValueFormat, j as DashboardResult, k as DashboardQuery, l as ChartTypeSpec, nt as validateModel, q as MeasureMeta, tt as modelMeta, x as validateLayout, y as layoutModelWarnings } from "../layout-Bn1OiRov.js";
|
|
2
2
|
//#region src/codegen.d.ts
|
|
3
3
|
/** JSON com as chaves ordenadas: compara modelos sem depender da ordem das propriedades. */
|
|
4
4
|
declare function canonicalJson(value: unknown): string;
|
package/dist/server/index.js
CHANGED
|
@@ -28,6 +28,26 @@ function leafMeasures(model, names) {
|
|
|
28
28
|
});
|
|
29
29
|
return [...leaves];
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* De onde saem as medidas: cada tabela onde agregam (a fato) e as tabelas que as agregações dela
|
|
33
|
+
* citam. Duas consultas com as mesmas dimensões, filtros e origens dão as mesmas linhas, e cada
|
|
34
|
+
* medida o mesmo valor, juntas ou separadas — é o que o lote do `runQuery` usa para juntá-las.
|
|
35
|
+
*/
|
|
36
|
+
function measureSources(model, measures) {
|
|
37
|
+
const tableNames = Object.keys(model.tables);
|
|
38
|
+
const edges = modelEdges(model);
|
|
39
|
+
const sources = {};
|
|
40
|
+
for (const leaf of leafMeasures(model, measures)) {
|
|
41
|
+
const measure = model.measures[leaf];
|
|
42
|
+
if (!isAggregate(measure)) continue;
|
|
43
|
+
const tables = referencedTables(measure.sql, tableNames);
|
|
44
|
+
const home = measureHome(tables, edges);
|
|
45
|
+
if (!home) throw new DashboardQueryError(`Medida "${leaf}" sem tabela de origem`);
|
|
46
|
+
sources[home] ??= /* @__PURE__ */ new Set();
|
|
47
|
+
for (const table of tables) sources[home].add(table);
|
|
48
|
+
}
|
|
49
|
+
return Object.fromEntries(Object.entries(sources).sort(([a], [b]) => a.localeCompare(b)).map(([home, tables]) => [home, [...tables].sort()]));
|
|
50
|
+
}
|
|
31
51
|
function compileFilters(model, query) {
|
|
32
52
|
const filters = [];
|
|
33
53
|
for (const [name, values] of Object.entries(query.filters ?? {})) {
|
|
@@ -231,6 +251,114 @@ function defineModel(model) {
|
|
|
231
251
|
return model;
|
|
232
252
|
}
|
|
233
253
|
//#endregion
|
|
254
|
+
//#region src/server/batch.ts
|
|
255
|
+
const cleanFilters = (filters = {}) => Object.fromEntries(Object.keys(filters).filter((name) => Array.isArray(filters[name]) && filters[name].length).sort().map((name) => [name, filters[name]]));
|
|
256
|
+
/**
|
|
257
|
+
* Pode juntar? Com dimensões, as origens têm que ser as mesmas: uma fato a mais traria grupos que só
|
|
258
|
+
* existem nela. Sem dimensões (totais) sai sempre uma linha, então basta que cada fato em comum
|
|
259
|
+
* agregue com as mesmas tabelas (uma tabela a mais no JOIN de uma fato poderia mudar o valor dela).
|
|
260
|
+
*/
|
|
261
|
+
function compatible(a, b, totals) {
|
|
262
|
+
if (!totals) return JSON.stringify(a) === JSON.stringify(b);
|
|
263
|
+
return Object.keys(a).every((home) => !b[home] || a[home].join() === b[home].join());
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Agrupa as consultas do lote: mesmas dimensões e filtros e origens compatíveis viram uma consulta
|
|
267
|
+
* com todas as medidas. Consulta só de dimensões (opções de filtro) só junta com outra igual.
|
|
268
|
+
* Erro de uma consulta (medida desconhecida) fica em `errors`, sem derrubar as outras.
|
|
269
|
+
*/
|
|
270
|
+
function planBatch(model, queries) {
|
|
271
|
+
const candidates = [];
|
|
272
|
+
const errors = /* @__PURE__ */ new Map();
|
|
273
|
+
queries.forEach((raw, index) => {
|
|
274
|
+
const query = {
|
|
275
|
+
dimensions: raw?.dimensions ?? [],
|
|
276
|
+
measures: [...new Set(raw?.measures ?? [])],
|
|
277
|
+
filters: cleanFilters(raw?.filters)
|
|
278
|
+
};
|
|
279
|
+
const key = JSON.stringify([
|
|
280
|
+
query.dimensions,
|
|
281
|
+
query.filters,
|
|
282
|
+
!query.measures.length
|
|
283
|
+
]);
|
|
284
|
+
let sources = null;
|
|
285
|
+
try {
|
|
286
|
+
if (query.measures.length) sources = measureSources(model, query.measures);
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (!(error instanceof DashboardQueryError)) throw error;
|
|
289
|
+
errors.set(index, error.message);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const totals = !query.dimensions.length;
|
|
293
|
+
const group = candidates.find((candidate) => candidate.key === key && (sources && candidate.sources ? compatible(candidate.sources, sources, totals) : JSON.stringify(candidate.query.measures) === JSON.stringify(query.measures)));
|
|
294
|
+
if (!group) {
|
|
295
|
+
candidates.push({
|
|
296
|
+
key,
|
|
297
|
+
sources,
|
|
298
|
+
query,
|
|
299
|
+
members: [index]
|
|
300
|
+
});
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
group.members.push(index);
|
|
304
|
+
group.query.measures = [.../* @__PURE__ */ new Set([...group.query.measures, ...query.measures])];
|
|
305
|
+
if (group.sources && sources) group.sources = {
|
|
306
|
+
...group.sources,
|
|
307
|
+
...sources
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
groups: candidates.map(({ query, members }) => ({
|
|
312
|
+
query: {
|
|
313
|
+
...query,
|
|
314
|
+
measures: [...query.measures].sort()
|
|
315
|
+
},
|
|
316
|
+
members
|
|
317
|
+
})),
|
|
318
|
+
errors
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Executa o lote: uma consulta por grupo (em paralelo) e as linhas de cada consulta só com as
|
|
323
|
+
* colunas que ela pediu. `toQueryError` diz se um erro do grupo é da consulta (vai para as
|
|
324
|
+
* respostas dela) ou do servidor (`null`: derruba o request, como numa consulta sozinha).
|
|
325
|
+
*/
|
|
326
|
+
async function runBatch(model, queries, run, options) {
|
|
327
|
+
if (!Array.isArray(queries)) throw new DashboardQueryError("`queries` deve ser uma lista");
|
|
328
|
+
const { groups, errors } = planBatch(model, queries);
|
|
329
|
+
const results = queries.map((_, index) => {
|
|
330
|
+
const error = errors.get(index);
|
|
331
|
+
return error ? {
|
|
332
|
+
rows: [],
|
|
333
|
+
error
|
|
334
|
+
} : { rows: [] };
|
|
335
|
+
});
|
|
336
|
+
await Promise.all(groups.map(async ({ query, members }) => {
|
|
337
|
+
let execution;
|
|
338
|
+
try {
|
|
339
|
+
execution = await run(query);
|
|
340
|
+
} catch (error) {
|
|
341
|
+
const queryError = options.toQueryError(error);
|
|
342
|
+
if (!queryError) throw error;
|
|
343
|
+
for (const index of members) results[index] = {
|
|
344
|
+
rows: [],
|
|
345
|
+
error: queryError.message
|
|
346
|
+
};
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
for (const index of members) {
|
|
350
|
+
const own = queries[index];
|
|
351
|
+
const columns = [...own.dimensions ?? [], ...own.measures ?? []];
|
|
352
|
+
results[index] = {
|
|
353
|
+
rows: execution.rows.map((row) => Object.fromEntries(columns.map((column) => [column, row[column] ?? null]))),
|
|
354
|
+
...members.length > 1 && { batched: members.length },
|
|
355
|
+
...options.debug && { devtools: { debug: execution.debug } }
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
}));
|
|
359
|
+
return results;
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
234
362
|
//#region src/server/modelFile.ts
|
|
235
363
|
/**
|
|
236
364
|
* Troca, dentro do arquivo do modelo, só o objeto passado ao `defineModel(...)` e o import do
|
|
@@ -715,8 +843,11 @@ async function runQuery(models, query, execute, options = {}) {
|
|
|
715
843
|
meta: modelMeta(model)
|
|
716
844
|
};
|
|
717
845
|
const ttl = options.cacheTtlMs ?? 0;
|
|
718
|
-
const run = async (target) => {
|
|
719
|
-
const { sql, params } = compileQuery(target,
|
|
846
|
+
const run = async (target, compiled = query) => {
|
|
847
|
+
const { sql, params } = compileQuery(target, {
|
|
848
|
+
model: query.model,
|
|
849
|
+
...compiled
|
|
850
|
+
});
|
|
720
851
|
const load = async () => {
|
|
721
852
|
const startedAt = performance.now();
|
|
722
853
|
const rows = await execute(sql, params);
|
|
@@ -736,6 +867,33 @@ async function runQuery(models, query, execute, options = {}) {
|
|
|
736
867
|
...await execution
|
|
737
868
|
};
|
|
738
869
|
};
|
|
870
|
+
const toQueryError = (error) => {
|
|
871
|
+
if (error instanceof DashboardQueryError) return error;
|
|
872
|
+
return devtools ? new DashboardQueryError(dbMessage(error)) : null;
|
|
873
|
+
};
|
|
874
|
+
if (query.queries) {
|
|
875
|
+
const { debug: _, model: __, ...others } = devtools ?? {};
|
|
876
|
+
if (Object.values(others).some(Boolean)) throw new DashboardQueryError("Num lote, o devtools só aceita `debug` e `model`");
|
|
877
|
+
return {
|
|
878
|
+
rows: [],
|
|
879
|
+
results: await runBatch(model, query.queries, async (compiled) => {
|
|
880
|
+
const { rows, sql, params, durationMs, cached } = await run(model, compiled);
|
|
881
|
+
return {
|
|
882
|
+
rows,
|
|
883
|
+
debug: {
|
|
884
|
+
sql,
|
|
885
|
+
params,
|
|
886
|
+
durationMs,
|
|
887
|
+
cached,
|
|
888
|
+
draft: !!draft
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
}, {
|
|
892
|
+
debug: !!devtools?.debug,
|
|
893
|
+
toQueryError
|
|
894
|
+
})
|
|
895
|
+
};
|
|
896
|
+
}
|
|
739
897
|
if (devtools) {
|
|
740
898
|
if (devtools.clearCache) cache.clear();
|
|
741
899
|
try {
|
|
@@ -756,8 +914,7 @@ async function runQuery(models, query, execute, options = {}) {
|
|
|
756
914
|
}
|
|
757
915
|
}
|
|
758
916
|
const { rows, sql, params, durationMs, cached } = await run(model).catch((error) => {
|
|
759
|
-
|
|
760
|
-
throw new DashboardQueryError(dbMessage(error));
|
|
917
|
+
throw toQueryError(error) ?? error;
|
|
761
918
|
});
|
|
762
919
|
if (!devtools?.debug) return { rows };
|
|
763
920
|
return {
|