@adminforth/dashboard 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +81 -55
  2. package/custom/model/dashboard.types.ts +17 -9
  3. package/custom/skills/adminforth-dashboard/SKILL.md +28 -12
  4. package/dist/custom/model/dashboard.types.d.ts +15 -8
  5. package/dist/custom/model/dashboard.types.ts +17 -9
  6. package/dist/custom/queries/useDashboardConfig.d.ts +222 -4
  7. package/dist/custom/queries/useWidgetData.d.ts +222 -4
  8. package/dist/custom/skills/adminforth-dashboard/SKILL.md +28 -12
  9. package/dist/schema/api.d.ts +5440 -941
  10. package/dist/schema/api.js +2 -2
  11. package/dist/schema/widget.d.ts +432 -23
  12. package/dist/schema/widget.js +1 -1
  13. package/dist/schema/widgets/charts.d.ts +558 -28
  14. package/dist/schema/widgets/charts.js +2 -2
  15. package/dist/schema/widgets/common.d.ts +17 -6
  16. package/dist/schema/widgets/common.js +16 -7
  17. package/dist/schema/widgets/gauge-card.d.ts +38 -2
  18. package/dist/schema/widgets/kpi-card.d.ts +38 -2
  19. package/dist/schema/widgets/pivot-table.d.ts +38 -2
  20. package/dist/schema/widgets/table.d.ts +38 -2
  21. package/dist/services/calc-evaluator.d.ts +1 -0
  22. package/dist/services/calc-evaluator.js +28 -0
  23. package/dist/services/dashboardFilterService.d.ts +5 -0
  24. package/dist/services/dashboardFilterService.js +125 -0
  25. package/dist/services/widgetDataService.js +53 -201
  26. package/package.json +2 -1
  27. package/schema/api.ts +1 -2
  28. package/schema/widget.ts +0 -1
  29. package/schema/widgets/charts.ts +1 -2
  30. package/schema/widgets/common.ts +16 -7
  31. package/services/calc-evaluator.ts +33 -0
  32. package/services/dashboardFilterService.ts +162 -0
  33. package/services/widgetDataService.ts +88 -263
package/README.md CHANGED
@@ -29,7 +29,7 @@ Each widget has common fields:
29
29
  | `label` | Optional widget title. |
30
30
  | `target` | Widget type: `table`, `chart`, `kpi_card`, `pivot_table`, or `gauge_card`. |
31
31
  | `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`. |
33
33
  | `size` | Preset width: `small`, `medium`, `large`, `wide`, or `full`. |
34
34
  | `width`, `height`, `min_width`, `max_width` | Optional explicit layout constraints. |
35
35
  | `query` | Data query definition. |
@@ -39,7 +39,7 @@ Each widget has common fields:
39
39
  | Widget target | Config field | Main settings | Data usage |
40
40
  | --- | --- | --- | --- |
41
41
  | `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`. |
43
43
  | `kpi_card` | `card` | `value`, `subtitle`, `comparison`, `sparkline` | Reads the first returned query row. |
44
44
  | `gauge_card` | `card` | `value`, `target`, `progress`, `color` | Reads the first returned query row. |
45
45
  | `pivot_table` | `pivot` | `rows`, `columns`, `values` | Uses query rows to build a pivot table. |
@@ -52,13 +52,14 @@ Chart widget types:
52
52
  | `pie` | Uses `label` and `value`. |
53
53
  | `bar` | Uses `x` and `y`. |
54
54
  | `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. |
56
56
  | `histogram` | Uses `x`, `y`, and optional `buckets`. |
57
57
 
58
58
  ## Query Shape
59
59
 
60
60
  ```ts
