Dashboards and Notebooks
Build dashboards and notebooks as Malloy documents, and add filter controls with givens
A dashboard or notebook in Credible is one .malloy document. It names the views to show, lays them out, and declares the filters a viewer can change. Every viewer runs it as themselves, so a source with access rules shows each person only their own rows.
The two differ only in shape:
- A dashboard is read at a glance: tiles on a grid.
- A notebook is read top to bottom: text between the results.
Both get the same filter controls, shareable URLs, and click-through.
Ask the agent
You rarely write one by hand. Ask in a chat, for example "build me a dashboard of revenue by brand and by month, with a filter on product category".
- In a draft package (a modeling chat), the agent writes the document as a file in the package,
dashboards/<name>.malloyornotebooks/<name>.malloy. It ships with the package when you publish. - In an analysis chat, the agent saves the document to your workspace. It reads a published model and doesn't change the package.
To change one, ask again in a chat: "add a filter on brand", "add a tile with order count by month".
What the document looks like
A dashboard is an ## artifact tag listing its tiles, followed by the views those tiles name:
##! experimental.givens
## artifact { title="Storefront overview" tiles=["overview -> revenue_trend", "overview -> revenue_by_brand"] } dashboard { columns=12 }
import { order_items, products } from '../storefront.malloy'
# label="Category" control=select suggest { source=products dimension=category }
given: CATEGORY :: filter<string> is f''
source: overview is order_items extend {
# colspan=8
# label="Revenue by month"
view: revenue_trend is sales_by_month + { where: category ~ $CATEGORY }
# colspan=4
# label="Revenue by brand"
view: revenue_by_brand is sales_by_brand + { where: category ~ $CATEGORY }
}tiles=[…]names each tile assource -> view, in order.dashboard { columns=12 }sets the grid width.# colspan=Non a view sets how wide its tile is, and# breakstarts a new row.- A view's tags go on the lines above
view:, never inside its braces.
A notebook uses kind=notebook and adds text entries between the query tiles.
Filter controls are givens
A filter control is a given: a named parameter declared with given: and read with $NAME.
- Declare the given with its control tags.
control=selectrenders a dropdown, andsuggest { … }fills it with a dimension's values. The defaultf''matches every row. - Bind it on each tile that should respond to it, with a refinement:
+ { where: category ~ $CATEGORY }. Afilter<…>given binds with~, and a plaindateornumbergiven binds with>=,<=or=. Adateornumbergiven always filters from its default value (for exampleSINCE :: date is @2023-01-01), because unlikef''it has no default that matches every row.
Only givens that some tile reads become controls. A given the model already declares is imported by name rather than declared again.
Use givens, not the legacy #(filter) annotation, for new filters. Three cases keep #(filter) for now, because migrating them fails without an error: #(filter, required) (it partitions the value index), implicit filters (row security, which moves to #(access_filter)), and a date or number range that must default to no filter. See Fine-grained access control.
Dashboards saved from an analysis chat
A document saved to your workspace is restricted: it may not declare a given:, import a file, or set ##! flags. Its filters come from givens the model already declares. When the model declares none, the dashboard uses a fixed filter, and a modeler can add a given to the model to turn it into a control.