Data & analytics
BI Workspace
Dashboards built on the same catalog, prepared tables and metrics your agents use — so a chart and an agent answering about it cannot disagree.
Open Data & BI → BI Workspace. Dashboards live in workspaces and folders; each is a grid of resizable widgets you drag into place.
AI Analyst — conversational analysis#
AI Analyst (under Data & BI) is the dedicated ask-anything surface. An analyst is three choices: a reasoning model from your connected providers (the dialog suggests reasoning families and nudges you when a pick doesn't look like one), the data it analyses — local datasets and uploads, one table, or a warehouse connection — and the predictive models it may score or forecast with: any model it can use, or exactly the ones you tick. It chooses among them by the question, and a model outside the list is refused even when a plan names it. All three stay editable from the pencil on the analyst's card, and an edit applies to your next question: earlier analyses are not re-run and each keeps a record of the model that produced it, so an exported report never attributes old numbers to a newly-chosen model. Reasoning models are also given a longer request deadline than chat models, because their time goes on thinking rather than output — sized for chat models, the analyst's required model class was the one that timed out.
Governed steps#
Governed steps are compiled, not described. When a step's numbers come from a governed semantic model, the plan names the model, its metrics and its dimensions, and the semantic compiler writes the SQL — with the fan-out refusals, rollup routing, row filters and column masks a hand-written SELECT cannot honour. Every name is checked against the catalog, and a block naming anything the model does not have is dropped whole: compiling the half that matched would answer a different question under a governance badge. A dropped block is not a refusal — the step falls back to written SQL and appears without the badge. A step whose SQL is later corrected by the self-check loses the badge too, because the correction is the analyst's SQL, not the compiler's. Where a rollup answered instead of the fact table, or a row filter narrowed the result, the step says so in words. Past analyses are all reachable from the picker beside New analysis.
Predictions from trained models#
Scored steps predict; the analyst never estimates. When a question asks what will happen, which rows are likely something, or for a predicted value, and a trained model from ML Models is in scope, the plan may add "score": { "model": "<name>" } to a step. The step's SQL selects the entities — the model's key column(s) when it is bound to a feature view, so the features are read from the view, or its feature columns otherwise — and the model supplies the prediction columns, joined onto the step's rows (at most fifty, the agent tool's cap). The step carries a scored badge naming the model, and under its table says how many rows were scored, the model's version and headline metric, where the features came from and which keys were not found. Every name is checked against the models the analyst actually loaded; a step naming anything else is simply not scored. A scoring that fails leaves the rows unscored and says so in the self-check note. The write-up is told which steps were scored and must report predictions as what the model estimates, naming it, never as something observed. A step whose SQL the self-check corrects is scored again on the corrected rows, and its note says so; a step re-run by hand loses its predictions with the badge — they belonged to the old rows — until the question is asked again. A prediction column that collides with one the SQL already returned is kept under a predicted_ prefix, so the model's numbers are the ones on the table the badge vouches for. The SQL writer is told, in the step's own goal, that the rows will be scored afterwards and what they must carry — the model's key column(s) from the source table, at most fifty rows, no stored predictions table and no prediction of its own. And a write-up that comes back as data rather than prose (an object of rows under answer, with caveats) is rendered as a table with its caveats, not reported as "no write-up". The self-check is told which columns the model added and that they exist in no table — it judges the SQL by the rows it was asked to return, and a correction it writes never selects the model's columns (live, one did, and died on a binder error); and the contribution and trend arithmetic the check and the write-up are handed reads only the observed columns, never the estimates (live, a table of order_id, prediction, probability was read as a two-period breakdown by prediction, and the write-up listed the resulting "total change" as a caveat beside the real one). The model's health rides along: the planner sees each scorable model's latest drift reading or evaluation verdict, and a scored step's badge and disclosure say it —"open drift alert (PSI 3.291 on 2026-09-14)" — so the write-up can warn beside the numbers it cites.
Forecasts, rankings and what a model can say. A trained forecast model in scope is offered to the planner as a forecast step ("forecast": { "model": "<name>", "horizon": N }): no SQL — the step's rows are the model's projected periods with their interval, the badge says forecast by, and the write-up is told the rows are a projection. A regression model is never a forecast: the planner is told so, and the write-up is told a scored regression step gives one estimate per row and is not a series. The most, least or top-N by a model's output — most anomalous, most likely enterprise, highest predicted value — is decided by the platform on the scored rows ("rank": { "by": "<output column>", "desc": true, "limit": N } inside score), never in SQL, where the column does not exist; the disclosure says "ranked by anomaly_score (highest first), top 10 of the 50 scored". A step whose goal names a model in scope is scored by it even when the planner forgot the block, because naming the model is the request. The tool's model notes — what each class means, each group's size and typical row, the trainer's warnings — travel with the scored step (a collapsible under the disclosure) and into the write-up, so it can describe a group rather than say it was not told. And the analyst refuses rather than improvises: a question naming a model nobody has ("the churn model") stops and asks, naming the models that exist, instead of scoring with another; rows missing most of a model's feature columns are not scored, and the missing ones are named; a self-review correction that reads a model's column is refused in code and the original result stands; a correction that fails says so, and the write-up is told the step did not fail. A scored step's rows are quoted to the write-up in full (up to the fifty the model scored) — summarised, fifteen scored orders became "order_id total=22104" and a findings table built from it — and an identifier column is never totalled in the facts at all.
What-if scenarios#
What-if scenarios ride the same compiler. A compiled step offers the two things that can honestly vary — the model's declared parameters and the values of filters the step already has — and recompiles the same query with one thing changed, so the difference between the numbers is that change and nothing else. The result is labelled with exactly what was assumed, shown beside the measured result rather than replacing it, and never folded into the findings. Where both sides are a single row the delta is computed in code; where they are grouped the comparison is refused rather than matching rows by position. A scenario that varies nothing is refused too — re-running an identical query under a “scenario” heading invites the reader to conclude a change was tested and made no difference.
Verified answers and the self-check#
Verified answers. Mark a finished analysis verified, or flag it wrong with a reason — a flag without one leaves the next reader where they started. The verdict records who and when, and is pinned to a fingerprint of the steps it judged: each step's SQL and the governed model that compiled it. Edit a step, or let the self-check rewrite one, and the verdict voids — shown as void rather than dropped, because the reader needs to know a check existed and no longer covers these queries. Results and prose sit outside the fingerprint: the same SQL over refreshed data is the same checked work. Ask a question someone has already judged and the verdict is offered, not applied — asking re-runs the queries against today's data, and only still-valid verdicts are offered. Every question runs a transparent loop: plan the steps, write and run the SQL (the same generator and governed metric definitions the BI analyst uses, SELECT-only), check its own work — a wrong step re-runs with corrected SQL, a doubtful one is presented flagged — then write up findings where every number cites the step it came from. Analyses persist per analyst, follow-up questions see the earlier answers, and the whole trace exports as a PDF.
Charts, pinning and computed reasoning#
Every step that has something to show gets its own chart, so a three-query answer arrives as three visuals rather than one headline picture. Any step can be pinned to a dashboard — the widget keeps that step's SQL and chart, and re-runs it on refresh — or edited and re-run with your own SQL. When you edit one, the findings above are marked written before a step was re-run until you rewrite them, and the step drops the check verdict that judged the previous query: a green “Check passed” must never vouch for SQL the analyst never saw. After each answer it offers follow-up questions, and a brand-new analyst offers starter questions written from its own schema.
Three kinds of reasoning are computed in code, not narrated, because a plausible number reads exactly as confident as a correct one. A step returning two periods side by side gets contribution analysis — each member's change and its share of the total change, with drivers separated from the offsets a headline hides, and shares left above 100% when winners and losers cancel. A time-series step gets its trend and outliers (median-based, so the spike does not hide inside the statistic judging it) and, with at least eight periods, a projection labelled as an estimate carrying its method and fit error — with less history it refuses to extrapolate at all. And when a question genuinely cannot be answered without knowing something — an unstated time range, a word your schema defines two ways — it asks before querying, offering the assumption it would otherwise make so accepting takes one click.
Concurrency and schedules#
Steps run concurrently — a three-query analysis issues its queries at once, bounded so one question cannot become everyone's rate limit. That is safe because no step consumes another's output: each writes its SQL from its own goal, and results are read only once every step has finished. Results are matched back to steps by index, never by completion order, so the trace still reads top to bottom; you just see steps finish out of order. Identical SQL inside one analysis runs once — the cache holds the in-flight query, so two steps asking the same thing at the same moment share one round-trip. It is never carried across questions, because between two questions the data can move and a cached row served later is a number that is no longer true.
Schedule re-runs an analysis hourly, daily or weekly — and it re-runs the saved queries, it does not ask the question again. Re-planning would let the SQL differ between runs, so the number you watch over time would have a definition that moves underneath you. The steps are pinned instead, no model is called, and the findings are marked written before a step was re-run until you rewrite them. A human verdict survives (the SQL it judged did not change); a what-if is dropped, because one computed against last week is not a what-if against this week. The digest reports what moved — precisely for a single-row result, as a row count where results are grouped, since matching rows between runs would invent findings — and says so plainly when nothing changed. Run now takes the identical path.
Sharing an analyst#
Sharing an analyst shares the analyst, not your data access. Grant it to IAM groups and recipients can open it and ask their own questions — but every query they run is authorised as them, with their dataset grants, their warehouse credentials and their row filters. A shared analyst can therefore return different numbers to different people, which the share dialog states before you grant, blocking problems first. An analyst scoped to all local datasets resolves per reader, so shared it points at the recipient's data rather than yours. Your saved analyses stay yours — they hold rows fetched under your access — and renaming, editing and deleting remain owner-only. A grant is refused outright if the recipients' IAM model rules do not allow the analyst's model.
Provenance and export#
Every step that ran a query carries a Where these numbers came from disclosure. The tables it names come from the SQL that actually ran, never from the model definition — models get edited, and a panel built from today's definition would misdescribe a query that ran against yesterday's. So a step answered by a rollup names the rollup and says the fact table was not read, and a step whose SQL you edited says no governed definition vouches for it. Under each table, where the evidence exists, sits what a prep flow combined to build it and what the warehouse's own lineage records upstream — loaded only when you open the disclosure. A lookup that fails says so, rather than reporting “nothing upstream”.
Export data gives you the analysis as a workbook rather than a document: an overview sheet with the questions, findings and — per step — its governed model, check verdict, human verdict and whether it was edited by hand, then one sheet per step result. A spreadsheet gets mailed around and outlives the query behind it, so the qualifiers travel in the cells: the file is stamped with its export date, and what-if rows get their own sheet with the assumption in the first column — an unlabelled hypothetical becomes a measurement the moment someone copies it. Steps that returned no rows are skipped rather than exported as empty sheets.
Building a dashboard#
- 1
Create a dashboard
Give it a home folder — folders are how you keep dev and production content apart. - 2
Add a visual
The right-hand builder pane takes your source (table, prepared table, or a governed metric), the fields to plot, and the chart type. - 3
Or describe it
The AI tab writes a whole dashboard, or a single visual, from a sentence. Point it at a table and it writes SQL you should read before trusting the chart; point it at a semantic model and the compiler writes the SQL instead. - 4
Arrange and publish
Drag and resize, then publish to make it visible to the people you've shared it with.
You can also send a chart straight from the SQL workbench with Add to dashboard once a query returns something worth keeping.
Generate a dashboard from a governed model#
Generate dashboard with AI offers two sources: your tables, or a semantic model. Choosing a model governs the whole dashboard.
On the governed path the planner is shown the model's declared metrics and dimensions — deliberately not the physical tables or columns, because showing it the schema invites it to reach past the layer this path exists to enforce. It picks names from that vocabulary and the compiler turns each choice into SQL. So the worst a bad suggestion can do is ask a well-formed question nobody wanted, rather than compute a number nobody defined.
Everything it returns is checked before it reaches the compiler: an undeclared metric or dimension, a time grain on something that is not a time, a rollup the compiler does not know, a chart shape that cannot render what it was given, or a duplicate query wearing a second name. Each is refused with a reason naming the offending item, and the rejections are shown in the dialog rather than swallowed — a generate that proposed twelve and built nine has to say which three it lost.
Metrics only, chosen once
How a widget gets its data#
Every chart widget stores the SQL it ran and a snapshot of the rows that came back. Which of those a viewer sees depends on the widget's data mode, and the difference decides freshness, cost, and — in one case worth reading carefully — whether the number is right.
| Mode | What happens at view time | Use it when |
|---|---|---|
| Import (default) | The stored snapshot renders. No query runs, nothing touches your warehouse, load is instant. | Almost always. Pair it with a refresh schedule. |
| Direct | The widget's SQL re-runs against the warehouse on every view, for the current truth. | A number that must be live, on a dashboard few people open. |
Published and embedded dashboards always render the snapshot
The row cap, and the totals it can quietly break#
A snapshot holds at most 500 rows. That is fine for a chart of twelve months or twenty regions, and it is a problem for a chart built over raw rows — because the browser is what sums duplicate categories together.
A capped snapshot sums a subset and calls it the total
sum(amount) by region over a 50,000-row table and the snapshot keeps the first 500. The renderer then adds up the regions it can see, and draws a bar chart that looks entirely normal and is wrong — no error, just a smaller number. Widgets in this state carry a Partial badge, and the fix is the next section.Aggregate in SQL#
Turning on Aggregate in SQL compiles the chart into a GROUP BY and makes the database do the adding. One row comes back per category instead of thousands of raw rows, so the totals are complete and the cap stops mattering:
-- what the widget stores
SELECT region, amount FROM sales
-- what actually runs with "Aggregate in SQL" on
SELECT region, SUM(amount) AS amount
FROM (SELECT region, amount FROM sales) AS _dq
GROUP BY regionSupported aggregates are sum (the default), avg, min, max, count and count_distinct. Wrapping SQL that already aggregates is harmless — grouping rows whose key is already unique returns them unchanged.
Why it isn't just switched on everywhere
Aggregating in SQL fixes the values, not the row count
VITE_BI_SNAPSHOT_ROWS_CAP.Incremental refresh#
For a large table where only recent rows change, a widget can re-query just a recent window on a chosen date column and keep the previous snapshot's older rows. A refresh then reads a week instead of three years. Bind it in the builder: pick the date column, then Full re-query or a window of 7, 30, 90 or 365 days.
It assumes history does not change
avg and count_distinct correct, since those cannot be combined from partials. The trade you are accepting is the usual one: a late-arriving edit to a row outside the window will not appear until a full refresh. If your source back-dates corrections, either widen the window past your correction lag or leave incremental off.Chart types — all 26, with required fields#
Each chart declares which fields it needs. The builder only offers types your selected columns can satisfy, so a chart missing a required field cannot be created.
Comparison and trend#
| Type | Required fields | Optional | Use for |
|---|---|---|---|
bar | xField, yField | seriesField, stacked | Vertical columns; ranking and comparison |
hbar | xField, yField | — | Horizontal bars; long category names |
line | xField, yField | seriesField | Change over time |
area | xField, yField | seriesField | Trend with volume emphasis |
scolumn | xField, yField, seriesField | — | Stacked vertical columns |
shbar | xField, yField, seriesField | — | Stacked horizontal bars |
combo | xField, barField, lineField | — | Two measures at different scales (volume + rate) |
waterfall | xField, yField | — | How a total is built up from contributions |
Part of a whole#
| Type | Required fields | Use for |
|---|---|---|
pie | nameField, valueField | Few slices — beyond ~6 it stops being readable |
treemap | nameField, valueField | Many parts, nested by size |
funnel | nameField, valueField | Stage-to-stage drop-off |
nightingale | nameField, valueField | Polar rose — cyclical categories |
sankey | xField (source), yField (target), valueField | Flow between nodes |
Single value#
| Type | Required fields | Optional | Use for |
|---|---|---|---|
kpi | valueField | label, targetField | One headline number, optionally against a target |
gauge | valueField | label, targetField, max | Progress toward a ceiling |
Distribution and relationship#
| Type | Required fields | Optional | Use for |
|---|---|---|---|
scatter | xField, yField | sizeField | Correlation between two measures |
heatmap | xField, yField, valueField | — | Density across two dimensions |
boxplot | xField, yField | — | Spread and outliers per category |
radar | xField, yField | seriesField | Several measures compared across entities |
Tables and geography#
| Type | Required fields | Optional | Use for |
|---|---|---|---|
table | — | columnFormats | Exact figures read row by row |
matrix | rowField, colField, valueField | rowSubField, condFormat | Pivot. rowSubField makes rows expandable groups with subtotals; condFormat colours cells. |
map | locationField, valueField | — | Choropleth by region |
bubblemap | locationField, valueField | — | Magnitude at points |
Specialised#
| Type | Required fields | Optional | Use for |
|---|---|---|---|
barrace | xField, yField, timeField | — | Animated ranking over time |
wordcloud | textField | valueField | Term frequency; weight by valueField when you have one |
ontology | spec | — | AI-built knowledge graph: subject–predicate–object triples across datasets, warehouses and KB knowledge graphs; nodes and edges are clickable |
Why it works this way
Two widgets that plot nothing#
Not everything on a dashboard is a chart. Two widget types take no data at all, and they are what turns a wall of numbers into something a reader can follow without being told what it means.
| Widget | Takes | Use it for |
|---|---|---|
text | A title and Markdown content — no fields, no query. | The executive summary at the top, a definition of a metric people keep misreading, a note on why last month looks odd. |
image | An uploaded image or an image URL. | A logo, an architecture diagram, a photograph — anything the numbers are about but cannot show. |
A dashboard nobody can read is a dashboard nobody uses
Number formatting#
| Option | Values | Notes |
|---|---|---|
format | currency | percent | Omit for a plain number |
currency | ISO 4217 code | Defaults to USD when format is currency |
decimals | 0 – 4 | Fixed fraction digits. Leave undefined for automatic/compact (1.24M). |
columnFormats | per column | Table widgets only — format each column independently (number | currency | percent, with its own currency and decimals). |
Analytics options#
Available on every chart spec; each renderer applies the ones it supports. This is where most of the analytical value lives, and it is the part people miss.
| Option | Values | Applies to | Effect |
|---|---|---|---|
drillFields | string[] | bar, hbar, pie | Drill hierarchy; level 0 is the configured field. Readers descend one level per click. |
dateGrain | auto | day | week | month | quarter | year | line, area | Default bucketing; viewers can toggle it. |
compare | prior_period | prior_year | line, area (single series) | Overlays the previous bucket or the same bucket last year. |
running | boolean | line, area (single series) | Cumulative running total. |
trend | boolean | line (single series) | Linear trend line. |
forecast | number of buckets, or a registry model | line (single series) | Projects ahead — seasonal exponential smoothing when the history shows a season that beats a straight line, else a linear trend — with a residual band that widens with distance. Or draws a registry forecast model's projection. |
A projection is not a plan
Conditional formatting (matrix)#
| Mode | Configuration |
|---|---|
scale | Continuous colour scale across the values, with an optional base colour. |
rules | An ordered list — first match wins. Each rule is an operator (gt, gte, lt, lte, eq, neq, between), a value (plus value2 for between, inclusive) and a colour. |
Reference lines#
A horizontal reference line on cartesian charts, in one of two modes: avg draws the series average, or value draws a fixed number you supply. Both take an optional label — use it, because an unlabelled line invites the reader to guess what it means.
Making it interactive#
- Global filters
- Dashboard-level controls — value pickers, numeric ranges and relative-date presets (last 7 days, this quarter). Save defaults so the dashboard opens on the right view.
- Cross-filtering
- Click a bar and every other widget filters to it. The fastest way to answer "what's driving that spike" without building anything.
- Drill hierarchies
- Define year → quarter → month, or region → country → city, and let readers descend a level at a time.
- Drill-through
- Open the underlying rows behind any data point — the answer to "is this number real?" and the fastest way to spot a broken join. Everything narrowing the view goes into the query: the widget's own filters, the drill level you clicked into, the active cross-filter. The 1,000-row cap applies after that, so the count is the real number of matching rows and the cap is disclosed when it bites. A category computed in the widget's SELECT list (DATE_TRUNC(…) AS month) has no column to trace back to, and that is refused rather than answered with the whole table.
- Ask AI (embeds)
- On an EMBEDDED dashboard, readers can ask a follow-up question of its data in natural language, including a drill-down on what they clicked. Inside the app this is not a separate control — the AI analyst in the builder answers the same questions with more of the model behind it.
AI insights#
Each visual can generate a written reading of what it shows — the notable movement, the outlier, the thing a person would say out loud. There is also a dashboard-level digest summarising the whole page.
Careful
Scan — computed, not generated#
Scan in the toolbar is the opposite kind of thing: it runs the obvious checks across every widget's snapshot using arithmetic, not a model. No call, no cost, no wait, and the same answer twice. It looks for three things and states the bar for each:
- Trends — a least-squares slope of at least 2% per period, over at least five points.
- Outliers — 3 or more MAD from the median. Median and MAD, not mean and standard deviation, because an outlier inflates the very standard deviation a naive check would judge it by.
- Concentration — one member holding 60% or more of a total, across at least four members. (With two, one is always over half.)
Findings are ranked by how far each cleared its own threshold — shown as 2.6× — which is what makes an outlier and a trend comparable at all.
Finding nothing is not an all-clear
Alerts and scheduled reports#
- Alerts — watch a metric against a threshold and notify when it crosses. Delivered in-app and by email.
- Scheduled refresh — rebuild imported data on a cadence so the dashboard isn't stale.
- Incremental refresh — re-query only a recent window instead of the whole table. Covered in how a widget gets its data, along with the assumption it commits you to.
- Scheduled reports — email a digest of the dashboard on a schedule.
Aggregate in SQL — complete totals on any table size
GROUP BY compiled from the chart), so the warehouse returns complete grouped rows instead of raw ones. Existing widgets are not switched automatically — turning it on can change the number they display, which is the owner's call — they show a Partial badge when their snapshot hit the cap, with an "Aggregate in SQL" toggle in the widget menu.On a self-hosted deployment these need the scheduler running — see Install & deploy.
A rule's basis is either the latest refreshed values (the default) or the forecast: the aggregate over the next N projected periods of a single-series line or area widget, from the built-in forecaster or from the registry model attached to the chart. “Notify me when projected revenue for the next three months falls below target” is a rule, not a glance.
Sharing, export and embedding#
| Method | Who can see it | Notes |
|---|---|---|
| Group share | Named users or IAM groups | Read-only; respects the underlying data grants |
| Public link | Anyone with the URL | No sign-in — treat the URL as the secret |
| Embed key | Any site you allow | Domain-restricted; see Web embedding |
| Export | Whoever you send the file to | PDF or PowerPoint for the page, Excel/CSV for the data |
Export as a PowerPoint deck#
Export → Export to PowerPoint builds a branded deck. You choose which visuals to include (grouped by page), a model to write the prose, and optionally an instruction for tone, audience or emphasis.
The figures are the dashboard's figures. Every slide is filled from the same saved snapshot the card on screen renders — never re-queried while the deck is built. A deck that disagrees with the dashboard it came from is two sources of truth, and the one in the meeting room is the one people act on.
The model writes sentences and is not allowed to calculate. It receives the computed values as text it may quote, so it can write “revenue peaked in March” but not “revenue grew 12%” — the second is arithmetic, and arithmetic is done in code or not at all. Any figure in its prose that did not come from the data is stripped before it reaches a slide. That holds even if you explicitly ask for growth percentages in the instruction box.
A widget with no saved snapshot cannot become a slide and is listed with the reason (refresh the dashboard first) rather than quietly missing from a deck you believe is complete. Visuals PowerPoint cannot draw — sankey, geo map, bar race — become a table of the same data with the substitution printed on the slide, and category or row caps are disclosed on the slide itself.
Check before publishing
Row filters and hidden columns on shares#
A dashboard grant (Admin → IAM → Access) can carry a row filter (the grantee only sees rows where a column matches allowed values) and hidden columns (columns removed entirely). Both are enforced server-side: a grantee whose grant carries any restriction never reads stored widget data directly — the server applies the filter and drops masked columns before anything leaves it, on snapshots and on live direct queries alike. Semantics follow the rest of IAM: one unrestricted grant (directly or via any group) makes the whole dashboard visible, and a column is hidden only when every applicable grant hides it.
Paginated reports#
A dashboard is a grid you scroll and resize. A paginated report is the other shape: a fixed page, a flow of blocks down it, and content that continues onto the next page when the room runs out — the thing a month-end pack, an invoice or a regulatory return has to be, because somebody prints it and the page count matters. Open Data & BI → BI Workspace → Reports.
Reports and dashboards share everything below the layout. A report's chart block is a dashboard widget — the same query, the same cached rows, the same chart spec and the same renderer — so nothing about a number changes when it moves from a tile to a page.
Blocks#
- Heading
- Title, section or sub-section. The first two rule off underneath.
- Text
- A paragraph. Wraps to the content width and flows across pages.
- Spacer
- Vertical room, in points. 72 points is an inch.
- Page break
- Starts the next page. Ignored when the current page is still empty, so a break at the top does not print a blank sheet.
- Chart
- A widget's chart at a height you set. Exported as an image drawn from the same rows the preview shows.
- Table
- A widget's rows, printed. This is the block that continues onto the next page and redraws its header row when it does.
Why the preview is the layout, not a picture of one
Reusing a dashboard's widgets#
Add from a dashboard puts a widget you already built onto a page. It comes across whole — its query, its saved rows and its chart spec — so the page cannot disagree with the tile it came from, and you choose whether the page shows the visual or the rows behind it. Widgets from every page of a multi-page dashboard are offered. A widget with no saved snapshot is not offered at all, because it would print an empty box; refresh the dashboard first.
Page setup, header and footer#
Choose A4, Letter, Legal or A3, portrait or landscape, and a uniform margin. The running header and footer each have a left, centre and right slot that accept tokens: {{page}}, {{pages}}, {{title}}, {{date}} and {{time}}. A new report opens with Page {{page}} of {{pages}} in the footer, because the first thing anybody misses on a printed report is which page they are holding.
The bands are stamped after the layout, not during it. The total page count is not knowable until the last block has been placed, and a footer that says “of 3” on a four-page report is worse than no footer at all. The date and time are fixed once per export so every page of one file agrees.
How a long table breaks#
A table taller than the space left is not clipped and not shunted whole onto the next page. It fills what is there, breaks, and continues — with its header row drawn again on every page, because a column of numbers with no heading on page four is unreadable. A table that cannot fit its header plus at least one row starts on the next page instead of stranding a header at the bottom of this one. Max rows caps what the block prints; leave it blank to print every row the query returned.
Generating a report with AI#
Generate with AI runs the dashboard generator's machinery with one difference of shape. Pick a table, optionally say what the report is for, and the planner returns an ordered outline of sections rather than a set of tiles: each section is a heading, the question behind it, whether the answer belongs in a chart or in a table somebody will check a row of, and whether it should start a new page.
- 1
Pick the table
Optionally say what the report is for — the planner writes a different outline for a board pack than for an operations check. - 2
Review the planned sections
Each shows its question, whether it will be a chart or a table, and whether it starts a new page. Untick any you don't want. - 3
Build
Every section runs the same BI turn a dashboard widget does, and becomes a heading plus a chart or table block. - 4
Edit and save
Reorder, retitle, change the page setup and the bands in the designer. - 5
Export PDF
A section whose query fails or returns no rows is reported with its reason and skipped; the rest of the report is still built. If nothing could be built the dialog stays open with the reasons, rather than handing back an empty document. The planner runs through the same IAM-gated server route as every other BI generation and is traced as BI Agent: Report outline.
Export#
Export PDF builds a real vector-text PDF: headings, paragraphs and table cells are selectable text, not a screenshot. Only charts are images, rasterised from the nodes the preview is already showing, so the exported chart is the chart you were looking at. A chart with no rows prints a visible [chart unavailable] marker rather than a silent gap.
Reports are owner-only
Versioning and promotion#
- Version history — dashboards keep prior versions you can compare and restore.
- Dev → prod promotion — build in a development folder and promote a reviewed version into production, instead of editing what people are watching.
- Git export — export dashboard and model definitions as files for review in your own repository.
If a dashboard is slow#
- Switch heavy widgets from direct query to imported snapshots with a refresh schedule. This is usually the whole fix.
- Turn on Aggregate in SQL. It returns one row per category instead of thousands, so it makes the dashboard faster and the totals correct at the same time.
- Use incremental refresh if the refresh itself is what's slow rather than the view.
- Aggregate in a prepared table rather than charting millions of raw rows.
- Reduce the number of widgets on one page — each is a query.
- Apply a default date filter so the dashboard doesn't open on all history.