Skip to content

Commit d92a9d3

Browse files
committed
feat: update widget data handling and refactor query configurations for improved clarity and functionality
1 parent b2eeba2 commit d92a9d3

9 files changed

Lines changed: 334 additions & 314 deletions

File tree

README.md

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -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
6161
type 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

7592
type 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

92109
type 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
98132
target: chart
99133
label: 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
107134
chart:
108135
type: bar
109136
title: Average price by database
110137
x:
111138
field: name
112139
y:
113-
field: adjusted_value
140+
field: value
114141
query:
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
136161
target: chart
137162
label: 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
152163
chart:
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
161172
query:
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

custom/model/dashboard.types.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -140,19 +140,12 @@ export type ResourceQueryConfig = {
140140
formatting?: Record<string, JsonValue>
141141
}
142142

143-
export type StepsQueryStepConfig =
144-
| {
145-
name: string
146-
resource: string
147-
metric: QueryAggregateSelectItem
148-
filters?: FilterExpression
149-
}
150-
| {
151-
name: string
152-
resource: string
153-
select: QueryAggregateSelectItem[]
154-
filters?: FilterExpression
155-
}
143+
export type StepsQueryStepConfig = {
144+
name: string
145+
resource: string
146+
select: QueryAggregateSelectItem[]
147+
filters?: FilterExpression
148+
}
156149

157150
export type StepsQueryConfig = {
158151
source: 'steps'

custom/skills/adminforth-dashboard/SKILL.md

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -169,21 +169,18 @@ query:
169169
steps:
170170
- name: Leads
171171
resource: leads
172-
metric:
173-
agg: count
174-
as: value
172+
select:
173+
- agg: count
174+
as: value
175175
- name: Customers
176176
resource: orders
177-
metric:
178-
agg: count_distinct
179-
field: customer_id
180-
as: value
181-
182-
Each step may use either:
183-
- metric for one aggregate
184-
- select for multiple aggregate fields
177+
select:
178+
- agg: count_distinct
179+
field: customer_id
180+
as: value
185181

186182
Do not use bare query.steps without source: steps.
183+
Do not use metric. Use select even when a step has only one aggregate.
187184

188185
## Date range rules
189186

@@ -224,22 +221,18 @@ select raw token totals:
224221
- sum output_tokens as output_tokens
225222

226223
then query.calcs:
227-
- calculate total_spend from those aliases and lookup variables
224+
- calculate total_spend from those aliases with explicit constants
228225

229226
For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs.
230227

231-
## Calc variables
228+
## Calc rules
232229

233-
Use variables for static maps/rates.
234-
Use lookup($variables.some.map, row_field, default_number) in query.calcs.
230+
Calcs can reference only fields already present in the current row.
231+
Use explicit constants for rates.
235232

236233
Minimal example:
237234

238-
variables:
239-
prices:
240-
gpt-5.4: 2.5
241-
242235
query:
243236
calcs:
244-
- calc: tokens / 1000000 * lookup($variables.prices, model, 0)
237+
- calc: tokens / 1000000 * 2.5
245238
as: cost

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
}
5757
],
5858
"dependencies": {
59+
"expr-eval-fork": "^3.0.3",
5960
"vue": "^3.5.34",
6061
"vue-router": "^5.0.7",
6162
"yaml": "^2.9.0",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schema/widgets/common.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,6 @@ const ResourceQueryConfigSchema = z.object({
160160
formatting: z.record(z.string(), z.unknown()).optional(),
161161
}).strict()
162162

163-
const StepsQueryMetricStepSchema = z.object({
164-
name: z.string(),
165-
resource: z.string(),
166-
metric: QueryAggregateSelectItemSchema,
167-
filters: FilterExpressionSchema.optional(),
168-
}).strict()
169-
170163
const StepsQuerySelectStepSchema = z.object({
171164
name: z.string(),
172165
resource: z.string(),
@@ -178,10 +171,7 @@ export const QueryConfigSchema = z.union([
178171
ResourceQueryConfigSchema,
179172
z.object({
180173
source: z.literal('steps'),
181-
steps: z.array(z.union([
182-
StepsQueryMetricStepSchema,
183-
StepsQuerySelectStepSchema,
184-
])).min(1),
174+
steps: z.array(StepsQuerySelectStepSchema).min(1),
185175
calcs: z.array(QueryCalcItemSchema).optional(),
186176
order_by: z.array(QueryOrderByItemSchema).optional(),
187177
limit: z.number().int().positive().optional(),

0 commit comments

Comments
 (0)