Reference / Refresh
verify_dashboard
Prove the query service answers the plans a viewer's page will issue, before the viewer opens it. Parameters, per-plan statuses, filters, refusals.
Proves that a dashboard whose data Dashies keeps on its query service actually answers the queries a viewer's page will issue - before the viewer opens it.
It issues the plans the page issues, read off the stored spec, through the same route and the same capability a viewer gets, and reports what came back: how many rows arrived at each grain the page asked for, how long it took, and on any failure the query service's own error text, verbatim.
Which plans it issues
- A page whose markup you wrote (
look): everydashies.datasubscription's grain for each dataset it asks for - itsby, or the declared grain when that entry names none - folded into one request per dataset, exactly as the runtime folds them, at the default filter state the page's owndashies.filtersets before its first paint (a call outside every function, or one in adashies.datacallback guarded by anifthat testspage.filtersfor absence). A call with no options asks for every dataset it may read; a call whose options name any dataset asks for exactly those, so a dataset every call leaves unnamed is asked for by nobody and gets no plan of its own. - A page of managed tiles (
tiles): each dataset's declared grain - every dimension live, every measure - which is the widest request a tile can make and stands in for the narrower ones the tiles actually ask. Acustomtile's own subscriptions are added for the datasets itsreadsnames. - A dashboard with no stored spec: the declared grain, as above.
When no subscription asks a dataset for its declared grain, that grain is still
issued, as a separate row labelled declared grain (no page subscription asks for it), and counted apart in unrequested: a width refusal there is a fact
about the declared grain and not about the page, while a failed there still
names a dead object or host. A dataset no subscription asks for at all is
checked by that row alone, and page.notes names the calls that leave it
unasked.
A subscription whose options are computed rather than literal (dashies.data(cb, OPTS)) cannot be read; it contributes no grain, the report says so under
page.notes, and only the declared-grain row checks the datasets it draws. The
same list names a by the dataset would refuse.
The default-filter reading is deliberately strict: a dashies.filter call counts
only when its path is proven, and one it cannot prove - semicolon-less code, a
pre-boot call inside an IIFE or a DOMContentLoaded handler, the else branch of
a presence test, an early-return guard, a guard inside try, or a guard through
an alias not named filters - is skipped and named under page.notes, and the
plans are verified at the empty state instead.
Behaviour hints for clients: read-only, not open-world. It writes nothing.
Why this exists
validate_cube_sql proves the SQL runs in your warehouse. get_refresh_status
proves the refresh wrote rows. Neither proves the query service can answer the
plans the page issues, and the page cannot be opened from an agent.
That gap has already shipped a broken dashboard: a report was published, the refresh reported success with exactly the predicted row count, and every panel on the page read "the served query was refused (502)". Every instrument available at the time said the dashboard was fine.
Call it after the first refresh lands, and after every republish.
Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
slug | string | yes | Canonical slug of the dashboard to verify. |
filters | object | no | A filter state to exercise, keyed by dimension: one value, or a list for a multi-select. Only dimensions a served dataset declares are accepted. It sits on top of the page's own default state, key by key. |
all_values | boolean | no | Also exercise every distinct value of every declared dimension the filters argument does not fix, one plan per value, over the values the answered plans carried. Capped; the output says when the cap binds. |
workspace | string | no | Workspace slug. Any member of that workspace may verify its dashboards. |
Verify each value a filter offers. A page with a region filter is not
verified by the unfiltered load alone: a filtered plan is a different query and
can fail on its own. Pass filters: {region: "EMEA"} for one value, a list for
several, or all_values: true to sweep them.
Cells print as your page receives them. Every cell in first, total and
the rows per grain has the type and value dashies.data hands your page for it:
a measure is a number when it is a safe integer, a decimal whose significant
digits survive a float64 round trip, or a DOUBLE result such as an avg, and
its exact digits as a string otherwise (a count beyond
Number.MAX_SAFE_INTEGER, a decimal with more significant digits than a float64
keeps); a date or timestamp measure is epoch milliseconds; a dimension is a
string; a null cell is null. A declared ratio is null wherever the page gets
null.
A date dimension is swept by its day. The query service answers a DATE or
TIMESTAMP cell as epoch milliseconds; the report prints a date dimension, and
all_values filters by it, as the YYYY-MM-DD day the page's own filter menu
offers - the value the page sends. Pass a date filter yourself in the same form:
filters: {month: "2026-05-01"}. On a TIMESTAMP dimension whose members carry
a time of day, a day literal matches only that day's midnight rows, exactly as
a viewer choosing that member sees; a swept value that answers zero rows at the
grain says so in a per-plan note rather than reading as a healthy plan.
Returns
Verified "revenue-overview": 2 of 3 plans this page issues answered. 1 did not: mix [plan=enterprise]. 1 declared-grain plan this page does not issue, counted apart: 1 refused as too wide.
Plans (the requests this page's own dashies.data subscriptions issue, every grain a dataset is asked for folded into one request as the runtime folds them, at the page's default filter state; issued one at a time):
- ok mrr [region=EMEA] 412 ms rows: 13 over 2 grains ([] 1; [month] 12) first is at [] first: {month: null, region: null, mrr: 340211} total (unfiltered): {month: null, region: null, mrr: 2841003}
- ok mix 380 ms rows at grain [plan]: 4 first: {plan: "pro", accounts: 118} total (unfiltered): {plan: null, accounts: 511}
- failed mix [plan=enterprise] 118 ms the query host failed at span "execute". The host answered 500: {"kind":"failed","span":"execute","message":"IO Error: Connection error for HTTP HEAD to 'https://.../mix/part-0.parquet' (HTTP 404)"}
- refused mrr [region=EMEA] declared grain (no page subscription asks for it) 9 ms refused as too wide (query_answer_rows_exceeded): the answer holds at least 215040 cells against a bound of 110000 on node n1
Failures, in the tier's own words:
- mix [plan=enterprise]: the query host failed at span "execute". The host answered 500: {"kind":"failed","span":"execute","message":"IO Error: ... (HTTP 404)"}
Page: 2 dashies.data subscriptions read from look.html; default filter state set by the page's own dashies.filter: region=EMEA.
BEGIN_JSON
{"slug":"revenue-overview","served":true,"answered":2,"unrequested":1,"plans":4,"page":{"kind":"look","subscriptions":2,"default_filters":{"region":"EMEA"},"notes":[]},"results":[]}
END_JSON
The first line is the verdict to relay. It counts the plans the page issues; the declared-grain rows the page does not issue get their own closing clause. Never report a dashboard as done on a failed plan, and relay the failure text unchanged - the host's sentence names the object or the limit that a summary of it would lose.
A plan carrying several grains reports the rows at each of them (rows: 13 over 2 grains ([] 1; [month] 12)); a plan carrying one prints rows at grain [plan]: 4. The JSON carries every grain under results[].grains with who asked for it.
The counts satisfy
answered + failures + reshaping + pending + unrequested + not_run === plans.
A declared ratio measure (ratio: { num, den }) appears under its own key in
first and total, computed from the answered operands exactly as the page
computes it for author code, and the plan's line names it as computed, in a
legend that opens ratio measures (computed here from the answered operands, as the page computes them; ...) and ends aov_aud = revenue_aud / orders. A plan
that answered without a declared measure names that too, after declared measures NOT in the answer:, so a key your page reads that never arrives is one
read here rather than a dash on the page.
Per-plan status
status | Meaning |
|---|---|
ok | The query service answered. |
refused | The service declined the plan - most often a grain too wide. On a plan the page issues that is what a viewer sees; on a declared grain (no page subscription asks for it) row it says nothing about the page. See below. |
reshaping | The dataset is being re-extracted under the spec you just published. Not a failure, and no action is needed. See below. |
pending | The dataset is declared and its first extraction has not landed, or the workspace's query host is still starting. Not a failure, and nothing on the dashboard needs changing. See below. |
failed | The query ran and failed. The host's own message is quoted. |
unavailable | The service was not reached: no host yet, unreachable, or slower than the verification's budget. |
denied | The route refused the capability. A Dashies defect, not a fact about your dashboard. |
not_run | The verification's time budget ran out first. Nothing was learned about this plan. |
A reshaping plan is the normal state during a republish. The stored
objects were extracted under the previous spec, the extraction under the current
one is already dispatched, and the plan answers once it lands. It is counted
apart from both answered and failed, so a verdict where every non-answered plan
is reshaping reports no failure at all. Re-run after get_refresh_status shows a
newer successful run; do not retry immediately, and do not report the dashboard
broken.
A pending plan is a dataset waiting on its first refresh. The dashboard
declares it and nothing has extracted it yet, which is every warehouse
dashboard's state until its first refresh runs, so there is nothing to answer
it from. It is counted apart from answered, failed and reshaping, and the
verdict line reports it as not yet extracted. Unlike reshaping, no refresh
is necessarily on its way: ask for one with trigger_refresh if none is
scheduled, wait for get_refresh_status to report a successful run, then
verify again.
A pending plan can also be the workspace's query host still starting.
The first publish in a workspace creates the machine that answers its served
dashboards, and that machine takes about a minute or two to come up. Until it
does, every plan is reported pending with the per-plan text naming
query_host_booting, the verdict line reports it as waiting on the query host, and a viewer's page paints its pending state rather than an error: a
tile reads "Updating. The first refresh will fill this in.", a metric tile's
value reads "Updating", and a page drawing its own markup gets a pending
subscription and decides for itself. That is the page's shared pending wording;
no refresh is involved here and nothing on the dashboard needs changing, so
verify again a minute later.
A refused is not the same as a failed, and the difference decides what you
do next. A refusal is the service declining a request as too wide. It applies
to ONE GRAIN: the service answers each grain a page asks a dataset for as its own
question, so a refused grain leaves that dataset's other grains answering, and
each grain in grains carries its own refused sentence or null. On a plan the
page issues, that is exactly what a viewer sees on the tiles drawing that grain:
subscribe at the grain you draw with by, bound the dimensions the dataset
declares, or declare fewer of them. On a row the page does not issue - the declared grain,
every dimension live at once, when no subscription asks for it - it is a fact
about that grain and nothing else, and the verdict counts it apart. A failed
or unavailable, by contrast, fails every narrower plan the same way, whichever
row it lands on.
The dry run already estimates this bound: a served dataset whose declared grain
can exceed it gets a declared_grain_over_cell_bound warning at
/datasets/<name> naming the estimate, the bound and the widest dimension, so
the shape is visible before the first refresh rather than after it.
States where nothing is verified
- Not refreshable. The dashboard's numbers are whatever the page carries; there is no served path.
- Data inside the page. The dashboard does not use the query service, so there is nothing for it to answer.
- Pending. On the served path with no data yet: the first refresh has not
run, so a page issues no query either. Ask for a refresh with
trigger_refresh, pollget_refresh_statusuntillast_successful_runmoves, then verify again.
Each is reported plainly rather than as a pass. Nothing is ever faked: a plan that was not issued says so.
Cost
Every plan is one query on your query host, issued one at a time, and the tool
is capped so a verification cannot become the load it exists to detect. When the
cap binds the output says so and names filters as the way to narrow.