@adminforth/dashboard 1.8.0 → 1.10.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.
- package/README.md +81 -55
- package/custom/api/dashboardApi.ts +73 -36
- package/custom/model/dashboard.types.ts +6 -13
- package/custom/runtime/DashboardRuntime.vue +26 -22
- package/custom/skills/adminforth-dashboard/SKILL.md +13 -20
- package/dist/custom/api/dashboardApi.d.ts +24 -18
- package/dist/custom/api/dashboardApi.js +42 -18
- package/dist/custom/api/dashboardApi.ts +73 -36
- package/dist/custom/model/dashboard.types.d.ts +0 -5
- package/dist/custom/model/dashboard.types.ts +6 -13
- package/dist/custom/queries/useDashboardConfig.d.ts +20 -120
- package/dist/custom/queries/useWidgetData.d.ts +20 -120
- package/dist/custom/runtime/DashboardRuntime.vue +26 -22
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +13 -20
- package/dist/endpoint/groups.js +22 -20
- package/dist/endpoint/widgets.js +28 -26
- package/dist/schema/api.d.ts +230 -3936
- package/dist/schema/api.js +7 -12
- package/dist/schema/widget.d.ts +20 -200
- package/dist/schema/widgets/charts.d.ts +24 -240
- package/dist/schema/widgets/common.d.ts +2 -20
- package/dist/schema/widgets/common.js +1 -10
- package/dist/schema/widgets/gauge-card.d.ts +2 -20
- package/dist/schema/widgets/kpi-card.d.ts +2 -20
- package/dist/schema/widgets/pivot-table.d.ts +2 -20
- package/dist/schema/widgets/table.d.ts +2 -20
- package/dist/services/calc-evaluator.d.ts +2 -0
- package/dist/services/calc-evaluator.js +54 -0
- package/dist/services/dashboardFilterService.d.ts +5 -0
- package/dist/services/dashboardFilterService.js +125 -0
- package/dist/services/widgetDataService.js +15 -168
- package/endpoint/groups.ts +22 -20
- package/endpoint/widgets.ts +28 -26
- package/package.json +2 -1
- package/schema/api.ts +7 -12
- package/schema/widgets/common.ts +1 -11
- package/services/calc-evaluator.ts +71 -0
- package/services/dashboardFilterService.ts +162 -0
- package/services/widgetDataService.ts +26 -213
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
|
|
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
|
|
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 `
|
|
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?:
|
|
81
|
-
neq?:
|
|
82
|
-
gt?:
|
|
83
|
-
gte?:
|
|
84
|
-
lt?:
|
|
85
|
-
lte?:
|
|
86
|
-
in?:
|
|
87
|
-
not_in?:
|
|
88
|
-
like?:
|
|
89
|
-
ilike?:
|
|
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
|
-
|
|
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:
|
|
140
|
+
field: value
|
|
114
141
|
query:
|
|
142
|
+
source: steps
|
|
115
143
|
steps:
|
|
116
144
|
- name: SQLite
|
|
117
145
|
resource: cars_sl
|
|
118
|
-
|
|
119
|
-
agg: avg
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
select:
|
|
147
|
+
- agg: avg
|
|
148
|
+
field: price
|
|
149
|
+
as: value
|
|
122
150
|
- name: MySQL
|
|
123
151
|
resource: cars_mysql
|
|
124
|
-
|
|
125
|
-
agg: avg
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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:
|
|
165
|
+
title: GPT-5.4 costs by day
|
|
155
166
|
x:
|
|
156
|
-
field:
|
|
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:
|
|
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
|
-
-
|
|
198
|
+
- field: used_at
|
|
199
|
+
as: day
|
|
200
|
+
grain: day
|
|
176
201
|
calcs:
|
|
177
|
-
- calc: input_tokens / 1000000 *
|
|
202
|
+
- calc: input_tokens / 1000000 * 2.5
|
|
178
203
|
as: input_cost
|
|
179
|
-
- calc: output_tokens / 1000000 *
|
|
204
|
+
- calc: output_tokens / 1000000 * 15
|
|
180
205
|
as: output_cost
|
|
181
|
-
- calc: cached_tokens / 1000000 *
|
|
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
|
|
@@ -25,6 +25,13 @@ export type DashboardWidgetDataResponse = {
|
|
|
25
25
|
data: unknown
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export type DashboardMutationResponse = {
|
|
29
|
+
ok: boolean
|
|
30
|
+
error?: string
|
|
31
|
+
groupId?: string
|
|
32
|
+
widgetId?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
export type DashboardWidgetDataRequest = {
|
|
29
36
|
pagination?: {
|
|
30
37
|
page: number
|
|
@@ -119,6 +126,36 @@ async function callDashboardApi(path: string, body: Record<string, unknown>): Pr
|
|
|
119
126
|
}
|
|
120
127
|
}
|
|
121
128
|
|
|
129
|
+
async function callDashboardMutationApi(path: string, body: Record<string, unknown>): Promise<DashboardMutationResponse> {
|
|
130
|
+
const rawResponse = await fetch(path, {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: {
|
|
133
|
+
'Content-Type': 'application/json',
|
|
134
|
+
'accept-language': localStorage.getItem('af_lang') || 'en',
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify(body),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const response = await parseDashboardResponse(rawResponse)
|
|
140
|
+
|
|
141
|
+
if (!rawResponse.ok) {
|
|
142
|
+
throw new DashboardApiError(
|
|
143
|
+
response?.error || rawResponse.statusText || `Dashboard request failed (${rawResponse.status})`,
|
|
144
|
+
normalizeValidationErrors(response),
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!response || response.error || response.ok === false) {
|
|
149
|
+
throw new DashboardApiError(response?.error || 'Dashboard request failed', normalizeValidationErrors(response))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
groupId: response.groupId,
|
|
155
|
+
widgetId: response.widgetId,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
122
159
|
async function callDashboardWidgetDataApi(
|
|
123
160
|
path: string,
|
|
124
161
|
body: Record<string, unknown>,
|
|
@@ -156,39 +193,39 @@ export const dashboardApi = {
|
|
|
156
193
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
|
|
157
194
|
},
|
|
158
195
|
|
|
159
|
-
async addDashboardGroup(slug: string): Promise<
|
|
160
|
-
return
|
|
196
|
+
async addDashboardGroup(slug: string): Promise<DashboardMutationResponse> {
|
|
197
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
|
|
161
198
|
},
|
|
162
199
|
|
|
163
200
|
async moveDashboardGroup(
|
|
164
201
|
slug: string,
|
|
165
202
|
groupId: string,
|
|
166
203
|
direction: DashboardGroupMoveDirection,
|
|
167
|
-
): Promise<
|
|
168
|
-
return
|
|
204
|
+
): Promise<DashboardMutationResponse> {
|
|
205
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/move_dashboard_group', {
|
|
169
206
|
slug,
|
|
170
207
|
groupId,
|
|
171
208
|
direction,
|
|
172
209
|
})
|
|
173
210
|
},
|
|
174
211
|
|
|
175
|
-
async removeDashboardGroup(slug: string, groupId: string): Promise<
|
|
176
|
-
return
|
|
212
|
+
async removeDashboardGroup(slug: string, groupId: string): Promise<DashboardMutationResponse> {
|
|
213
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/remove_dashboard_group', {
|
|
177
214
|
slug,
|
|
178
215
|
groupId,
|
|
179
216
|
})
|
|
180
217
|
},
|
|
181
218
|
|
|
182
|
-
async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<
|
|
183
|
-
return
|
|
219
|
+
async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardMutationResponse> {
|
|
220
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
|
|
184
221
|
slug,
|
|
185
222
|
groupId,
|
|
186
223
|
config,
|
|
187
224
|
})
|
|
188
225
|
},
|
|
189
226
|
|
|
190
|
-
async addDashboardWidget(slug: string, groupId: string): Promise<
|
|
191
|
-
return
|
|
227
|
+
async addDashboardWidget(slug: string, groupId: string): Promise<DashboardMutationResponse> {
|
|
228
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/add_dashboard_widget', {
|
|
192
229
|
slug,
|
|
193
230
|
groupId,
|
|
194
231
|
})
|
|
@@ -198,23 +235,23 @@ export const dashboardApi = {
|
|
|
198
235
|
slug: string,
|
|
199
236
|
widgetId: string,
|
|
200
237
|
direction: DashboardWidgetMoveDirection,
|
|
201
|
-
): Promise<
|
|
202
|
-
return
|
|
238
|
+
): Promise<DashboardMutationResponse> {
|
|
239
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/move_dashboard_widget', {
|
|
203
240
|
slug,
|
|
204
241
|
widgetId,
|
|
205
242
|
direction,
|
|
206
243
|
})
|
|
207
244
|
},
|
|
208
245
|
|
|
209
|
-
async removeDashboardWidget(slug: string, widgetId: string): Promise<
|
|
210
|
-
return
|
|
246
|
+
async removeDashboardWidget(slug: string, widgetId: string): Promise<DashboardMutationResponse> {
|
|
247
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/remove_dashboard_widget', {
|
|
211
248
|
slug,
|
|
212
249
|
widgetId,
|
|
213
250
|
})
|
|
214
251
|
},
|
|
215
252
|
|
|
216
|
-
async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<
|
|
217
|
-
return
|
|
253
|
+
async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardMutationResponse> {
|
|
254
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/set_widget_config', {
|
|
218
255
|
slug,
|
|
219
256
|
widgetId,
|
|
220
257
|
config,
|
|
@@ -225,8 +262,8 @@ export const dashboardApi = {
|
|
|
225
262
|
slug: string,
|
|
226
263
|
widgetId: string,
|
|
227
264
|
config: ConfigurableTableWidgetConfig,
|
|
228
|
-
): Promise<
|
|
229
|
-
return
|
|
265
|
+
): Promise<DashboardMutationResponse> {
|
|
266
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_table_widget', {
|
|
230
267
|
slug,
|
|
231
268
|
widgetId,
|
|
232
269
|
config,
|
|
@@ -237,8 +274,8 @@ export const dashboardApi = {
|
|
|
237
274
|
slug: string,
|
|
238
275
|
widgetId: string,
|
|
239
276
|
config: ConfigurableKpiCardWidgetConfig,
|
|
240
|
-
): Promise<
|
|
241
|
-
return
|
|
277
|
+
): Promise<DashboardMutationResponse> {
|
|
278
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_kpi_card_widget', {
|
|
242
279
|
slug,
|
|
243
280
|
widgetId,
|
|
244
281
|
config,
|
|
@@ -249,8 +286,8 @@ export const dashboardApi = {
|
|
|
249
286
|
slug: string,
|
|
250
287
|
widgetId: string,
|
|
251
288
|
config: ConfigurableGaugeCardWidgetConfig,
|
|
252
|
-
): Promise<
|
|
253
|
-
return
|
|
289
|
+
): Promise<DashboardMutationResponse> {
|
|
290
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_gauge_card_widget', {
|
|
254
291
|
slug,
|
|
255
292
|
widgetId,
|
|
256
293
|
config,
|
|
@@ -261,8 +298,8 @@ export const dashboardApi = {
|
|
|
261
298
|
slug: string,
|
|
262
299
|
widgetId: string,
|
|
263
300
|
config: ConfigurableLineChartWidgetConfig,
|
|
264
|
-
): Promise<
|
|
265
|
-
return
|
|
301
|
+
): Promise<DashboardMutationResponse> {
|
|
302
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_line_chart_widget', {
|
|
266
303
|
slug,
|
|
267
304
|
widgetId,
|
|
268
305
|
config,
|
|
@@ -273,8 +310,8 @@ export const dashboardApi = {
|
|
|
273
310
|
slug: string,
|
|
274
311
|
widgetId: string,
|
|
275
312
|
config: ConfigurableBarChartWidgetConfig,
|
|
276
|
-
): Promise<
|
|
277
|
-
return
|
|
313
|
+
): Promise<DashboardMutationResponse> {
|
|
314
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_bar_chart_widget', {
|
|
278
315
|
slug,
|
|
279
316
|
widgetId,
|
|
280
317
|
config,
|
|
@@ -285,8 +322,8 @@ export const dashboardApi = {
|
|
|
285
322
|
slug: string,
|
|
286
323
|
widgetId: string,
|
|
287
324
|
config: ConfigurableStackedBarChartWidgetConfig,
|
|
288
|
-
): Promise<
|
|
289
|
-
return
|
|
325
|
+
): Promise<DashboardMutationResponse> {
|
|
326
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_stacked_bar_chart_widget', {
|
|
290
327
|
slug,
|
|
291
328
|
widgetId,
|
|
292
329
|
config,
|
|
@@ -297,8 +334,8 @@ export const dashboardApi = {
|
|
|
297
334
|
slug: string,
|
|
298
335
|
widgetId: string,
|
|
299
336
|
config: ConfigurablePieChartWidgetConfig,
|
|
300
|
-
): Promise<
|
|
301
|
-
return
|
|
337
|
+
): Promise<DashboardMutationResponse> {
|
|
338
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_pie_chart_widget', {
|
|
302
339
|
slug,
|
|
303
340
|
widgetId,
|
|
304
341
|
config,
|
|
@@ -309,8 +346,8 @@ export const dashboardApi = {
|
|
|
309
346
|
slug: string,
|
|
310
347
|
widgetId: string,
|
|
311
348
|
config: ConfigurableHistogramChartWidgetConfig,
|
|
312
|
-
): Promise<
|
|
313
|
-
return
|
|
349
|
+
): Promise<DashboardMutationResponse> {
|
|
350
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_histogram_chart_widget', {
|
|
314
351
|
slug,
|
|
315
352
|
widgetId,
|
|
316
353
|
config,
|
|
@@ -321,8 +358,8 @@ export const dashboardApi = {
|
|
|
321
358
|
slug: string,
|
|
322
359
|
widgetId: string,
|
|
323
360
|
config: ConfigurableFunnelChartWidgetConfig,
|
|
324
|
-
): Promise<
|
|
325
|
-
return
|
|
361
|
+
): Promise<DashboardMutationResponse> {
|
|
362
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_funnel_chart_widget', {
|
|
326
363
|
slug,
|
|
327
364
|
widgetId,
|
|
328
365
|
config,
|
|
@@ -333,8 +370,8 @@ export const dashboardApi = {
|
|
|
333
370
|
slug: string,
|
|
334
371
|
widgetId: string,
|
|
335
372
|
config: ConfigurablePivotTableWidgetConfig,
|
|
336
|
-
): Promise<
|
|
337
|
-
return
|
|
373
|
+
): Promise<DashboardMutationResponse> {
|
|
374
|
+
return callDashboardMutationApi('/adminapi/v1/dashboard/configure_pivot_table_widget', {
|
|
338
375
|
slug,
|
|
339
376
|
widgetId,
|
|
340
377
|
config,
|
|
@@ -140,19 +140,12 @@ export type ResourceQueryConfig = {
|
|
|
140
140
|
formatting?: Record<string, JsonValue>
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
export type StepsQueryStepConfig =
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
+
}
|
|
156
149
|
|
|
157
150
|
export type StepsQueryConfig = {
|
|
158
151
|
source: 'steps'
|
|
@@ -286,7 +286,8 @@ async function addGroup() {
|
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
try {
|
|
289
|
-
|
|
289
|
+
await dashboardApi.addDashboardGroup(props.dashboardSlug)
|
|
290
|
+
await refreshDashboardConfig()
|
|
290
291
|
} catch (error) {
|
|
291
292
|
console.error('Failed to add dashboard group', error)
|
|
292
293
|
}
|
|
@@ -298,7 +299,8 @@ async function addWidget(groupId: string) {
|
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
try {
|
|
301
|
-
|
|
302
|
+
await dashboardApi.addDashboardWidget(props.dashboardSlug, groupId)
|
|
303
|
+
await refreshDashboardConfig()
|
|
302
304
|
} catch (error) {
|
|
303
305
|
console.error('Failed to add dashboard widget', error)
|
|
304
306
|
}
|
|
@@ -310,9 +312,8 @@ async function moveGroup(groupId: string, direction: DashboardGroupMoveDirection
|
|
|
310
312
|
}
|
|
311
313
|
|
|
312
314
|
try {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
315
|
+
await dashboardApi.moveDashboardGroup(props.dashboardSlug, groupId, direction)
|
|
316
|
+
await refreshDashboardConfig()
|
|
316
317
|
} catch (error) {
|
|
317
318
|
console.error('Failed to move dashboard group', error)
|
|
318
319
|
}
|
|
@@ -324,7 +325,8 @@ async function removeGroup(groupId: string) {
|
|
|
324
325
|
}
|
|
325
326
|
|
|
326
327
|
try {
|
|
327
|
-
|
|
328
|
+
await dashboardApi.removeDashboardGroup(props.dashboardSlug, groupId)
|
|
329
|
+
await refreshDashboardConfig()
|
|
328
330
|
} catch (error) {
|
|
329
331
|
console.error('Failed to remove dashboard group', error)
|
|
330
332
|
}
|
|
@@ -348,13 +350,12 @@ async function saveGroupConfig() {
|
|
|
348
350
|
try {
|
|
349
351
|
const groupConfig = parseYaml(groupConfigCode.value) as EditableDashboardGroupConfig
|
|
350
352
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
groupConfig,
|
|
356
|
-
),
|
|
353
|
+
await dashboardApi.setDashboardGroupConfig(
|
|
354
|
+
props.dashboardSlug,
|
|
355
|
+
editingGroupId.value,
|
|
356
|
+
groupConfig,
|
|
357
357
|
)
|
|
358
|
+
await refreshDashboardConfig()
|
|
358
359
|
closeGroupConfigEditor()
|
|
359
360
|
} catch (error) {
|
|
360
361
|
groupConfigError.value = error instanceof Error ? error.message : 'Invalid group config'
|
|
@@ -373,9 +374,8 @@ async function moveWidget(widgetId: string, direction: DashboardWidgetMoveDirect
|
|
|
373
374
|
}
|
|
374
375
|
|
|
375
376
|
try {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
)
|
|
377
|
+
await dashboardApi.moveDashboardWidget(props.dashboardSlug, widgetId, direction)
|
|
378
|
+
await refreshDashboardConfig()
|
|
379
379
|
} catch (error) {
|
|
380
380
|
console.error('Failed to move dashboard widget', error)
|
|
381
381
|
}
|
|
@@ -387,7 +387,8 @@ async function removeWidget(widgetId: string) {
|
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
try {
|
|
390
|
-
|
|
390
|
+
await dashboardApi.removeDashboardWidget(props.dashboardSlug, widgetId)
|
|
391
|
+
await refreshDashboardConfig()
|
|
391
392
|
} catch (error) {
|
|
392
393
|
console.error('Failed to remove dashboard widget', error)
|
|
393
394
|
}
|
|
@@ -410,13 +411,12 @@ async function saveWidgetConfig() {
|
|
|
410
411
|
widgetConfigFieldErrors.value = []
|
|
411
412
|
const widgetConfig = parseYaml(widgetConfigCode.value) as DashboardWidgetConfig
|
|
412
413
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
serializeDashboardWidgetConfigForEditor(widgetConfig),
|
|
418
|
-
),
|
|
414
|
+
await dashboardApi.setWidgetConfig(
|
|
415
|
+
props.dashboardSlug,
|
|
416
|
+
editingWidgetId.value,
|
|
417
|
+
serializeDashboardWidgetConfigForEditor(widgetConfig),
|
|
419
418
|
)
|
|
419
|
+
await refreshDashboardConfig()
|
|
420
420
|
closeWidgetConfigEditor()
|
|
421
421
|
} catch (error) {
|
|
422
422
|
widgetConfigError.value = error instanceof Error ? error.message : 'Invalid widget config'
|
|
@@ -431,6 +431,10 @@ function closeWidgetConfigEditor() {
|
|
|
431
431
|
widgetConfigFieldErrors.value = []
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
async function refreshDashboardConfig() {
|
|
435
|
+
applyDashboardResponse(await dashboardApi.getDashboardConfig(props.dashboardSlug))
|
|
436
|
+
}
|
|
437
|
+
|
|
434
438
|
function applyDashboardResponse(response: DashboardResponse) {
|
|
435
439
|
draftConfig.value = cloneConfig(response.config)
|
|
436
440
|
currentRevision.value = response.revision
|
|
@@ -169,21 +169,18 @@ query:
|
|
|
169
169
|
steps:
|
|
170
170
|
- name: Leads
|
|
171
171
|
resource: leads
|
|
172
|
-
|
|
173
|
-
agg: count
|
|
174
|
-
|
|
172
|
+
select:
|
|
173
|
+
- agg: count
|
|
174
|
+
as: value
|
|
175
175
|
- name: Customers
|
|
176
176
|
resource: orders
|
|
177
|
-
|
|
178
|
-
agg: count_distinct
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
185
181
|
|
|
186
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.
|
|
187
184
|
|
|
188
185
|
## Date range rules
|
|
189
186
|
|
|
@@ -224,22 +221,18 @@ select raw token totals:
|
|
|
224
221
|
- sum output_tokens as output_tokens
|
|
225
222
|
|
|
226
223
|
then query.calcs:
|
|
227
|
-
- calculate total_spend from those aliases
|
|
224
|
+
- calculate total_spend from those aliases with explicit constants
|
|
228
225
|
|
|
229
226
|
For today vs yesterday KPI, use multiple aggregate select items with filters and distinct aliases, then calcs.
|
|
230
227
|
|
|
231
|
-
## Calc
|
|
228
|
+
## Calc rules
|
|
232
229
|
|
|
233
|
-
|
|
234
|
-
Use
|
|
230
|
+
Calcs can reference only fields already present in the current row.
|
|
231
|
+
Use explicit constants for rates.
|
|
235
232
|
|
|
236
233
|
Minimal example:
|
|
237
234
|
|
|
238
|
-
variables:
|
|
239
|
-
prices:
|
|
240
|
-
gpt-5.4: 2.5
|
|
241
|
-
|
|
242
235
|
query:
|
|
243
236
|
calcs:
|
|
244
|
-
- calc: tokens / 1000000 *
|
|
237
|
+
- calc: tokens / 1000000 * 2.5
|
|
245
238
|
as: cost
|