@adminforth/dashboard 1.4.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.
- package/README.md +23 -4
- package/custom/api/dashboardApi.ts +6 -9
- package/custom/model/dashboard.types.ts +60 -275
- package/custom/model/dashboardTopics.ts +5 -0
- package/custom/runtime/DashboardGroup.vue +2 -2
- package/custom/runtime/DashboardPage.vue +17 -7
- package/custom/runtime/DashboardRuntime.vue +20 -8
- package/custom/runtime/WidgetRenderer.vue +1 -2
- package/custom/runtime/WidgetShell.vue +3 -3
- package/custom/skills/adminforth-dashboard/SKILL.md +2 -2
- package/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
- package/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
- package/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
- package/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
- package/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
- package/custom/widgets/chart/ChartWidget.vue +4 -15
- package/{dist/custom/widgets/chart/funnel → custom/widgets/chart}/FunnelChart.vue +80 -78
- package/{dist/custom/widgets/chart/line → custom/widgets/chart}/LineChart.vue +2 -2
- package/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
- package/{dist/custom/widgets/chart/stacked-bar → custom/widgets/chart}/StackedBarChart.vue +97 -95
- package/custom/widgets/chart/chart.types.ts +0 -28
- package/dist/custom/api/dashboardApi.d.ts +4 -8
- package/dist/custom/api/dashboardApi.ts +6 -9
- package/dist/custom/model/dashboard.types.d.ts +38 -32
- package/dist/custom/model/dashboard.types.js +2 -155
- package/dist/custom/model/dashboard.types.ts +60 -275
- package/dist/custom/model/dashboardTopics.d.ts +2 -0
- package/dist/custom/model/dashboardTopics.js +8 -0
- package/dist/custom/model/dashboardTopics.ts +5 -0
- package/dist/custom/queries/useDashboardConfig.d.ts +96 -96
- package/dist/custom/queries/useWidgetData.d.ts +96 -96
- package/dist/custom/runtime/DashboardGroup.vue +2 -2
- package/dist/custom/runtime/DashboardPage.vue +17 -7
- package/dist/custom/runtime/DashboardRuntime.vue +20 -8
- package/dist/custom/runtime/WidgetRenderer.vue +1 -2
- package/dist/custom/runtime/WidgetShell.vue +3 -3
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +2 -2
- package/dist/custom/widgets/{gauge-card/GaugeCardWidget.vue → GaugeCardWidget.vue} +63 -61
- package/dist/custom/widgets/{kpi-card/KpiCardWidget.vue → KpiCardWidget.vue} +35 -33
- package/dist/custom/widgets/{pivot-table/PivotTableWidget.vue → PivotTableWidget.vue} +71 -68
- package/dist/custom/widgets/{table/TableWidget.vue → TableWidget.vue} +5 -5
- package/dist/custom/widgets/chart/{bar/BarChart.vue → BarChart.vue} +2 -2
- package/dist/custom/widgets/chart/ChartWidget.vue +4 -15
- package/{custom/widgets/chart/funnel → dist/custom/widgets/chart}/FunnelChart.vue +80 -78
- package/{custom/widgets/chart/line → dist/custom/widgets/chart}/LineChart.vue +2 -2
- package/dist/custom/widgets/chart/{pie/PieChart.vue → PieChart.vue} +2 -2
- package/{custom/widgets/chart/stacked-bar → dist/custom/widgets/chart}/StackedBarChart.vue +97 -95
- package/dist/custom/widgets/chart/chart.types.d.ts +0 -2
- package/dist/custom/widgets/chart/chart.types.js +0 -23
- package/dist/custom/widgets/chart/chart.types.ts +0 -28
- package/dist/endpoint/dashboard.d.ts +2 -3
- package/dist/endpoint/dashboard.js +12 -32
- package/dist/endpoint/groups.d.ts +2 -21
- package/dist/endpoint/groups.js +18 -16
- package/dist/endpoint/widgets.d.ts +0 -3
- package/dist/endpoint/widgets.js +27 -74
- package/dist/index.js +1 -3
- package/dist/schema/api.d.ts +2090 -511
- package/dist/schema/api.js +18 -15
- package/dist/schema/widget.d.ts +1003 -250
- package/dist/schema/widget.js +102 -46
- package/dist/services/dashboardConfigService.d.ts +0 -10
- package/dist/services/dashboardConfigService.js +6 -21
- package/dist/services/widgetDataService.js +226 -196
- package/endpoint/dashboard.ts +13 -46
- package/endpoint/groups.ts +25 -42
- package/endpoint/widgets.ts +36 -95
- package/index.ts +0 -3
- package/package.json +3 -3
- package/schema/api.ts +19 -15
- package/schema/widget.ts +113 -52
- package/services/dashboardConfigService.ts +6 -25
- package/services/widgetDataService.ts +304 -229
- package/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
- package/dist/custom/widgets/chart/histogram/HistogramChart.vue +0 -21
- package/dist/services/widgetConfigValidator.d.ts +0 -8
- package/dist/services/widgetConfigValidator.js +0 -27
- package/services/widgetConfigValidator.ts +0 -61
package/README.md
CHANGED
|
@@ -61,16 +61,35 @@ Chart widget types:
|
|
|
61
61
|
type QueryConfig = {
|
|
62
62
|
resource: string
|
|
63
63
|
select?: Array<
|
|
64
|
-
| { field: string; as?: string; grain?: '
|
|
65
|
-
| { agg: 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'; field?: string; as: string; filters?:
|
|
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[] }
|
|
66
66
|
| { calc: string; as: string }
|
|
67
67
|
>
|
|
68
|
-
filters?:
|
|
69
|
-
group_by?: Array<string | { field: string; as?: string; grain?: '
|
|
68
|
+
filters?: DashboardFilter | DashboardFilter[]
|
|
69
|
+
group_by?: Array<string | { field: string; as?: string; grain?: 'day' | 'week' | 'month' | 'year'; timezone?: string }>
|
|
70
70
|
order_by?: Array<{ field: string; direction?: 'asc' | 'desc' }>
|
|
71
71
|
limit?: number
|
|
72
72
|
offset?: number
|
|
73
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 }
|
|
74
93
|
```
|
|
75
94
|
|
|
76
95
|
Step-based chart queries use `steps` and may include `calcs`:
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
DashboardConfig,
|
|
3
|
-
|
|
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,7 +140,7 @@ export const dashboardApi = {
|
|
|
143
140
|
return callDashboardApi('/adminapi/v1/dashboard/get-config', { slug })
|
|
144
141
|
},
|
|
145
142
|
|
|
146
|
-
async setDashboardConfig(slug: string, config:
|
|
143
|
+
async setDashboardConfig(slug: string, config: unknown): Promise<DashboardResponse> {
|
|
147
144
|
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_config', { slug, config })
|
|
148
145
|
},
|
|
149
146
|
|
|
@@ -170,7 +167,7 @@ export const dashboardApi = {
|
|
|
170
167
|
})
|
|
171
168
|
},
|
|
172
169
|
|
|
173
|
-
async setDashboardGroupConfig(slug: string, groupId: string, config:
|
|
170
|
+
async setDashboardGroupConfig(slug: string, groupId: string, config: EditableDashboardGroupConfig): Promise<DashboardResponse> {
|
|
174
171
|
return callDashboardApi('/adminapi/v1/dashboard/set_dashboard_group_config', {
|
|
175
172
|
slug,
|
|
176
173
|
groupId,
|
|
@@ -204,7 +201,7 @@ export const dashboardApi = {
|
|
|
204
201
|
})
|
|
205
202
|
},
|
|
206
203
|
|
|
207
|
-
async setWidgetConfig(slug: string, widgetId: string, config:
|
|
204
|
+
async setWidgetConfig(slug: string, widgetId: string, config: unknown): Promise<DashboardResponse> {
|
|
208
205
|
return callDashboardApi('/adminapi/v1/dashboard/set_widget_config', {
|
|
209
206
|
slug,
|
|
210
207
|
widgetId,
|
|
@@ -6,7 +6,15 @@ export type DashboardConfig = {
|
|
|
6
6
|
widgets: DashboardWidgetConfig[]
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export type
|
|
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>
|
|
10
18
|
|
|
11
19
|
export type DashboardGroupConfig = {
|
|
12
20
|
id: string
|
|
@@ -14,12 +22,18 @@ export type DashboardGroupConfig = {
|
|
|
14
22
|
order: number
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
export type EditableDashboardGroupConfig = Pick<DashboardGroupConfig, 'label'>
|
|
26
|
+
|
|
17
27
|
export type DashboardGroupMoveDirection = 'up' | 'down'
|
|
18
28
|
export type DashboardWidgetMoveDirection = 'up' | 'down'
|
|
19
29
|
export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card'
|
|
20
30
|
export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full'
|
|
31
|
+
export type DashboardWidgetConfigValidationError = {
|
|
32
|
+
field: string
|
|
33
|
+
message: string
|
|
34
|
+
}
|
|
21
35
|
export type QueryAggregateOperation = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'
|
|
22
|
-
export type TimeGrain = '
|
|
36
|
+
export type TimeGrain = 'day' | 'week' | 'month' | 'year'
|
|
23
37
|
export type ValueFormat =
|
|
24
38
|
| 'number'
|
|
25
39
|
| 'compact_number'
|
|
@@ -32,8 +46,8 @@ export type ValueFormat =
|
|
|
32
46
|
export type WidgetLayout = {
|
|
33
47
|
size?: DashboardWidgetSize
|
|
34
48
|
width?: number
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
min_width?: number
|
|
50
|
+
max_width?: number | null
|
|
37
51
|
height?: number
|
|
38
52
|
}
|
|
39
53
|
|
|
@@ -45,8 +59,8 @@ export type WidgetBaseConfig = {
|
|
|
45
59
|
size?: DashboardWidgetSize
|
|
46
60
|
width?: number
|
|
47
61
|
height?: number
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
min_width?: number
|
|
63
|
+
max_width?: number | null
|
|
50
64
|
order: number
|
|
51
65
|
}
|
|
52
66
|
|
|
@@ -56,16 +70,16 @@ export type FilterExpression =
|
|
|
56
70
|
| Array<FilterExpression>
|
|
57
71
|
| {
|
|
58
72
|
field: string
|
|
59
|
-
eq?:
|
|
60
|
-
neq?:
|
|
61
|
-
gt?:
|
|
62
|
-
gte?:
|
|
63
|
-
lt?:
|
|
64
|
-
lte?:
|
|
65
|
-
in?:
|
|
66
|
-
not_in?:
|
|
67
|
-
like?:
|
|
68
|
-
ilike?:
|
|
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
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
export type QueryFieldSelectItem = {
|
|
@@ -106,26 +120,26 @@ export type QueryConfig = {
|
|
|
106
120
|
resource: string
|
|
107
121
|
select?: QuerySelectItem[]
|
|
108
122
|
filters?: FilterExpression
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
group_by?: QueryGroupByItem[]
|
|
124
|
+
order_by?: QueryOrderByItem[]
|
|
111
125
|
limit?: number
|
|
112
126
|
offset?: number
|
|
113
|
-
|
|
127
|
+
time_series?: {
|
|
114
128
|
field: string
|
|
115
129
|
grain: TimeGrain
|
|
116
130
|
timezone?: string
|
|
117
131
|
}
|
|
118
132
|
period?: {
|
|
119
133
|
field: string
|
|
120
|
-
gte?:
|
|
121
|
-
lt?:
|
|
134
|
+
gte?: JsonValue
|
|
135
|
+
lt?: JsonValue
|
|
122
136
|
}
|
|
123
137
|
bucket?: {
|
|
124
138
|
field: string
|
|
125
139
|
buckets: Array<{ label: string, min?: number, max?: number }>
|
|
126
140
|
}
|
|
127
141
|
calcs?: QueryCalcSelectItem[]
|
|
128
|
-
formatting?: Record<string,
|
|
142
|
+
formatting?: Record<string, JsonValue>
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
export type FunnelQueryConfig = {
|
|
@@ -149,7 +163,7 @@ export type FieldRef = string | {
|
|
|
149
163
|
export type TableViewConfig = {
|
|
150
164
|
columns?: FieldRef[]
|
|
151
165
|
pagination?: boolean
|
|
152
|
-
|
|
166
|
+
page_size?: number
|
|
153
167
|
}
|
|
154
168
|
|
|
155
169
|
export type KpiCardViewConfig = {
|
|
@@ -164,8 +178,8 @@ export type KpiCardViewConfig = {
|
|
|
164
178
|
text?: string
|
|
165
179
|
field?: string
|
|
166
180
|
}
|
|
167
|
-
comparison?:
|
|
168
|
-
sparkline?:
|
|
181
|
+
comparison?: JsonValue
|
|
182
|
+
sparkline?: JsonValue
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
export type GaugeCardViewConfig = {
|
|
@@ -182,9 +196,9 @@ export type GaugeCardViewConfig = {
|
|
|
182
196
|
label?: string
|
|
183
197
|
}
|
|
184
198
|
progress?: {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
value_field: string
|
|
200
|
+
target_value?: number
|
|
201
|
+
target_field?: string
|
|
188
202
|
format?: ValueFormat
|
|
189
203
|
}
|
|
190
204
|
color?: string
|
|
@@ -243,6 +257,14 @@ export type DashboardWidgetConfig =
|
|
|
243
257
|
| GaugeCardWidgetConfig
|
|
244
258
|
| PivotTableWidgetConfig
|
|
245
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
|
+
|
|
246
268
|
export type DashboardWidgetTableData = {
|
|
247
269
|
kind?: 'table'
|
|
248
270
|
columns: string[]
|
|
@@ -270,256 +292,19 @@ export type DashboardWidgetAggregateData = {
|
|
|
270
292
|
|
|
271
293
|
export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
|
|
272
294
|
|
|
273
|
-
export function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function normalizeDashboardWidgetConfig(config: unknown) {
|
|
286
|
-
if (!isRecord(config)) {
|
|
287
|
-
return config
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const normalized: Record<string, unknown> = { ...config }
|
|
291
|
-
normalizeWidgetLayoutConfig(normalized)
|
|
292
|
-
|
|
293
|
-
if (normalized.query !== undefined) {
|
|
294
|
-
normalized.query = normalizeQueryConfig(normalized.query)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (normalized.table !== undefined) {
|
|
298
|
-
normalized.table = normalizeTableConfig(normalized.table)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (normalized.card !== undefined) {
|
|
302
|
-
normalized.card = normalizeCardConfig(normalized.card)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (normalized.pivot !== undefined) {
|
|
306
|
-
normalized.pivot = normalizePivotConfig(normalized.pivot)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const target = normalizeDashboardWidgetTarget(normalized.target)
|
|
310
|
-
|
|
311
|
-
if (target !== undefined) {
|
|
312
|
-
normalized.target = target
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return normalized
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig) {
|
|
319
|
-
const serialized: Record<string, unknown> = { ...widget }
|
|
320
|
-
|
|
321
|
-
if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
|
|
322
|
-
serialized.min_width = widget.minWidth
|
|
323
|
-
delete serialized.minWidth
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
|
|
327
|
-
serialized.max_width = widget.maxWidth
|
|
328
|
-
delete serialized.maxWidth
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if ('query' in widget) {
|
|
332
|
-
serialized.query = serializeQueryConfigForEditor(widget.query)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if ('table' in widget && widget.table !== undefined) {
|
|
336
|
-
serialized.table = serializeTableConfigForEditor(widget.table)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
if ('card' in widget && widget.card !== undefined) {
|
|
340
|
-
serialized.card = serializeCardConfigForEditor(widget.card)
|
|
341
|
-
}
|
|
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
|
|
342
304
|
|
|
343
|
-
|
|
344
|
-
serialized.pivot = serializePivotConfigForEditor(widget.pivot)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return serialized
|
|
305
|
+
return editableWidget
|
|
348
306
|
}
|
|
349
307
|
|
|
350
308
|
export function getFieldRefField(value: FieldRef | undefined) {
|
|
351
309
|
return typeof value === 'string' ? value : value?.field
|
|
352
310
|
}
|
|
353
|
-
|
|
354
|
-
export function getFieldRefLabel(value: FieldRef | undefined) {
|
|
355
|
-
return typeof value === 'string' ? value : value?.label
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
|
|
359
|
-
switch (value) {
|
|
360
|
-
case 'empty':
|
|
361
|
-
case 'table':
|
|
362
|
-
case 'chart':
|
|
363
|
-
case 'kpi_card':
|
|
364
|
-
case 'pivot_table':
|
|
365
|
-
case 'gauge_card':
|
|
366
|
-
return value
|
|
367
|
-
default:
|
|
368
|
-
return undefined
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function normalizeWidgetLayoutConfig(value: Record<string, unknown>) {
|
|
373
|
-
if (value.min_width !== undefined) {
|
|
374
|
-
value.minWidth = value.min_width
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
if (value.max_width !== undefined) {
|
|
378
|
-
value.maxWidth = value.max_width
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
function normalizeQueryConfig(value: unknown): unknown {
|
|
383
|
-
if (!isRecord(value)) {
|
|
384
|
-
return value
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
if (Array.isArray(value.steps)) {
|
|
388
|
-
return removeUndefinedFields({
|
|
389
|
-
steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
|
|
390
|
-
calcs: Array.isArray(value.calcs) ? value.calcs as QueryCalcSelectItem[] : undefined,
|
|
391
|
-
})
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
...value,
|
|
396
|
-
...(Array.isArray(value.group_by) ? { groupBy: value.group_by } : {}),
|
|
397
|
-
...(Array.isArray(value.order_by) ? { orderBy: value.order_by } : {}),
|
|
398
|
-
...(value.time_series !== undefined ? { timeSeries: value.time_series } : {}),
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function normalizeFunnelQueryStep(value: unknown) {
|
|
403
|
-
if (!isRecord(value)) {
|
|
404
|
-
return value
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const { resource_id, ...rest } = value
|
|
408
|
-
|
|
409
|
-
return removeUndefinedFields({
|
|
410
|
-
...rest,
|
|
411
|
-
resource: typeof resource_id === 'string' ? resource_id : rest.resource,
|
|
412
|
-
})
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function normalizeTableConfig(value: unknown) {
|
|
416
|
-
if (!isRecord(value)) {
|
|
417
|
-
return value
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
...value,
|
|
422
|
-
...(value.page_size !== undefined ? { pageSize: value.page_size } : {}),
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function normalizeCardConfig(value: unknown): unknown {
|
|
427
|
-
if (!isRecord(value)) {
|
|
428
|
-
return value
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const normalized = { ...value }
|
|
432
|
-
|
|
433
|
-
if (isRecord(normalized.progress)) {
|
|
434
|
-
normalized.progress = {
|
|
435
|
-
...normalized.progress,
|
|
436
|
-
...(normalized.progress.value_field !== undefined ? { valueField: normalized.progress.value_field } : {}),
|
|
437
|
-
...(normalized.progress.target_value !== undefined ? { targetValue: normalized.progress.target_value } : {}),
|
|
438
|
-
...(normalized.progress.target_field !== undefined ? { targetField: normalized.progress.target_field } : {}),
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (isRecord(normalized.comparison)) {
|
|
443
|
-
normalized.comparison = {
|
|
444
|
-
...normalized.comparison,
|
|
445
|
-
...(normalized.comparison.positive_is_good !== undefined ? { positiveIsGood: normalized.comparison.positive_is_good } : {}),
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
return normalized
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function normalizePivotConfig(value: unknown): unknown {
|
|
453
|
-
return value
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function serializeQueryConfigForEditor(value: QueryConfig | FunnelQueryConfig) {
|
|
457
|
-
if ('steps' in value) {
|
|
458
|
-
return removeUndefinedFields({
|
|
459
|
-
steps: value.steps.map((step) => ({
|
|
460
|
-
...step,
|
|
461
|
-
resource_id: step.resource,
|
|
462
|
-
resource: undefined,
|
|
463
|
-
})).map((step) => removeUndefinedFields(step)),
|
|
464
|
-
calcs: value.calcs,
|
|
465
|
-
})
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return removeUndefinedFields({
|
|
469
|
-
...value,
|
|
470
|
-
group_by: value.groupBy,
|
|
471
|
-
groupBy: undefined,
|
|
472
|
-
order_by: value.orderBy,
|
|
473
|
-
orderBy: undefined,
|
|
474
|
-
time_series: value.timeSeries,
|
|
475
|
-
timeSeries: undefined,
|
|
476
|
-
})
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function serializeTableConfigForEditor(value: TableViewConfig) {
|
|
480
|
-
return removeUndefinedFields({
|
|
481
|
-
...value,
|
|
482
|
-
page_size: value.pageSize,
|
|
483
|
-
pageSize: undefined,
|
|
484
|
-
})
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function serializeCardConfigForEditor(value: KpiCardViewConfig | GaugeCardViewConfig) {
|
|
488
|
-
const serialized: Record<string, unknown> = { ...value }
|
|
489
|
-
|
|
490
|
-
if (isRecord(serialized.progress)) {
|
|
491
|
-
serialized.progress = removeUndefinedFields({
|
|
492
|
-
...serialized.progress,
|
|
493
|
-
value_field: serialized.progress.valueField,
|
|
494
|
-
valueField: undefined,
|
|
495
|
-
target_value: serialized.progress.targetValue,
|
|
496
|
-
targetValue: undefined,
|
|
497
|
-
target_field: serialized.progress.targetField,
|
|
498
|
-
targetField: undefined,
|
|
499
|
-
})
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (isRecord(serialized.comparison)) {
|
|
503
|
-
serialized.comparison = removeUndefinedFields({
|
|
504
|
-
...serialized.comparison,
|
|
505
|
-
positive_is_good: serialized.comparison.positiveIsGood,
|
|
506
|
-
positiveIsGood: undefined,
|
|
507
|
-
})
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return removeUndefinedFields(serialized)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function serializePivotConfigForEditor(value: PivotTableViewConfig) {
|
|
514
|
-
return value
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
function removeUndefinedFields<T extends Record<string, unknown>>(value: T) {
|
|
518
|
-
return Object.fromEntries(
|
|
519
|
-
Object.entries(value).filter(([, item]) => item !== undefined),
|
|
520
|
-
)
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function isRecord(value: unknown): value is Record<string, any> {
|
|
524
|
-
return typeof value === 'object' && value !== null
|
|
525
|
-
}
|
|
@@ -112,8 +112,8 @@
|
|
|
112
112
|
:layout="{
|
|
113
113
|
size: widget.size,
|
|
114
114
|
width: widget.width,
|
|
115
|
-
|
|
116
|
-
|
|
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)"
|
|
@@ -50,20 +50,31 @@ import { useRoute } from 'vue-router'
|
|
|
50
50
|
import { Button } from '@/afcl'
|
|
51
51
|
import { useCoreStore } from '@/stores/core'
|
|
52
52
|
import websocket from '@/websocket'
|
|
53
|
+
import { getDashboardConfigUpdatedTopic } from '../model/dashboardTopics.js'
|
|
53
54
|
import DashboardRuntime from './DashboardRuntime.vue'
|
|
54
55
|
import { useDashboardConfig } from '../queries/useDashboardConfig.js'
|
|
55
56
|
|
|
56
57
|
const route = useRoute()
|
|
57
58
|
const coreStore = useCoreStore()
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
function getDashboardSlugFromRouteParam(value: string | string[] | undefined) {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
if (!value[0]) {
|
|
63
|
+
throw new Error('Dashboard slug route param is required')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return value[0]
|
|
67
|
+
}
|
|
61
68
|
|
|
62
|
-
if (
|
|
63
|
-
|
|
69
|
+
if (!value) {
|
|
70
|
+
throw new Error('Dashboard slug route param is required')
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
return
|
|
73
|
+
return value
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const dashboardSlug = computed(() => {
|
|
77
|
+
return getDashboardSlugFromRouteParam(route.params.slug as string | string[] | undefined)
|
|
67
78
|
})
|
|
68
79
|
|
|
69
80
|
const {
|
|
@@ -78,11 +89,10 @@ const isAdmin = computed(() => {
|
|
|
78
89
|
return coreStore.adminUser?.dbUser.role === 'superadmin'
|
|
79
90
|
})
|
|
80
91
|
|
|
81
|
-
const DASHBOARD_CONFIG_UPDATED_TOPIC_PREFIX = '/opentopic/dashboard-config-updated'
|
|
82
92
|
const subscribedTopic = ref<string | null>(null)
|
|
83
93
|
|
|
84
94
|
const dashboardConfigUpdatedTopic = computed(() => {
|
|
85
|
-
return
|
|
95
|
+
return getDashboardConfigUpdatedTopic(dashboardSlug.value)
|
|
86
96
|
})
|
|
87
97
|
|
|
88
98
|
function handleDashboardConfigUpdated(data: { slug?: string; revision?: number }) {
|
|
@@ -207,6 +207,8 @@ import { DashboardApiError, dashboardApi, type DashboardResponse } from '../api/
|
|
|
207
207
|
import type {
|
|
208
208
|
DashboardConfig,
|
|
209
209
|
DashboardGroupConfig,
|
|
210
|
+
EditableDashboardGroupConfig,
|
|
211
|
+
EditableDashboardWidgetConfig,
|
|
210
212
|
DashboardGroupMoveDirection,
|
|
211
213
|
DashboardWidgetConfig,
|
|
212
214
|
DashboardWidgetMoveDirection,
|
|
@@ -252,13 +254,15 @@ const sortedGroups = computed(() => {
|
|
|
252
254
|
return [...draftConfig.value.groups].sort((a, b) => a.order - b.order)
|
|
253
255
|
})
|
|
254
256
|
|
|
255
|
-
|
|
257
|
+
function groupWidgetsByGroupId(widgets: DashboardWidgetConfig[]) {
|
|
256
258
|
const result = new Map<string, DashboardWidgetConfig[]>()
|
|
257
259
|
|
|
258
|
-
for (const widget of
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
for (const widget of widgets) {
|
|
261
|
+
const nextWidgets = result.get(widget.group_id)
|
|
262
|
+
? [...result.get(widget.group_id)!, widget]
|
|
263
|
+
: [widget]
|
|
264
|
+
|
|
265
|
+
result.set(widget.group_id, nextWidgets)
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
for (const [groupId, widgets] of result.entries()) {
|
|
@@ -266,6 +270,10 @@ const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
|
|
|
266
270
|
}
|
|
267
271
|
|
|
268
272
|
return result
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const widgetsByGroupId = computed<Map<string, DashboardWidgetConfig[]>>(() => {
|
|
276
|
+
return groupWidgetsByGroupId(draftConfig.value.widgets as DashboardWidgetConfig[])
|
|
269
277
|
})
|
|
270
278
|
|
|
271
279
|
const visibleGroups = computed(() => {
|
|
@@ -329,8 +337,12 @@ async function removeGroup(groupId: string) {
|
|
|
329
337
|
}
|
|
330
338
|
|
|
331
339
|
function editGroup(group: DashboardGroupConfig) {
|
|
340
|
+
const editableGroupConfig: EditableDashboardGroupConfig = {
|
|
341
|
+
label: group.label,
|
|
342
|
+
}
|
|
343
|
+
|
|
332
344
|
editingGroupId.value = group.id
|
|
333
|
-
groupConfigCode.value = stringifyYaml(
|
|
345
|
+
groupConfigCode.value = stringifyYaml(editableGroupConfig)
|
|
334
346
|
groupConfigError.value = ''
|
|
335
347
|
}
|
|
336
348
|
|
|
@@ -340,7 +352,7 @@ async function saveGroupConfig() {
|
|
|
340
352
|
}
|
|
341
353
|
|
|
342
354
|
try {
|
|
343
|
-
const groupConfig = parseYaml(groupConfigCode.value) as
|
|
355
|
+
const groupConfig = parseYaml(groupConfigCode.value) as EditableDashboardGroupConfig
|
|
344
356
|
|
|
345
357
|
applyDashboardResponse(
|
|
346
358
|
await dashboardApi.setDashboardGroupConfig(
|
|
@@ -402,7 +414,7 @@ async function saveWidgetConfig() {
|
|
|
402
414
|
try {
|
|
403
415
|
widgetConfigError.value = ''
|
|
404
416
|
widgetConfigFieldErrors.value = []
|
|
405
|
-
const widgetConfig = parseYaml(widgetConfigCode.value) as
|
|
417
|
+
const widgetConfig = parseYaml(widgetConfigCode.value) as EditableDashboardWidgetConfig
|
|
406
418
|
|
|
407
419
|
applyDashboardResponse(
|
|
408
420
|
await dashboardApi.setWidgetConfig(
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
<script setup lang="ts">
|
|
27
27
|
import { computed } from 'vue'
|
|
28
28
|
import type { DashboardWidgetConfig } from '../model/dashboard.types.js'
|
|
29
|
-
import { normalizeChartWidgetConfig } from '../widgets/chart/chart.types.js'
|
|
30
29
|
import { getWidgetLabel, getWidgetRegistration } from '../widgets/registry.js'
|
|
31
30
|
|
|
32
31
|
const props = defineProps<{
|
|
@@ -53,7 +52,7 @@ const widgetTitle = computed(() => {
|
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
if (props.widget.target === 'chart') {
|
|
56
|
-
return
|
|
55
|
+
return props.widget.chart.title || 'Untitled chart'
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
return getWidgetLabel(props.widget.target)
|
|
@@ -136,10 +136,10 @@ const widgetLayoutVars = computed<CSSProperties>(() => {
|
|
|
136
136
|
|
|
137
137
|
return {
|
|
138
138
|
'--widget-basis': clampToContainerWidth(fixedWidth ?? basis),
|
|
139
|
-
'--widget-min-width': clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.
|
|
140
|
-
'--widget-max-width': props.layout?.
|
|
139
|
+
'--widget-min-width': clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.min_width) ?? basis),
|
|
140
|
+
'--widget-max-width': props.layout?.max_width === null
|
|
141
141
|
? '100%'
|
|
142
|
-
: clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.
|
|
142
|
+
: clampToContainerWidth(fixedWidth ?? formatWidth(props.layout?.max_width) ?? '100%'),
|
|
143
143
|
height: formatWidth(props.layout?.height ?? DEFAULT_WIDGET_HEIGHT),
|
|
144
144
|
}
|
|
145
145
|
})
|