Skip to content

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): every dashies.data subscription's grain for each dataset it asks for - its by, 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 own dashies.filter sets before its first paint (a call outside every function, or one in a dashies.data callback guarded by an if that tests page.filters for 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. A custom tile's own subscriptions are added for the datasets its reads names.
  • 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

ParameterTypeRequiredNotes
slugstringyesCanonical slug of the dashboard to verify.
filtersobjectnoA 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_valuesbooleannoAlso 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.
workspacestringnoWorkspace 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

statusMeaning
okThe query service answered.
refusedThe 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.
reshapingThe dataset is being re-extracted under the spec you just published. Not a failure, and no action is needed. See below.
pendingThe 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.
failedThe query ran and failed. The host's own message is quoted.
unavailableThe service was not reached: no host yet, unreachable, or slower than the verification's budget.
deniedThe route refused the capability. A Dashies defect, not a fact about your dashboard.
not_runThe 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, poll get_refresh_status until last_successful_run moves, 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.