@adminforth/dashboard 1.3.0 → 1.4.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 CHANGED
@@ -29,6 +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
33
  | `size` | Preset width: `small`, `medium`, `large`, `wide`, or `full`. |
33
34
  | `width`, `height`, `min_width`, `max_width` | Optional explicit layout constraints. |
34
35
  | `query` | Data query definition. |
@@ -38,7 +39,7 @@ Each widget has common fields:
38
39
  | Widget target | Config field | Main settings | Data usage |
39
40
  | --- | --- | --- | --- |
40
41
  | `table` | `table` | `pagination`, `page_size`, `columns` | Uses `query` to display raw or aggregate rows. |
41
- | `chart` | `chart` | `type`, `x`, `y`, `label`, `value`, `series`, `buckets`, `color`, `colors` | Uses `query`; funnel charts use `query.steps`. |
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
43
  | `kpi_card` | `card` | `value`, `subtitle`, `comparison`, `sparkline` | Reads the first returned query row. |
43
44
  | `gauge_card` | `card` | `value`, `target`, `progress`, `color` | Reads the first returned query row. |
44
45
  | `pivot_table` | `pivot` | `rows`, `columns`, `values` | Uses query rows to build a pivot table. |
@@ -72,26 +73,94 @@ type QueryConfig = {
72
73
  }
73
74
  ```
74
75
 
75
- Funnel charts use a steps query:
76
+ Step-based chart queries use `steps` and may include `calcs`:
76
77
 
77
78
  ```yaml
78
79
  target: chart
80
+ label: Average price by database
81
+ variables:
82
+ price_multipliers:
83
+ cars_sl: 0.84
84
+ cars_mysql: 1.12
85
+ cars_pg: 0.91
86
+ cars_mongo: 1.07
87
+ cars_ch: 0.76
79
88
  chart:
80
- type: funnel
81
- title: Sales funnel
89
+ type: bar
90
+ title: Average price by database
91
+ x:
92
+ field: name
93
+ y:
94
+ field: adjusted_value
82
95
  query:
83
96
  steps:
84
- - name: Leads
85
- resource: leads
97
+ - name: SQLite
98
+ resource: cars_sl
86
99
  metric:
87
- agg: count
100
+ agg: avg
101
+ field: price
88
102
  as: value
89
- - name: Customers
90
- resource: orders
103
+ - name: MySQL
104
+ resource: cars_mysql
91
105
  metric:
92
- agg: count_distinct
93
- field: customer_id
106
+ agg: avg
107
+ field: price
94
108
  as: value