61
61
  type QueryConfig = {
62
+ source?: 'resource'
62
63
  resource: string
63
64
  select?: Array<
64
65
  | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year' }
@@ -70,6 +71,22 @@ type QueryConfig = {
70
71
  order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
71
72
  limit?: number
72
73
  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>
73
90
  }
74
91
 
75
92
  type DashboardFilter =
@@ -77,91 +94,97 @@ type DashboardFilter =
77
94
  | { or: DashboardFilter[] }
78
95
  | {
79
96
  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
90
107
  }
91
108
 
92
109
  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
93
127
  ```
94
128
 
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:
96
130
 
97
131
  ```yaml
98
132
  target: chart
99
133
  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
107
134
  chart:
108
135
  type: bar
109
136
  title: Average price by database
110
137
  x:
111
138
  field: name
112
139
  y:
113
- field: adjusted_value
140
+ field: value
114
141
  query:
142
+ source: steps
115
143
  steps:
116
144
  - name: SQLite
117
145
  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
122
150
  - name: MySQL
123
151
  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
131
156
  ```
132
157
 
133
- Widget-level variables example:
158
+ Cost calculation example:
134
159
 
135
160
  ```yaml
136
161
  target: chart
137
162
  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
152
163
  chart:
153
164
  type: stacked_bar
154
- title: LLM costs by model
165
+ title: GPT-5.4 costs by day
155
166
  x:
156
- field: model
167
+ field: day
157
168
  y:
158
169
  - field: input_cost
159
170
  - field: output_cost
160
171
  - field: cached_cost
161
172
  query:
162
173
  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
163
184
  select:
164
- - field: model
185
+ - field: used_at
186
+ as: day
187
+ grain: day
165
188
  - agg: sum
166
189
  field: input_tokens
167
190
  as: input_tokens
@@ -172,16 +195,19 @@ query:
172
195
  field: cached_tokens
173
196
  as: cached_tokens
174
197
  group_by:
175
- - model
198
+ - field: used_at
199
+ as: day
200
+ grain: day
176
201
  calcs:
177
- - calc: input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
202
+ - calc: input_tokens / 1000000 * 2.5
178
203
  as: input_cost
179
- - calc: output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
204
+ - calc: output_tokens / 1000000 * 15
180
205
  as: output_cost
181
- - calc: cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
206
+ - calc: cached_tokens / 1000000 * 0.25
182
207
  as: cached_cost
183
208
  ```
184
209
 
210
+
185
211
  ## Runtime Structure
186
212
 
187
213
  ```text
@@ -117,7 +117,8 @@ export type QueryOrderByItem = {
117
117
  direction?: 'asc' | 'desc'
118
118
  }
119
119
 
120
- export type QueryConfig = {
120
+ export type ResourceQueryConfig = {
121
+ source?: 'resource'
121
122
  resource: string
122
123
  select?: QuerySelectItem[]
123
124
  sparkline?: {
@@ -139,18 +140,25 @@ export type QueryConfig = {
139
140
  formatting?: Record<string, JsonValue>
140
141
  }
141
142
 
142
- export type FunnelQueryConfig = {
143
- steps: FunnelQueryStep[]
144
- calcs?: QueryCalcSelectItem[]
145
- }
146
-
147
- export type FunnelQueryStep = {
143
+ export type StepsQueryStepConfig = {
148
144
  name: string
149
145
  resource: string
150
- metric: QueryAggregateSelectItem
146
+ select: QueryAggregateSelectItem[]
151
147
  filters?: FilterExpression
152
148
  }
153
149
 
150
+ export type StepsQueryConfig = {
151
+ source: 'steps'
152
+ steps: StepsQueryStepConfig[]
153
+ calcs?: QueryCalcSelectItem[]
154
+ order_by?: QueryOrderByItem[]
155
+ limit?: number
156
+ offset?: number
157
+ formatting?: Record<string, JsonValue>
158
+ }
159
+
160
+ export type QueryConfig = ResourceQueryConfig | StepsQueryConfig
161
+
154
162
  export type FieldRef = string | {
155
163
  field: string
156
164
  label?: string
@@ -246,7 +254,7 @@ export type TableWidgetConfig = WidgetBaseConfig & {
246
254
  export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
247
255
  target: 'chart'
248
256
  chart: ChartWidgetConfig
249
- query: QueryConfig | FunnelQueryConfig
257
+ query: QueryConfig
250
258
  }
251
259
 
252
260
  export type KpiCardWidgetConfig = WidgetBaseConfig & {
@@ -151,8 +151,8 @@ Use resource, not resourceId.
151
151
 
152
152
  ## Query shape rules
153
153
 
154
- Use dashboard_configure_funnel_chart_widget for funnel charts and set query.steps.
155
- Do not use query.steps for kpi_card, gauge_card, table, pivot_table, line, bar, stacked bar, pie, or histogram charts.
154
+ All chart widgets, including funnel charts, use the same query shape.
155
+ Use a single-resource query by default.
156
156
 
157
157
  For kpi_card and normal charts, use:
158
158
  - query.resource
@@ -162,6 +162,26 @@ For kpi_card and normal charts, use:
162
162
  - optional query.order_by
163
163
  - optional query.calcs
164
164
 
165
+ For multi-resource charts or widgets, use the general steps source:
166
+
167
+ query:
168
+ source: steps
169
+ steps:
170
+ - name: Leads
171
+ resource: leads
172
+ select:
173
+ - agg: count
174
+ as: value
175
+ - name: Customers
176
+ resource: orders
177
+ select:
178
+ - agg: count_distinct
179
+ field: customer_id
180
+ as: value
181
+
182
+ Do not use bare query.steps without source: steps.
183
+ Do not use metric. Use select even when a step has only one aggregate.
184
+
165
185
  ## Date range rules
166
186
 
167
187
  Use only query.filters for time ranges.
@@ -201,22 +221,18 @@ select raw token totals:
201
221
  - sum output_tokens as output_tokens
202
222
 
203
223
  then query.calcs:
204
- - calculate total_spend from those aliases and lookup variables
224
+ - calculate total_spend from those aliases with explicit constants
205
225
 
206
- For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs. Do not use query.steps.
226
+ For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs.
207
227
 
208
- ## Calc variables
228
+ ## Calc rules
209
229
 
210
- Use variables for static maps/rates.
211
- 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.
212
232
 
213
233
  Minimal example:
214
234
 
215
- variables:
216
- prices:
217
- gpt-5.4: 2.5
218
-
219
235
  query:
220
236
  calcs:
221
- - calc: tokens / 1000000 * lookup($variables.prices, model, 0)
237
+ - calc: tokens / 1000000 * 2.5
222
238
  as: cost
@@ -87,7 +87,8 @@ export type QueryOrderByItem = {
87
87
  field: string;
88
88
  direction?: 'asc' | 'desc';
89
89
  };
90
- export type QueryConfig = {
90
+ export type ResourceQueryConfig = {
91
+ source?: 'resource';
91
92
  resource: string;
92
93
  select?: QuerySelectItem[];
93
94
  sparkline?: {
@@ -112,16 +113,22 @@ export type QueryConfig = {
112
113
  calcs?: QueryCalcSelectItem[];
113
114
  formatting?: Record<string, JsonValue>;
114
115
  };
115
- export type FunnelQueryConfig = {
116
- steps: FunnelQueryStep[];
117
- calcs?: QueryCalcSelectItem[];
118
- };
119
- export type FunnelQueryStep = {
116
+ export type StepsQueryStepConfig = {
120
117
  name: string;
121
118
  resource: string;
122
- metric: QueryAggregateSelectItem;
119
+ select: QueryAggregateSelectItem[];
123
120
  filters?: FilterExpression;
124
121
  };
122
+ export type StepsQueryConfig = {
123
+ source: 'steps';
124
+ steps: StepsQueryStepConfig[];
125
+ calcs?: QueryCalcSelectItem[];
126
+ order_by?: QueryOrderByItem[];
127
+ limit?: number;
128
+ offset?: number;
129
+ formatting?: Record<string, JsonValue>;
130
+ };
131
+ export type QueryConfig = ResourceQueryConfig | StepsQueryConfig;
125
132
  export type FieldRef = string | {
126
133
  field: string;
127
134
  label?: string;
@@ -210,7 +217,7 @@ export type TableWidgetConfig = WidgetBaseConfig & {
210
217
  export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
211
218
  target: 'chart';
212
219
  chart: ChartWidgetConfig;
213
- query: QueryConfig | FunnelQueryConfig;
220
+ query: QueryConfig;
214
221
  };
215
222
  export type KpiCardWidgetConfig = WidgetBaseConfig & {
216
223
  target: 'kpi_card';
@@ -117,7 +117,8 @@ export type QueryOrderByItem = {
117
117
  direction?: 'asc' | 'desc'
118
118
  }
119
119
 
120
- export type QueryConfig = {
120
+ export type ResourceQueryConfig = {
121
+ source?: 'resource'
121
122
  resource: string
122
123
  select?: QuerySelectItem[]
123
124
  sparkline?: {
@@ -139,18 +140,25 @@ export type QueryConfig = {
139
140
  formatting?: Record<string, JsonValue>
140
141
  }
141
142
 
142
- export type FunnelQueryConfig = {
143
- steps: FunnelQueryStep[]
144
- calcs?: QueryCalcSelectItem[]
145
- }
146
-
147
- export type FunnelQueryStep = {
143
+ export type StepsQueryStepConfig = {
148
144
  name: string
149
145
  resource: string
150
- metric: QueryAggregateSelectItem
146
+ select: QueryAggregateSelectItem[]
151
147
  filters?: FilterExpression
152
148
  }
153
149
 
150
+ export type StepsQueryConfig = {
151
+ source: 'steps'
152
+ steps: StepsQueryStepConfig[]
153
+ calcs?: QueryCalcSelectItem[]
154
+ order_by?: QueryOrderByItem[]
155
+ limit?: number
156
+ offset?: number
157
+ formatting?: Record<string, JsonValue>
158
+ }
159
+
160
+ export type QueryConfig = ResourceQueryConfig | StepsQueryConfig
161
+
154
162
  export type FieldRef = string | {
155
163
  field: string
156
164
  label?: string
@@ -246,7 +254,7 @@ export type TableWidgetConfig = WidgetBaseConfig & {
246
254
  export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
247
255
  target: 'chart'
248
256
  chart: ChartWidgetConfig
249
- query: QueryConfig | FunnelQueryConfig
257
+ query: QueryConfig
250
258
  }
251
259
 
252
260
  export type KpiCardWidgetConfig = WidgetBaseConfig & {