@adminforth/dashboard 1.3.0 → 1.4.1

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 (80) hide show
  1. package/README.md +103 -15
  2. package/custom/api/dashboardApi.ts +9 -8
  3. package/custom/model/dashboard.types.ts +63 -270
  4. package/custom/model/dashboardTopics.ts +5 -0
  5. package/custom/runtime/DashboardGroup.vue +2 -2
  6. package/custom/runtime/DashboardPage.vue +17 -7
  7. package/custom/runtime/DashboardRuntime.vue +20 -8
  8. package/custom/runtime/WidgetRenderer.vue +1 -2
  9. package/custom/runtime/WidgetShell.vue +3 -3
  10. package/custom/skills/adminforth-dashboard/SKILL.md +110 -3
  11. package/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  12. package/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  13. package/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  14. package/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  15. package/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  16. package/custom/widgets/chart/ChartWidget.vue +24 -18
  17. package/{dist/custom/widgets/chart/funnel → custom/widgets/chart}/FunnelChart.vue +80 -78
  18. package/{dist/custom/widgets/chart/line → custom/widgets/chart}/LineChart.vue +2 -2
  19. package/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  20. package/{dist/custom/widgets/chart/stacked-bar → custom/widgets/chart}/StackedBarChart.vue +97 -95
  21. package/custom/widgets/chart/chart.types.ts +0 -28
  22. package/dist/custom/api/dashboardApi.d.ts +4 -7
  23. package/dist/custom/api/dashboardApi.js +5 -0
  24. package/dist/custom/api/dashboardApi.ts +9 -8
  25. package/dist/custom/model/dashboard.types.d.ts +40 -31
  26. package/dist/custom/model/dashboard.types.js +13 -152
  27. package/dist/custom/model/dashboard.types.ts +63 -270
  28. package/dist/custom/model/dashboardTopics.d.ts +2 -0
  29. package/dist/custom/model/dashboardTopics.js +8 -0
  30. package/dist/custom/model/dashboardTopics.ts +5 -0
  31. package/dist/custom/queries/useDashboardConfig.d.ts +116 -96
  32. package/dist/custom/queries/useWidgetData.d.ts +116 -96
  33. package/dist/custom/runtime/DashboardGroup.vue +2 -2
  34. package/dist/custom/runtime/DashboardPage.vue +17 -7
  35. package/dist/custom/runtime/DashboardRuntime.vue +20 -8
  36. package/dist/custom/runtime/WidgetRenderer.vue +1 -2
  37. package/dist/custom/runtime/WidgetShell.vue +3 -3
  38. package/dist/custom/skills/adminforth-dashboard/SKILL.md +110 -3
  39. package/dist/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
  40. package/dist/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
  41. package/dist/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
  42. package/dist/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
  43. package/dist/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
  44. package/dist/custom/widgets/chart/ChartWidget.vue +24 -18
  45. package/{custom/widgets/chart/funnel → dist/custom/widgets/chart}/FunnelChart.vue +80 -78
  46. package/{custom/widgets/chart/line → dist/custom/widgets/chart}/LineChart.vue +2 -2
  47. package/dist/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
  48. package/{custom/widgets/chart/stacked-bar → dist/custom/widgets/chart}/StackedBarChart.vue +97 -95
  49. package/dist/custom/widgets/chart/chart.types.d.ts +0 -2
  50. package/dist/custom/widgets/chart/chart.types.js +0 -23
  51. package/dist/custom/widgets/chart/chart.types.ts +0 -28
  52. package/dist/endpoint/dashboard.d.ts +6 -2
  53. package/dist/endpoint/dashboard.js +29 -5
  54. package/dist/endpoint/groups.d.ts +2 -21
  55. package/dist/endpoint/groups.js +18 -16
  56. package/dist/endpoint/widgets.d.ts +2 -4
  57. package/dist/endpoint/widgets.js +28 -74
  58. package/dist/index.js +1 -3
  59. package/dist/schema/api.d.ts +2172 -500
  60. package/dist/schema/api.js +21 -13
  61. package/dist/schema/widget.d.ts +1076 -263
  62. package/dist/schema/widget.js +108 -49
  63. package/dist/services/dashboardConfigService.d.ts +0 -10
  64. package/dist/services/dashboardConfigService.js +6 -21
  65. package/dist/services/widgetDataService.d.ts +2 -1
  66. package/dist/services/widgetDataService.js +266 -206
  67. package/endpoint/dashboard.ts +47 -7
  68. package/endpoint/groups.ts +25 -42
  69. package/endpoint/widgets.ts +41 -96
  70. package/index.ts +0 -3
  71. package/package.json +3 -3
  72. package/schema/api.ts +23 -13
  73. package/schema/widget.ts +119 -55
  74. package/services/dashboardConfigService.ts +6 -25
  75. package/services/widgetDataService.ts +350 -237
  76. package/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  77. package/dist/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
  78. package/dist/services/widgetConfigValidator.d.ts +0 -8
  79. package/dist/services/widgetConfigValidator.js +0 -27
  80. package/services/widgetConfigValidator.ts +0 -61
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. |
@@ -60,38 +61,125 @@ Chart widget types:
60
61
  type QueryConfig = {
61
62
  resource: string
62
63
  select?: Array<
63
- | { field: string; as?: string; grain?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' }
64
- | { agg: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'; field?: string; as: string; filters?: unknown }
64
+ | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year' }
65
+ | { agg: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'; field?: string; as: string; filters?: DashboardFilter | DashboardFilter[] }
65
66
  | { calc: string; as: string }
66
67
  >
67
- filters?: unknown
68
- group_by?: Array<string | { field: string; as?: string; grain?: 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; timezone?: string }>
68
+ filters?: DashboardFilter | DashboardFilter[]
69
+ group_by?: Array<string | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year'; timezone?: string }>
69
70
  order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
70
71
  limit?: number
71
72
  offset?: number
72
73
  }
74
+
75
+ type DashboardFilter =
76
+ | { and: DashboardFilter[] }
77
+ | { or: DashboardFilter[] }
78
+ | {
79
+ 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
90
+ }
91
+
92
+ type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }
73
93
  ```
74
94
 
75
- Funnel charts use a steps query:
95
+ Step-based chart queries use `steps` and may include `calcs`:
76
96
 
77
97
  ```yaml
78
98
  target: chart
99
+ 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
79
107
  chart:
80
- type: funnel
81
- title: Sales funnel
108
+ type: bar
109
+ title: Average price by database
110
+ x:
111
+ field: name
112
+ y:
113
+ field: adjusted_value
82
114
  query:
83
115
  steps:
84
- - name: Leads
85
- resource: leads
116
+ - name: SQLite
117
+ resource: cars_sl
86
118
  metric:
87
- agg: count
119
+ agg: avg
120
+ field: price
88
121
  as: value
89
- - name: Customers
90
- resource: orders
122
+ - name: MySQL
123
+ resource: cars_mysql
91
124
  metric:
92
- agg: count_distinct
93
- field: customer_id
125
+ agg: avg
126
+ field: price
94
127
  as: value
128
+ calcs:
129
+ - calc: value * lookup($variables.price_multipliers, resource, 1)
130
+ as: adjusted_value
131
+ ```
132
+
133
+ Widget-level variables example:
134
+
135
+ ```yaml
136
+ target: chart
137
+ 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
+ chart:
153
+ type: stacked_bar
154
+ title: LLM costs by model
155
+ x:
156
+ field: model
157
+ y:
158
+ - field: input_cost
159
+ - field: output_cost
160
+ - field: cached_cost
161
+ query:
162
+ resource: model_usage
163
+ select:
164
+ - field: model
165
+ - agg: sum
166
+ field: input_tokens
167
+ as: input_tokens
168
+ - agg: sum
169
+ field: output_tokens
170
+ as: output_tokens
171
+ - agg: sum
172
+ field: cached_tokens
173
+ as: cached_tokens
174
+ group_by:
175
+ - model
176
+ calcs:
177
+ - calc: input_tokens / 1000000 * lookup($variables.token_prices_per_1m.input, model, 0)
178
+ as: input_cost
179
+ - calc: output_tokens / 1000000 * lookup($variables.token_prices_per_1m.output, model, 0)
180
+ as: output_cost
181
+ - calc: cached_tokens / 1000000 * lookup($variables.token_prices_per_1m.cached, model, 0)
182
+ as: cached_cost
95
183
  ```
96
184
 
97
185
  ## Runtime Structure
@@ -1,16 +1,13 @@
1
1
  import type {
2
2
  DashboardConfig,
3
- DashboardGroupConfig,
3
+ EditableDashboardGroupConfig,
4
+ EditableDashboardWidgetConfig,
4
5
  DashboardGroupMoveDirection,
5
6
  DashboardWidgetConfig,
7
+ DashboardWidgetConfigValidationError,
6
8
  DashboardWidgetMoveDirection,
7
9
  } from '../model/dashboard.types.js'
8
10
 
9
- export type DashboardWidgetConfigValidationError = {
10
- field: string
11
- message: string
12
- }
13
-
14
11
  export type DashboardResponse = {
15
12
  id: string
16
13
  slug: string
@@ -143,6 +140,10 @@ export const dashboardApi = {
143
140
  return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
144
141
  },
145
142
 
143
+ async setDashboardConfig(slug: string, config: unknown): Promise<DashboardResponse> {
144
+ return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
145
+ },
146
+
146
147
  async addDashboardGroup(slug: string): Promise<DashboardResponse> {
147
148
  return callDashboardApi('/adminapi/v1/dashboard/add_dashboard_group', { slug })
148
149
  },
@@ -166,7 +167,7 @@ export const dashboardApi = {
166
167
  })
167
168
  },
168
169
 
169
- async setDashboardGroupConfig(slug: string, groupId: string, config: DashboardGroupConfig): Promise<DashboardResponse> {
170
+ async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardResponse> {
170
171
  return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
171
172
  slug,
172
173
  groupId,
@@ -200,7 +201,7 @@ export const dashboardApi = {
200
201
  })
201
202
  },
202
203
 
203
- async setWidgetConfig(slug: string, widgetId: string, config: DashboardWidgetConfig): Promise<DashboardResponse> {
204
+ async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardResponse> {
204
205
  return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
205
206
  slug,
206
207
  widgetId,
@@ -6,18 +6,34 @@ export type DashboardConfig = {
6
6
  widgets: DashboardWidgetConfig[]
7
7
  }
8
8
 
9
+ export type JsonValue =
10
+ | string
11
+ | number
12
+ | boolean
13
+ | null
14
+ | JsonValue[]
15
+ | { [key: string]: JsonValue }
16
+
17
+ export type DashboardVariables = Record<string, JsonValue>
18
+
9
19
  export type DashboardGroupConfig = {
10
20
  id: string
11
21
  label: string
12
22
  order: number
13
23
  }
14
24
 
25
+ export type EditableDashboardGroupConfig = Pick<DashboardGroupConfig, 'label'>
26
+
15
27
  export type DashboardGroupMoveDirection = 'up' | 'down'
16
28
  export type DashboardWidgetMoveDirection = 'up' | 'down'
17
29
  export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card'
18
30
  export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full'
31
+ export type DashboardWidgetConfigValidationError = {
32
+ field: string
33
+ message: string
34
+ }
19
35
  export type QueryAggregateOperation = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'
20
- export type TimeGrain = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
36
+ export type TimeGrain = 'day' | 'week' | 'month' | 'year'
21
37
  export type ValueFormat =
22
38
  | 'number'
23
39
  | 'compact_number'
@@ -30,8 +46,8 @@ export type ValueFormat =
30
46
  export type WidgetLayout = {
31
47
  size?: DashboardWidgetSize
32
48
  width?: number
33
- minWidth?: number
34
- maxWidth?: number | null
49
+ min_width?: number
50
+ max_width?: number | null
35
51
  height?: number
36
52
  }
37
53
 
@@ -39,11 +55,12 @@ export type WidgetBaseConfig = {
39
55
  id: string
40
56
  group_id: string
41
57
  label?: string
58
+ variables?: DashboardVariables
42
59
  size?: DashboardWidgetSize
43
60
  width?: number
44
61
  height?: number
45
- minWidth?: number
46
- maxWidth?: number | null
62
+ min_width?: number
63
+ max_width?: number | null
47
64
  order: number
48
65
  }
49
66
 
@@ -53,16 +70,16 @@ export type FilterExpression =
53
70
  | Array<FilterExpression>
54
71
  | {
55
72
  field: string
56
- eq?: unknown
57
- neq?: unknown
58
- gt?: unknown
59
- gte?: unknown
60
- lt?: unknown
61
- lte?: unknown
62
- in?: unknown[]
63
- not_in?: unknown[]
64
- like?: unknown
65
- ilike?: unknown
73
+ eq?: JsonValue
74
+ neq?: JsonValue
75
+ gt?: JsonValue
76
+ gte?: JsonValue
77
+ lt?: JsonValue
78
+ lte?: JsonValue
79
+ in?: JsonValue[]
80
+ not_in?: JsonValue[]
81
+ like?: JsonValue
82
+ ilike?: JsonValue
66
83
  }
67
84
 
68
85
  export type QueryFieldSelectItem = {
@@ -103,30 +120,31 @@ export type QueryConfig = {
103
120
  resource: string
104
121
  select?: QuerySelectItem[]
105
122
  filters?: FilterExpression
106
- groupBy?: QueryGroupByItem[]
107
- orderBy?: QueryOrderByItem[]
123
+ group_by?: QueryGroupByItem[]
124
+ order_by?: QueryOrderByItem[]
108
125
  limit?: number
109
126
  offset?: number
110
- timeSeries?: {
127
+ time_series?: {
111
128
  field: string
112
129
  grain: TimeGrain
113
130
  timezone?: string
114
131
  }
115
132
  period?: {
116
133
  field: string
117
- gte?: unknown
118
- lt?: unknown
134
+ gte?: JsonValue
135
+ lt?: JsonValue
119
136
  }
120
137
  bucket?: {
121
138
  field: string
122
139
  buckets: Array<{ label: string, min?: number, max?: number }>
123
140
  }
124
141
  calcs?: QueryCalcSelectItem[]
125
- formatting?: Record<string, unknown>
142
+ formatting?: Record<string, JsonValue>
126
143
  }
127
144
 
128
145
  export type FunnelQueryConfig = {
129
146
  steps: FunnelQueryStep[]
147
+ calcs?: QueryCalcSelectItem[]
130
148
  }
131
149
 
132
150
  export type FunnelQueryStep = {
@@ -145,7 +163,7 @@ export type FieldRef = string | {
145
163
  export type TableViewConfig = {
146
164
  columns?: FieldRef[]
147
165
  pagination?: boolean
148
- pageSize?: number
166
+ page_size?: number
149
167
  }
150
168
 
151
169
  export type KpiCardViewConfig = {
@@ -160,8 +178,8 @@ export type KpiCardViewConfig = {
160
178
  text?: string
161
179
  field?: string
162
180
  }
163
- comparison?: unknown
164
- sparkline?: unknown
181
+ comparison?: JsonValue
182
+ sparkline?: JsonValue
165
183
  }
166
184
 
167
185
  export type GaugeCardViewConfig = {
@@ -178,9 +196,9 @@ export type GaugeCardViewConfig = {
178
196
  label?: string
179
197
  }
180
198
  progress?: {
181
- valueField: string
182
- targetValue?: number
183
- targetField?: string
199
+ value_field: string
200
+ target_value?: number
201
+ target_field?: string
184
202
  format?: ValueFormat
185
203
  }
186
204
  color?: string
@@ -239,6 +257,14 @@ export type DashboardWidgetConfig =
239
257
  | GaugeCardWidgetConfig
240
258
  | PivotTableWidgetConfig
241
259
 
260
+ export type EditableDashboardWidgetConfig =
261
+ | Omit<EmptyWidgetConfig, 'id' | 'group_id' | 'order'>
262
+ | Omit<TableWidgetConfig, 'id' | 'group_id' | 'order'>
263
+ | Omit<ChartDashboardWidgetConfig, 'id' | 'group_id' | 'order'>
264
+ | Omit<KpiCardWidgetConfig, 'id' | 'group_id' | 'order'>
265
+ | Omit<GaugeCardWidgetConfig, 'id' | 'group_id' | 'order'>
266
+ | Omit<PivotTableWidgetConfig, 'id' | 'group_id' | 'order'>
267
+
242
268
  export type DashboardWidgetTableData = {
243
269
  kind?: 'table'
244
270
  columns: string[]
@@ -266,252 +292,19 @@ export type DashboardWidgetAggregateData = {
266
292
 
267
293
  export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
268
294
 
269
- export function normalizeDashboardConfig(config: unknown): DashboardConfig {
270
- const value = isRecord(config) ? config : {}
271
-
272
- return {
273
- version: typeof value.version === 'number' ? value.version : 1,
274
- groups: Array.isArray(value.groups) ? (value.groups as DashboardGroupConfig[]) : [],
275
- widgets: Array.isArray(value.widgets)
276
- ? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget) as DashboardWidgetConfig)
277
- : [],
278
- }
279
- }
280
-
281
- export function normalizeDashboardWidgetConfig(config: unknown) {
282
- if (!isRecord(config)) {
283
- return config
284
- }
285
-
286
- const normalized: Record<string, unknown> = { ...config }
287
- normalizeWidgetLayoutConfig(normalized)
288
-
289
- if (normalized.query !== undefined) {
290
- normalized.query = normalizeQueryConfig(normalized.query)
291
- }
292
-
293
- if (normalized.table !== undefined) {
294
- normalized.table = normalizeTableConfig(normalized.table)
295
- }
296
-
297
- if (normalized.card !== undefined) {
298
- normalized.card = normalizeCardConfig(normalized.card)
299
- }
300
-
301
- if (normalized.pivot !== undefined) {
302
- normalized.pivot = normalizePivotConfig(normalized.pivot)
303
- }
304
-
305
- const target = normalizeDashboardWidgetTarget(normalized.target)
306
-
307
- if (target !== undefined) {
308
- normalized.target = target
309
- }
310
-
311
- return normalized
312
- }
313
-
314
- export function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig) {
315
- const serialized: Record<string, unknown> = { ...widget }
316
-
317
- if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
318
- serialized.min_width = widget.minWidth
319
- delete serialized.minWidth
320
- }
321
-
322
- if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
323
- serialized.max_width = widget.maxWidth
324
- delete serialized.maxWidth
325
- }
326
-
327
- if ('query' in widget) {
328
- serialized.query = serializeQueryConfigForEditor(widget.query)
329
- }
330
-
331
- if ('table' in widget && widget.table !== undefined) {
332
- serialized.table = serializeTableConfigForEditor(widget.table)
333
- }
334
-
335
- if ('card' in widget && widget.card !== undefined) {
336
- serialized.card = serializeCardConfigForEditor(widget.card)
337
- }
295
+ export function serializeDashboardWidgetConfigForEditor(
296
+ widget: DashboardWidgetConfig,
297
+ ): unknown {
298
+ const {
299
+ id: _id,
300
+ group_id: _groupId,
301
+ order: _order,
302
+ ...editableWidget
303
+ } = widget
338
304
 
339
- if ('pivot' in widget && widget.pivot !== undefined) {
340
- serialized.pivot = serializePivotConfigForEditor(widget.pivot)
341
- }
342
-
343
- return serialized
305
+ return editableWidget
344
306
  }
345
307
 
346
308
  export function getFieldRefField(value: FieldRef | undefined) {
347
309
  return typeof value === 'string' ? value : value?.field
348
310
  }
349
-
350
- export function getFieldRefLabel(value: FieldRef | undefined) {
351
- return typeof value === 'string' ? value : value?.label
352
- }
353
-
354
- function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
355
- switch (value) {
356
- case 'empty':
357
- case 'table':
358
- case 'chart':
359
- case 'kpi_card':
360
- case 'pivot_table':
361
- case 'gauge_card':
362
- return value
363
- default:
364
- return undefined
365
- }
366
- }
367
-
368
- function normalizeWidgetLayoutConfig(value: Record<string, unknown>) {
369
- if (value.min_width !== undefined) {
370
- value.minWidth = value.min_width
371
- }
372
-
373
- if (value.max_width !== undefined) {
374
- value.maxWidth = value.max_width
375
- }
376
- }
377
-
378
- function normalizeQueryConfig(value: unknown): unknown {
379
- if (!isRecord(value)) {
380
- return value
381
- }
382
-
383
- if (Array.isArray(value.steps)) {
384
- return {
385
- steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
386
- }
387
- }
388
-
389
- return {
390
- ...value,
391
- ...(Array.isArray(value.group_by) ? { groupBy: value.group_by } : {}),
392
- ...(Array.isArray(value.order_by) ? { orderBy: value.order_by } : {}),
393
- ...(value.time_series !== undefined ? { timeSeries: value.time_series } : {}),
394
- }
395
- }
396
-
397
- function normalizeFunnelQueryStep(value: unknown) {
398
- if (!isRecord(value)) {
399
- return value
400
- }
401
-
402
- return {
403
- ...value,
404
- ...(typeof value.resource_id === 'string' ? { resource: value.resource_id } : {}),
405
- }
406
- }
407
-
408
- function normalizeTableConfig(value: unknown) {
409
- if (!isRecord(value)) {
410
- return value
411
- }
412
-
413
- return {
414
- ...value,
415
- ...(value.page_size !== undefined ? { pageSize: value.page_size } : {}),
416
- }
417
- }
418
-
419
- function normalizeCardConfig(value: unknown): unknown {
420
- if (!isRecord(value)) {
421
- return value
422
- }
423
-
424
- const normalized = { ...value }
425
-
426
- if (isRecord(normalized.progress)) {
427
- normalized.progress = {
428
- ...normalized.progress,
429
- ...(normalized.progress.value_field !== undefined ? { valueField: normalized.progress.value_field } : {}),
430
- ...(normalized.progress.target_value !== undefined ? { targetValue: normalized.progress.target_value } : {}),
431
- ...(normalized.progress.target_field !== undefined ? { targetField: normalized.progress.target_field } : {}),
432
- }
433
- }
434
-
435
- if (isRecord(normalized.comparison)) {
436
- normalized.comparison = {
437
- ...normalized.comparison,
438
- ...(normalized.comparison.positive_is_good !== undefined ? { positiveIsGood: normalized.comparison.positive_is_good } : {}),
439
- }
440
- }
441
-
442
- return normalized
443
- }
444
-
445
- function normalizePivotConfig(value: unknown): unknown {
446
- return value
447
- }
448
-
449
- function serializeQueryConfigForEditor(value: QueryConfig | FunnelQueryConfig) {
450
- if ('steps' in value) {
451
- return {
452
- steps: value.steps.map((step) => ({
453
- ...step,
454
- resource_id: step.resource,
455
- resource: undefined,
456
- })).map((step) => removeUndefinedFields(step)),
457
- }
458
- }
459
-
460
- return removeUndefinedFields({
461
- ...value,
462
- group_by: value.groupBy,
463
- groupBy: undefined,
464
- order_by: value.orderBy,
465
- orderBy: undefined,
466
- time_series: value.timeSeries,
467
- timeSeries: undefined,
468
- })
469
- }
470
-
471
- function serializeTableConfigForEditor(value: TableViewConfig) {
472
- return removeUndefinedFields({
473
- ...value,
474
- page_size: value.pageSize,
475
- pageSize: undefined,
476
- })
477
- }
478
-
479
- function serializeCardConfigForEditor(value: KpiCardViewConfig | GaugeCardViewConfig) {
480
- const serialized: Record<string, unknown> = { ...value }
481
-
482
- if (isRecord(serialized.progress)) {
483
- serialized.progress = removeUndefinedFields({
484
- ...serialized.progress,
485
- value_field: serialized.progress.valueField,
486
- valueField: undefined,
487
- target_value: serialized.progress.targetValue,
488
- targetValue: undefined,
489
- target_field: serialized.progress.targetField,
490
- targetField: undefined,
491
- })
492
- }
493
-
494
- if (isRecord(serialized.comparison)) {
495
- serialized.comparison = removeUndefinedFields({
496
- ...serialized.comparison,
497
- positive_is_good: serialized.comparison.positiveIsGood,
498
- positiveIsGood: undefined,
499
- })
500
- }
501
-
502
- return removeUndefinedFields(serialized)
503
- }
504
-
505
- function serializePivotConfigForEditor(value: PivotTableViewConfig) {
506
- return value
507
- }
508
-
509
- function removeUndefinedFields<T extends Record<string, unknown>>(value: T) {
510
- return Object.fromEntries(
511
- Object.entries(value).filter(([, item]) => item !== undefined),
512
- )
513
- }
514
-
515
- function isRecord(value: unknown): value is Record<string, any> {
516
- return typeof value === 'object' && value !== null
517
- }
@@ -0,0 +1,5 @@
1
+ export const DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX = '/opentopic/dashboard-config-updated'
2
+
3
+ export function getDashboardConfigUpdatedTopic(slug: string) {
4
+ return `${DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX}/${slug}`
5
+ }
@@ -112,8 +112,8 @@
112
112
  :layout="{
113
113
  size: widget.size,
114
114
  width: widget.width,
115
- minWidth: widget.minWidth,
116
- maxWidth: widget.maxWidth,
115
+ min_width: widget.min_width,
116
+ max_width: widget.max_width,
117
117
  height: widget.height,
118
118
  }"
119
119
  @edit="emit('edit-widget', widget)"