109
+ calcs:
110
+ - calc: value * lookup($variables.price_multipliers, resource, 1)
111
+ as: adjusted_value
112
+ ```
113
+
114
+ Widget-level variables example:
115
+
116
+ ```yaml
117
+ target: chart
118
+ label: Model costs
119
+ variables:
120
+ token_prices_per_1m:
121
+ input:
122
+ gpt-4.1: 2.00
123
+ gpt-4.1-mini: 0.40
124
+ gpt-4o-mini: 0.15
125
+ output:
126
+ gpt-4.1: 8.00
127
+ gpt-4.1-mini: 1.60
128
+ gpt-4o-mini: 0.60
129
+ cached:
130
+ gpt-4.1: 0.50
131
+ gpt-4.1-mini: 0.10
132
+ gpt-4o-mini: 0.075
133
+ chart:
134
+ type: stacked_bar
135
+ title: LLM costs by model
136
+ x:
137
+ field: model
138
+ y:
139
+ - field: input_cost
140
+ - field: output_cost
141
+ - field: cached_cost
142
+ query:
143
+ resource: model_usage
144
+ select:
145
+ - field: model
146
+ - agg: sum
147
+ field: input_tokens
148
+ as: input_tokens
149
+ - agg: sum
150
+ field: output_tokens
151
+ as: output_tokens
152
+ - agg: sum
153
+ field: cached_tokens
154
+ as: cached_tokens
155
+ group_by:
156
+ - model
157
+ calcs:
158
+ - calc: input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
159
+ as: input_cost
160
+ - calc: output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
161
+ as: output_cost
162
+ - calc: cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
163
+ as: cached_cost
95
164
  ```
96
165
 
97
166
  ## Runtime Structure
@@ -143,6 +143,10 @@ export const dashboardApi = {
143
143
  return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
144
144
  },
145
145
 
146
+ async setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse> {
147
+ return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
148
+ },
149
+
146
150
  async addDashboardGroup(slug: string): Promise<DashboardResponse> {
147
151
  return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
148
152
  },
@@ -6,6 +6,8 @@ export type DashboardConfig = {
6
6
  widgets: DashboardWidgetConfig[]
7
7
  }
8
8
 
9
+ export type DashboardVariables = Record<string, unknown>
10
+
9
11
  export type DashboardGroupConfig = {
10
12
  id: string
11
13
  label: string
@@ -39,6 +41,7 @@ export type WidgetBaseConfig = {
39
41
  id: string
40
42
  group_id: string
41
43
  label?: string
44
+ variables?: DashboardVariables
42
45
  size?: DashboardWidgetSize
43
46
  width?: number
44
47
  height?: number
@@ -127,6 +130,7 @@ export type QueryConfig = {
127
130
 
128
131
  export type FunnelQueryConfig = {
129
132
  steps: FunnelQueryStep[]
133
+ calcs?: QueryCalcSelectItem[]
130
134
  }
131
135
 
132
136
  export type FunnelQueryStep = {
@@ -381,9 +385,10 @@ function normalizeQueryConfig(value: unknown): unknown {
381
385
  }
382
386
 
383
387
  if (Array.isArray(value.steps)) {
384
- return {
388
+ return removeUndefinedFields({
385
389
  steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
386
- }
390
+ calcs: Array.isArray(value.calcs) ? value.calcs as QueryCalcSelectItem[] : undefined,
391
+ })
387
392
  }
388
393
 
389
394
  return {
@@ -399,10 +404,12 @@ function normalizeFunnelQueryStep(value: unknown) {
399
404
  return value
400
405
  }
401
406
 
402
- return {
403
- ...value,
404
- ...(typeof value.resource_id === 'string' ? { resource: value.resource_id } : {}),
405
- }
407
+ const { resource_id, ...rest } = value
408
+
409
+ return removeUndefinedFields({
410
+ ...rest,
411
+ resource: typeof resource_id === 'string' ? resource_id : rest.resource,
412
+ })
406
413
  }
407
414
 
408
415
  function normalizeTableConfig(value: unknown) {
@@ -448,13 +455,14 @@ function normalizePivotConfig(value: unknown): unknown {
448
455
 
449
456
  function serializeQueryConfigForEditor(value: QueryConfig | FunnelQueryConfig) {
450
457
  if ('steps' in value) {
451
- return {
458
+ return removeUndefinedFields({
452
459
  steps: value.steps.map((step) => ({
453
460
  ...step,
454
461
  resource_id: step.resource,
455
462
  resource: undefined,
456
463
  })).map((step) => removeUndefinedFields(step)),
457
- }
464
+ calcs: value.calcs,
465
+ })
458
466
  }
459
467
 
460
468
  return removeUndefinedFields({
@@ -41,6 +41,7 @@ If the user asks how the schema works, how to implement the API, or how to chang
41
41
  Use these tools whenever available:
42
42
 
43
43
  - `dashboard_get_config`
44
+ - `dashboard_set_dashboard_config`
44
45
  - `dashboard_add_dashboard_group`
45
46
  - `dashboard_set_dashboard_group_config`
46
47
  - `dashboard_move_dashboard_group`
@@ -58,6 +59,7 @@ If a dashboard tool is known by name but its argument schema is not loaded, call
58
59
  Do not pass fields between dashboard tools by analogy. Use each tool's schema.
59
60
 
60
61
  - `dashboard_add_dashboard_group` creates a new group. It accepts the dashboard slug only. Never pass `groupId` to this tool.
62
+ - `dashboard_set_dashboard_config` replaces the full dashboard config. Use it when the user explicitly asks to edit the whole dashboard config.
61
63
  - `dashboard_add_dashboard_widget` creates a widget inside an existing group. Use it when you already have a `groupId`.
62
64
  - `dashboard_set_dashboard_group_config`, `dashboard_move_dashboard_group`, and `dashboard_remove_dashboard_group` operate on an existing group and need `groupId`.
63
65
  - `dashboard_set_widget_config`, `dashboard_move_dashboard_widget`, `dashboard_remove_dashboard_widget`, and `dashboard_get_dashboard_widget_data` operate on an existing widget and need `widgetId`.
@@ -113,6 +115,24 @@ For group requests:
113
115
 
114
116
  If slug is missing, use `default`.
115
117
 
118
+ ## Dashboard Config Workflow
119
+
120
+ Use `dashboard_set_dashboard_config` only when the user explicitly asks to edit the whole dashboard root config.
121
+
122
+ For requests like:
123
+
124
+ - "update root dashboard config"
125
+ - "replace the whole dashboard config"
126
+
127
+ do this:
128
+
129
+ 1. Call `dashboard_get_config`.
130
+ 2. Modify the returned root config, preserving existing `version`, `groups`, and `widgets` unless the user asked to change them.
131
+ 3. Call `dashboard_set_dashboard_config` with the full updated config.
132
+ 4. Return a short summary of the root-level fields changed.
133
+
134
+ Do not use `dashboard_set_dashboard_config` to store reusable widget variables.
135
+
116
136
  ## Widget Config Rules
117
137
 
118
138
  Use the current schema keys exactly:
@@ -124,6 +144,93 @@ Use the current schema keys exactly:
124
144
  - Use `group_by`, not `groupBy`.
125
145
  - Use `order_by`, not `orderBy`.
126
146
  - Use `page_size`, not `pageSize`.
127
- - For funnel charts, use `query.steps` as an ordered array of `{ name, resource, metric, filters }` steps.
147
+ - For step-based chart queries, use `query.steps` as an ordered array of `{ name, resource, metric, filters }` steps and add `query.calcs` when derived fields are needed.
128
148
  - Use `card` for KPI and gauge widget view config.
129
149
  - Use `pivot` for pivot table view config.
150
+ - Use `variables` for reusable static maps or constants at widget level.
151
+ - In `query.calcs`, use `lookup($variables.some.map, row_field, default_number)` to read a numeric value from a variable map by the current row/group field.
152
+
153
+ ## Variables And Lookup Calcs
154
+
155
+ Widget config can define variables:
156
+
157
+ ```yaml
158
+ variables:
159
+ token_prices_per_1m:
160
+ input:
161
+ gpt-4.1: 2.00
162
+ gpt-4.1-mini: 0.40
163
+ gpt-4o-mini: 0.15
164
+ output:
165
+ gpt-4.1: 8.00
166
+ gpt-4.1-mini: 1.60
167
+ gpt-4o-mini: 0.60
168
+ cached:
169
+ gpt-4.1: 0.50
170
+ gpt-4.1-mini: 0.10
171
+ gpt-4o-mini: 0.075
172
+ ```
173
+
174
+ Use variables when a calculation needs a static rate table, threshold table, coefficient map, or other reusable constants. In calcs, `lookup($variables.path.to.map, field_name, 0)` returns the value from the map using `field_name` from the current row/group. The third argument is the numeric fallback when the key is missing.
175
+
176
+ Example widget:
177
+
178
+ ```yaml
179
+ target: chart
180
+ label: Model costs
181
+ size: large
182
+ variables:
183
+ token_prices_per_1m:
184
+ input:
185
+ gpt-4.1: 2.00
186
+ gpt-4.1-mini: 0.40
187
+ gpt-4o-mini: 0.15
188
+ output:
189
+ gpt-4.1: 8.00
190
+ gpt-4.1-mini: 1.60
191
+ gpt-4o-mini: 0.60
192
+ cached:
193
+ gpt-4.1: 0.50
194
+ gpt-4.1-mini: 0.10
195
+ gpt-4o-mini: 0.075
196
+
197
+ chart:
198
+ type: stacked_bar
199
+ title: LLM costs by model
200
+ x:
201
+ field: model
202
+ label: Model
203
+ y:
204
+ - field: input_cost
205
+ label: Input
206
+ format: currency
207
+ - field: output_cost
208
+ label: Output
209
+ format: currency
210
+ - field: cached_cost
211
+ label: Cached
212
+ format: currency
213
+
214
+ query:
215
+ resource: model_usage
216
+ select:
217
+ - field: model
218
+ - agg: sum
219
+ field: input_tokens
220
+ as: input_tokens
221
+ - agg: sum
222
+ field: output_tokens
223
+ as: output_tokens
224
+ - agg: sum
225
+ field: cached_tokens
226
+ as: cached_tokens
227
+ group_by:
228
+ - model
229
+ calcs:
230
+ - calc: input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
231
+ as: input_cost
232
+ - calc: output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
233
+ as: output_cost
234
+ - calc: cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
235
+ as: cached_cost
236
+ ```
@@ -69,10 +69,10 @@
69
69
 
70
70
  <StackedBarChart
71
71
  v-else-if="chartConfig?.type === 'stacked_bar'"
72
- :rows="rows"
72
+ :rows="stackedBarRows"
73
73
  :x-field="xField"
74
- :y-field="yField"
75
- :series-field="seriesField"
74
+ :y-field="stackedBarYField"
75
+ :series-field="stackedBarSeriesField"
76
76
  :colors="chartConfig.colors"
77
77
  :height="chartHeight"
78
78
  />
@@ -155,6 +155,21 @@ const valueField = computed(() => chartConfig.value?.value?.field || columns.val
155
155
  const pieRows = computed(() => rows.value)
156
156
  const pieLabelField = computed(() => labelField.value)
157
157
  const pieValueField = computed(() => valueField.value)
158
+ const stackedBarYItems = computed(() => {
159
+ const y = chartConfig.value?.y
160
+ return Array.isArray(y) ? y : []
161
+ })
162
+ const stackedBarRows = computed(() => {
163
+ if (chartConfig.value?.type !== 'stacked_bar' || !stackedBarYItems.value.length) {
164
+ return rows.value
165
+ }
166
+
167
+ return rows.value.flatMap((row) => stackedBarYItems.value.map((item) => ({
168
+ [xField.value]: row[xField.value],
169
+ __series: item.label ?? item.field,
170
+ __value: row[item.field],
171
+ })))
172
+ })
158
173
  const barRows = computed(() => {
159
174
  const bucketField = chartConfig.value?.type === 'histogram'
160
175
  ? chartConfig.value.x?.field
@@ -178,6 +193,8 @@ const barRows = computed(() => {
178
193
  const barLabelField = computed(() => chartConfig.value?.type === 'histogram' && chartConfig.value.buckets ? 'label' : xField.value)
179
194
  const barValueField = computed(() => chartConfig.value?.type === 'histogram' && chartConfig.value.buckets ? 'count' : yField.value)
180
195
  const seriesField = computed(() => chartConfig.value?.series?.field || columns.value[2] || '')
196
+ const stackedBarYField = computed(() => stackedBarYItems.value.length ? '__value' : yField.value)
197
+ const stackedBarSeriesField = computed(() => stackedBarYItems.value.length ? '__series' : seriesField.value)
181
198
  const lineSeriesName = computed(() => {
182
199
  const y = chartConfig.value?.y
183
200
  return Array.isArray(y) ? y[0]?.label : undefined
@@ -26,6 +26,7 @@ export declare class DashboardApiError extends Error {
26
26
  }
27
27
  export declare const dashboardApi: {
28
28
  getDashboardConfig(slug: string): Promise<DashboardResponse>;
29
+ setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse>;
29
30
  addDashboardGroup(slug: string): Promise<DashboardResponse>;
30
31
  moveDashboardGroup(slug: string, groupId: string, direction: DashboardGroupMoveDirection): Promise<DashboardResponse>;
31
32
  removeDashboardGroup(slug: string, groupId: string): Promise<DashboardResponse>;
@@ -103,6 +103,11 @@ exports.dashboardApi = {
103
103
  return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug });
104
104
  });
105
105
  },
106
+ setDashboardConfig(slug, config) {
107
+ return __awaiter(this, void 0, void 0, function* () {
108
+ return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config });
109
+ });
110
+ },
106
111
  addDashboardGroup(slug) {
107
112
  return __awaiter(this, void 0, void 0, function* () {
108
113
  return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug });
@@ -143,6 +143,10 @@ export const dashboardApi = {
143
143
  return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
144
144
  },
145
145
 
146
+ async setDashboardConfig(slug: string, config: DashboardConfig): Promise<DashboardResponse> {
147
+ return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
148
+ },
149
+
146
150
  async addDashboardGroup(slug: string): Promise<DashboardResponse> {
147
151
  return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
148
152
  },
@@ -4,6 +4,7 @@ export type DashboardConfig = {
4
4
  groups: DashboardGroupConfig[];
5
5
  widgets: DashboardWidgetConfig[];
6
6
  };
7
+ export type DashboardVariables = Record<string, unknown>;
7
8
  export type DashboardGroupConfig = {
8
9
  id: string;
9
10
  label: string;
@@ -27,6 +28,7 @@ export type WidgetBaseConfig = {
27
28
  id: string;
28
29
  group_id: string;
29
30
  label?: string;
31
+ variables?: DashboardVariables;
30
32
  size?: DashboardWidgetSize;
31
33
  width?: number;
32
34
  height?: number;
@@ -108,6 +110,7 @@ export type QueryConfig = {
108
110
  };
109
111
  export type FunnelQueryConfig = {
110
112
  steps: FunnelQueryStep[];
113
+ calcs?: QueryCalcSelectItem[];
111
114
  };
112
115
  export type FunnelQueryStep = {
113
116
  name: string;
@@ -1,4 +1,15 @@
1
1
  "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
2
13
  Object.defineProperty(exports, "__esModule", { value: true });
3
14
  exports.normalizeDashboardConfig = normalizeDashboardConfig;
4
15
  exports.normalizeDashboardWidgetConfig = normalizeDashboardWidgetConfig;
@@ -95,9 +106,10 @@ function normalizeQueryConfig(value) {
95
106
  return value;
96
107
  }
97
108
  if (Array.isArray(value.steps)) {
98
- return {
109
+ return removeUndefinedFields({
99
110
  steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
100
- };
111
+ calcs: Array.isArray(value.calcs) ? value.calcs : undefined,
112
+ });
101
113
  }
102
114
  return Object.assign(Object.assign(Object.assign(Object.assign({}, value), (Array.isArray(value.group_by) ? { groupBy: value.group_by } : {})), (Array.isArray(value.order_by) ? { orderBy: value.order_by } : {})), (value.time_series !== undefined ? { timeSeries: value.time_series } : {}));
103
115
  }
@@ -105,7 +117,8 @@ function normalizeFunnelQueryStep(value) {
105
117
  if (!isRecord(value)) {
106
118
  return value;
107
119
  }
108
- return Object.assign(Object.assign({}, value), (typeof value.resource_id === 'string' ? { resource: value.resource_id } : {}));
120
+ const { resource_id } = value, rest = __rest(value, ["resource_id"]);
121
+ return removeUndefinedFields(Object.assign(Object.assign({}, rest), { resource: typeof resource_id === 'string' ? resource_id : rest.resource }));
109
122
  }
110
123
  function normalizeTableConfig(value) {
111
124
  if (!isRecord(value)) {
@@ -131,9 +144,10 @@ function normalizePivotConfig(value) {
131
144
  }
132
145
  function serializeQueryConfigForEditor(value) {
133
146
  if ('steps' in value) {
134
- return {
147
+ return removeUndefinedFields({
135
148
  steps: value.steps.map((step) => (Object.assign(Object.assign({}, step), { resource_id: step.resource, resource: undefined }))).map((step) => removeUndefinedFields(step)),
136
- };
149
+ calcs: value.calcs,
150
+ });
137
151
  }
138
152
  return removeUndefinedFields(Object.assign(Object.assign({}, value), { group_by: value.groupBy, groupBy: undefined, order_by: value.orderBy, orderBy: undefined, time_series: value.timeSeries, timeSeries: undefined }));
139
153
  }
@@ -6,6 +6,8 @@ export type DashboardConfig = {
6
6
  widgets: DashboardWidgetConfig[]
7
7
  }
8
8
 
9
+ export type DashboardVariables = Record<string, unknown>
10
+
9
11
  export type DashboardGroupConfig = {
10
12
  id: string
11
13
  label: string
@@ -39,6 +41,7 @@ export type WidgetBaseConfig = {
39
41
  id: string
40
42
  group_id: string
41
43
  label?: string
44
+ variables?: DashboardVariables
42
45
  size?: DashboardWidgetSize
43
46
  width?: number
44
47
  height?: number
@@ -127,6 +130,7 @@ export type QueryConfig = {
127
130
 
128
131
  export type FunnelQueryConfig = {
129
132
  steps: FunnelQueryStep[]
133
+ calcs?: QueryCalcSelectItem[]
130
134
  }
131
135
 
132
136
  export type FunnelQueryStep = {
@@ -381,9 +385,10 @@ function normalizeQueryConfig(value: unknown): unknown {
381
385
  }
382
386
 
383
387
  if (Array.isArray(value.steps)) {
384
- return {
388
+ return removeUndefinedFields({
385
389
  steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
386
- }
390
+ calcs: Array.isArray(value.calcs) ? value.calcs as QueryCalcSelectItem[] : undefined,
391
+ })
387
392
  }
388
393
 
389
394
  return {
@@ -399,10 +404,12 @@ function normalizeFunnelQueryStep(value: unknown) {
399
404
  return value
400
405
  }
401
406
 
402
- return {
403
- ...value,
404
- ...(typeof value.resource_id === 'string' ? { resource: value.resource_id } : {}),
405
- }
407
+ const { resource_id, ...rest } = value
408
+
409
+ return removeUndefinedFields({
410
+ ...rest,
411
+ resource: typeof resource_id === 'string' ? resource_id : rest.resource,
412
+ })
406
413
  }
407
414
 
408
415
  function normalizeTableConfig(value: unknown) {
@@ -448,13 +455,14 @@ function normalizePivotConfig(value: unknown): unknown {
448
455
 
449
456
  function serializeQueryConfigForEditor(value: QueryConfig | FunnelQueryConfig) {
450
457
  if ('steps' in value) {
451
- return {
458
+ return removeUndefinedFields({
452
459
  steps: value.steps.map((step) => ({
453
460
  ...step,
454
461
  resource_id: step.resource,
455
462
  resource: undefined,
456
463
  })).map((step) => removeUndefinedFields(step)),
457
- }
464
+ calcs: value.calcs,
465
+ })
458
466
  }
459
467
 
460
468
  return removeUndefinedFields({
@@ -16,6 +16,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
16
16
  id: string;
17
17
  group_id: string;
18
18
  label?: string | undefined;
19
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
19
20
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
20
21
  width?: number | undefined;
21
22
  height?: number | undefined;
@@ -27,6 +28,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
27
28
  id: string;
28
29
  group_id: string;
29
30
  label?: string | undefined;
31
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
30
32
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
31
33
  width?: number | undefined;
32
34
  height?: number | undefined;
@@ -99,6 +101,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
99
101
  id: string;
100
102
  group_id: string;
101
103
  label?: string | undefined;
104
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
102
105
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
103
106
  width?: number | undefined;
104
107
  height?: number | undefined;
@@ -209,11 +212,16 @@ export declare function useDashboardConfig(slug: Ref<string>): {
209
212
  };
210
213
  filters?: any;
211
214
  }[];
215
+ calcs?: {
216
+ calc: string;
217
+ as: string;
218
+ }[] | undefined;
212
219
  };
213
220
  } | {
214
221
  id: string;
215
222
  group_id: string;
216
223
  label?: string | undefined;
224
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
217
225
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
218
226
  width?: number | undefined;
219
227
  height?: number | undefined;
@@ -292,6 +300,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
292
300
  id: string;
293
301
  group_id: string;
294
302
  label?: string | undefined;
303
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
295
304
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
296
305
  width?: number | undefined;
297
306
  height?: number | undefined;
@@ -376,6 +385,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
376
385
  id: string;
377
386
  group_id: string;
378
387
  label?: string | undefined;
388
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
379
389
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
380
390
  width?: number | undefined;
381
391
  height?: number | undefined;
@@ -471,6 +481,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
471
481
  id: string;
472
482
  group_id: string;
473
483
  label?: string | undefined;
484
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
474
485
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
475
486
  width?: number | undefined;
476
487
  height?: number | undefined;
@@ -482,6 +493,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
482
493
  id: string;
483
494
  group_id: string;
484
495
  label?: string | undefined;
496
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
485
497
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
486
498
  width?: number | undefined;
487
499
  height?: number | undefined;
@@ -554,6 +566,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
554
566
  id: string;
555
567
  group_id: string;
556
568
  label?: string | undefined;
569
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
557
570
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
558
571
  width?: number | undefined;
559
572
  height?: number | undefined;
@@ -664,11 +677,16 @@ export declare function useDashboardConfig(slug: Ref<string>): {
664
677
  };
665
678
  filters?: any;
666
679
  }[];
680
+ calcs?: {
681
+ calc: string;
682
+ as: string;
683
+ }[] | undefined;
667
684
  };
668
685
  } | {
669
686
  id: string;
670
687
  group_id: string;
671
688
  label?: string | undefined;
689
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
672
690
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
673
691
  width?: number | undefined;
674
692
  height?: number | undefined;
@@ -747,6 +765,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
747
765
  id: string;
748
766
  group_id: string;
749
767
  label?: string | undefined;
768
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
750
769
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
751
770
  width?: number | undefined;
752
771
  height?: number | undefined;
@@ -831,6 +850,7 @@ export declare function useDashboardConfig(slug: Ref<string>): {
831
850
  id: string;
832
851
  group_id: string;
833
852
  label?: string | undefined;
853
+ variables?: import("../model/dashboard.types.js").DashboardVariables | undefined;
834
854
  size?: import("../model/dashboard.types.js").DashboardWidgetSize | undefined;
835
855
  width?: number | undefined;
836
856
  height?: number | undefined;