dbt Charts board YAML, drawn from GestaltBI’s own data.
Reads the dbt Charts board format — layout, charts, variables — and resolves it against the rows GestaltBI’s pipeline produces. Renders nothing: it works out what each chart shows, and the host decides how it looks.
It reads the format and nothing else. There is no SQL, no warehouse and no dct here.
import { readBoard, boardSources, resolveBoard } from '@gestaltbi/dbtcharts';
const { board, problems } = readBoard(yamlText);
boardSources(board); // the processes to load: ['date_agg', 'prod_agg']
const resolved = resolveBoard(board, {
frames: (process) => rowsFor(process),
variables: { region: 'North' },
label: (column) => labelFor(column),
});
resolved.layout // rows, cols, grid, tabs — charts referenced by id
resolved.charts // each chart's data, ready to draw
resolved.variables // each control's value and options
Resolving is pure: the same board, rows and variable values always give the same result, so a host calls
it again whenever any of the three change.
A query points at a process
In dbt Charts a query is usually SQL run in a warehouse. GestaltBI has neither, so a query names a
process from processing.json and reads its rows whole.
source rather than a key of our own, because every query shape in the dbt Charts schema rejects
properties it does not define — the tests check that process: would have failed. A query with a
source and no sql stays valid dbt Charts YAML, so a file named *.board.yml still gets dbt
Labs’ VS Code extension’s autocomplete and schema checks.
Inline data needs no process at all, exactly as in dbt Charts:
rowscolsgridtabsdetails, and charts: declared inside any of them
Variables
column binding, every input type, default, required, operator, static and query options
Text
markdown, with {{ name }} and one level of {% if name %}
And what is not — each reported rather than guessed at:
SQL, HTTP and schema queries, and file paths. Each is reported with what to write instead.
histogram and the four map types. They resolve as unavailable with a reason, so a host can show the gap in place.
style and theme. GestaltBI’s design system decides how GestaltBI looks.
cache and incremental. Nothing is cached or refreshed, so there is nothing for them to change.
Charts hold no arithmetic
In the dbt Charts schema every encoding is a bare column name — x is a string, y a string or a
list, color a string — and aggregation belongs to the query. So resolving a chart is mostly reading
columns out of rows in the right order.
The one thing added is a sum. GestaltBI frames are not always pre-aggregated, so when several rows land
on one bar, cell or slice they are added up — and reported as reaggregated, never done silently. dbt
Charts warns about the same situation.
Order
Axes and pivot columns follow the order dbt Charts documents: dates chronological, numbers ascending,
strings in the order the query returned them. A chart with sort follows the sorted rows instead.
Dates print as whole days — nothing is truncated to a month.
Variables filter rows
In dbt Charts a variable becomes a bound SQL parameter and the query re-runs. Here the rows are already in
hand, so a variable narrows them. For a query that passes rows straight through, the two give the same
answer.
variables:
region:
column: region
input: select
Unset — null, empty, an empty list — means no filter, as dbt Charts’ filter() returns 1=1.
A daterange includes the day it ends on.
A warehouse path like sales.orders.region falls back to its last segment.
Options come from a static list, a query’s column, or the column’s own values before filtering — so picking one never hides the rest.
A required variable with no value holds the charts back and is listed in blockedBy.
Reaching the totals
Narrowing finished frames cannot move a total: once rows are summed, the column a variable filters is
gone. pipelineFilter translates variables into the filter GestaltBI’s own filter ops apply upstream of
every aggregate, which is the GestaltBI equivalent of dbt Charts re-running the query.
What cannot go upstream is listed in clientOnly — operators the filter ops lack, and columns the raw
data does not have, since a key no row carries would match nothing and empty every chart.
Dates
Read in local time. GestaltBI parses dates at local midnight, so in Rome the first of January is
2019-12-31T23:00Z, and a UTC reading labels every day as the day before. The suite runs in
Europe/Rome and America/New_York, because it passed in UTC with exactly that bug in it.
Kept honest against dbt Labs’ schema
The tests validate every fixture board against dbt-charts-board.schema.json, copied unmodified from the
dbt Charts VS Code extension 0.2.1 (publisher dbtLabsInc, Apache-2.0 — the licence sits beside it).
They include the published KPI and pivot examples as written, a board in the GestaltBI dialect, and the
check that process: would have been rejected.
The schema is pinned by hash. dbt Charts is pre-1.0 and will change; replacing the file with a newer
version fails a test until someone has read what changed. The churn arrives as a red build, not as a
board that quietly reads wrong.
Reference
Extracted from src/index.ts when this page was built, so it
cannot drift from what the package actually exports.
function applyVariables(board: Board, rows: Row[], values: Record<string, unknown>): Row[]
Apply every variable a frame can answer to that frame.
Client-side, which is the one real difference from dbt Charts: there a variable becomes a bound SQL parameter and the query re-runs. Here the rows are already in hand, so a variable narrows them. For a query that passes rows straight through, the two give the same answer.
A calendar day as YYYY-MM-DD, or null when the value is not a date.
Read in local time, never through toISOString. GestaltBI parses dates with moment, which lands them on local midnight — so in Rome the first of January is 2019-12-31T23:00Z, and a UTC reading labels every day in the file as the day before. A date-only string is already a day and is taken as written.
The slice of Jinja board prose actually uses: {{ name }}, and one level of {% if name %} … {% else %} … {% endif %} — the form in dbt Charts' own examples. Anything beyond that is left standing and reported, because a template quietly half-rendered reads worse than one shown as written.
function ordered(rows: Row[], column: string): Category[]
The distinct values of a column, in the order dbt Charts documents.
"Dates and numbers chronological or ascending; strings first-seen from the query." Applied to axes as well as pivot columns so a board reads the same way in both tools. A column that mixes kinds keeps query order — sorting apples against dates has no answer worth giving.
function pipelineFilter(board: Board, values: Record<string, unknown>, upstreamColumns: string[]): PipelineFilter
Board variables, as a filter GestaltBI's pipeline can apply.
This is how a variable does what it does in dbt Charts — narrow the data before anything is totalled — rather than only trimming the frames that happen to still carry its column. In dbt Charts a variable re-runs the query; the GestaltBI equivalent is a filter op upstream of every aggregate, re-run with this value. A region picker then moves the totals, not only the chart that lists regions.
upstreamColumns are the columns of the raw dataset. A variable on a column missing there is kept out of the filter: the filter ops compare values directly, and a key no row has would match nothing and empty every chart.
function readBoard(input: string | Record<string, unknown> | null | undefined):
Read a board from YAML text, or from an object already parsed.
Always returns a board. A file with a bad query or an unknown chart still has a layout worth drawing, and the person who wrote it needs to see both what rendered and what did not — so problems are listed, never thrown.
function resolveChart(chart: ChartSpec, input: Row[], ctx: ChartContext): ResolvedChart
Turn one chart and its rows into data a host can draw.
Charts in dbt Charts hold no arithmetic — every encoding is a bare column name, and aggregation belongs to the query. So resolving is mostly reading columns out of rows in the right order. The one thing added is a sum where several rows land on the same mark, because GestaltBI frames are not always pre-aggregated, and that is reported rather than done silently.
Board variables translated for a GestaltBI filter op.
filter is keyed by column in the shape @gestaltbi/stream's filter ops read: a list for "one of these", { between: [a, b] } for a range, null to clear a variable that was set before.
Variables the pipeline cannot express — an operator the filter ops lack, or a column absent upstream. They still narrow the frames that carry their column, but cannot reach a total computed before them.
The distinct values of a column across the whole dataset, when the host knows them.
Offered as a variable's options in preference to the values in the frames. A host that filters its pipeline by variable (see {@link pipelineFilter}) hands over frames that are already narrowed — reading options from those would shrink a region list to the one region just chosen.
source is the GestaltBI binding: a query holding a source and no sql reads that process from processing.json. It is also valid dbt Charts YAML, which is why it was chosen over a key of our own — the schema rejects any property it does not define.