@@ -29,7 +29,7 @@ Each widget has common fields:
2929| ` label ` | Optional widget title. |
3030| ` target ` | Widget type: ` table ` , ` chart ` , ` kpi_card ` , ` pivot_table ` , or ` gauge_card ` . |
3131| ` order ` | Widget order inside its group. |
32- | ` variables ` | Optional static maps/constants available inside widget ` query . calcs ` via ` lookup ( $variables . path , field , default ) ` . |
32+ | ` variables ` | Optional widget variables passed to widget data loading. Variables are not available inside ` query . calcs ` . |
3333| ` size ` | Preset width: ` small ` , ` medium ` , ` large ` , ` wide ` , or ` full ` . |
3434| ` width ` , ` height ` , ` min_width ` , ` max_width ` | Optional explicit layout constraints. |
3535| ` query ` | Data query definition. |
@@ -39,7 +39,7 @@ Each widget has common fields:
3939| Widget target | Config field | Main settings | Data usage |
4040| --- | --- | --- | --- |
4141| ` table ` | ` table ` | ` pagination ` , ` page_size ` , ` columns ` | Uses ` query ` to display raw or aggregate rows. |
42- | ` chart ` | ` chart ` | ` type ` , ` x ` , ` y ` , ` label ` , ` value ` , ` series ` , ` buckets ` , ` color ` , ` colors ` | Uses ` query ` ; step-based charts may use ` query .steps ` with optional ` calcs ` . |
42+ | ` chart ` | ` chart ` | ` type ` , ` x ` , ` y ` , ` label ` , ` value ` , ` series ` , ` buckets ` , ` color ` , ` colors ` | Uses the same ` query ` shape for every chart type. Multi-resource charts use ` query .source : steps ` . |
4343| ` kpi_card ` | ` card ` | ` value ` , ` subtitle ` , ` comparison ` , ` sparkline ` | Reads the first returned query row. |
4444| ` gauge_card ` | ` card ` | ` value ` , ` target ` , ` progress ` , ` color ` | Reads the first returned query row. |
4545| ` pivot_table ` | ` pivot ` | ` rows ` , ` columns ` , ` values ` | Uses query rows to build a pivot table. |
@@ -52,13 +52,14 @@ Chart widget types:
5252| ` pie ` | Uses ` label ` and ` value ` . |
5353| ` bar ` | Uses ` x ` and ` y ` . |
5454| ` stacked_bar ` | Uses ` x ` , ` y ` , and ` series ` . |
55- | ` funnel ` | Uses ` query . steps ` and optional ` label ` , ` value ` , ` colors ` . |
55+ | ` funnel ` | Uses ` label ` , ` value ` , and optional ` colors ` . Data comes from the same ` query ` shapes as every other chart . |
5656| ` histogram ` | Uses ` x ` , ` y ` , and optional ` buckets ` . |
5757
5858## Query Shape
5959
6060` ` ` ts
6161type QueryConfig = {
62+ source? : ' resource'
6263 resource: string
6364 select? : Array <
6465 | { field: string ; as? : string ; grain? : ' day' | ' week' | ' month' | ' year' }
@@ -70,98 +71,120 @@ type QueryConfig = {
7071 order_by? : Array <{ field: string ; direction? : ' asc' | ' desc' }>
7172 limit? : number
7273 offset? : number
74+ bucket? : { field: string ; buckets: Array <{ label: string ; min? : number ; max? : number }> }
75+ calcs? : Array <{ calc: string ; as: string }>
76+ formatting? : Record <string , JsonValue >
77+ } | {
78+ source: ' steps'
79+ steps: Array <{
80+ name: string
81+ resource: string
82+ select: Array <{ agg: ' sum' | ' count' | ' count_distinct' | ' avg' | ' min' | ' max' | ' median' ; field? : string ; as: string ; filters? : DashboardFilter | DashboardFilter [] }>
83+ filters? : DashboardFilter | DashboardFilter []
84+ }>
85+ calcs? : Array <{ calc: string ; as: string }>
86+ order_by? : Array <{ field: string ; direction? : ' asc' | ' desc' }>
87+ limit? : number
88+ offset? : number
89+ formatting? : Record <string , JsonValue >
7390}
7491
7592type DashboardFilter =
7693 | { and: DashboardFilter [] }
7794 | { or: DashboardFilter [] }
7895 | {
7996 field: string
80- eq? : JsonValue
81- neq? : JsonValue
82- gt? : JsonValue
83- gte? : JsonValue
84- lt? : JsonValue
85- lte? : JsonValue
86- in? : JsonValue []
87- not_in? : JsonValue []
88- like? : JsonValue
89- ilike? : JsonValue
97+ eq? : FilterValue
98+ neq? : FilterValue
99+ gt? : FilterValue
100+ gte? : FilterValue
101+ lt? : FilterValue
102+ lte? : FilterValue
103+ in? : FilterValue []
104+ not_in? : FilterValue []
105+ like? : FilterValue
106+ ilike? : FilterValue
90107 }
91108
92109type JsonValue = string | number | boolean | null | JsonValue [] | { [key : string ]: JsonValue }
110+ type RelativeDateValue = { now: true } | { now_minus: ` ${number }${' h' | ' d' | ' w' | ' mo' | ' y' } ` }
111+ type FilterValue = JsonValue | RelativeDateValue
112+ ` ` `
113+
114+ Use ` filters ` for rolling date ranges. Do not hard-code dates for dashboards that should move with time:
115+
116+ ` ` ` yaml
117+ query :
118+ resource : orders
119+ filters :
120+ and :
121+ - field : created_at
122+ gte :
123+ now_minus : 30d
124+ - field : created_at
125+ lt :
126+ now : true
93127` ` `
94128
95- Step-based chart queries use ` steps ` and may include ` calcs ` :
129+ Multi-resource queries use ` source : steps ` . Each step uses ` select ` , even if it has only one aggregate :
96130
97131` ` ` yaml
98132target : chart
99133label : Average price by database
100- variables :
101- price_multipliers :
102- cars_sl : 0.84
103- cars_mysql : 1.12
104- cars_pg : 0.91
105- cars_mongo : 1.07
106- cars_ch : 0.76
107134chart :
108135 type : bar
109136 title : Average price by database
110137 x :
111138 field : name
112139 y :
113- field : adjusted_value
140+ field : value
114141query :
142+ source : steps
115143 steps :
116144 - name : SQLite
117145 resource : cars_sl
118- metric :
119- agg : avg
120- field : price
121- as : value
146+ select :
147+ - agg : avg
148+ field : price
149+ as : value
122150 - name : MySQL
123151 resource : cars_mysql
124- metric :
125- agg : avg
126- field : price
127- as : value
128- calcs :
129- - calc : value * lookup ($variables .price_multipliers , resource , 1 )
130- as : adjusted_value
152+ select :
153+ - agg : avg
154+ field : price
155+ as : value
131156```
132157
133- Widget-level variables example:
158+ Cost calculation example:
134159
135160``` yaml
136161target : chart
137162label : Model costs
138- variables :
139- token_prices_per_1m :
140- input :
141- gpt-4.1 : 2.00
142- gpt-4.1-mini : 0.40
143- gpt-4o-mini : 0.15
144- output :
145- gpt-4.1 : 8.00
146- gpt-4.1-mini : 1.60
147- gpt-4o-mini : 0.60
148- cached :
149- gpt-4.1 : 0.50
150- gpt-4.1-mini : 0.10
151- gpt-4o-mini : 0.075
152163chart :
153164 type : stacked_bar
154- title : LLM costs by model
165+ title : GPT-5.4 costs by day
155166 x :
156- field : model
167+ field : day
157168 y :
158169 - field : input_cost
159170 - field : output_cost
160171 - field : cached_cost
161172query :
162173 resource : model_usage
174+ filters :
175+ and :
176+ - field : model
177+ eq : gpt-5.4
178+ - field : used_at
179+ gte :
180+ now_minus : 7d
181+ - field : used_at
182+ lt :
183+ now : true
163184 select :
164- - field : model
185+ - field : used_at
186+ as : day
187+ grain : day
165188 - agg : sum
166189 field : input_tokens
167190 as : input_tokens
@@ -172,16 +195,19 @@ query:
172195 field : cached_tokens
173196 as : cached_tokens
174197 group_by :
175- - model
198+ - field : used_at
199+ as : day
200+ grain : day
176201 calcs :
177- - calc : input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
202+ - calc : input_tokens / 1000000 * 2.5
178203 as : input_cost
179- - calc : output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
204+ - calc : output_tokens / 1000000 * 15
180205 as : output_cost
181- - calc : cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
206+ - calc : cached_tokens / 1000000 * 0.25
182207 as : cached_cost
183208` ` `
184209
210+
185211## Runtime Structure
186212
187213` ` ` text
0 commit comments