@adminforth/dashboard 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +47 -39
  2. package/custom/composables/useElementSize.ts +17 -2
  3. package/custom/model/dashboard.types.ts +327 -234
  4. package/custom/skills/adminforth-dashboard/SKILL.md +6 -2
  5. package/custom/widgets/chart/ChartWidget.vue +23 -55
  6. package/custom/widgets/chart/bar/BarChart.vue +20 -12
  7. package/custom/widgets/chart/chart.types.ts +17 -66
  8. package/custom/widgets/chart/chart.utils.ts +11 -0
  9. package/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  10. package/custom/widgets/chart/line/LineChart.vue +23 -15
  11. package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  12. package/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
  13. package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  14. package/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
  15. package/custom/widgets/table/TableWidget.vue +8 -3
  16. package/dist/custom/composables/useElementSize.js +14 -2
  17. package/dist/custom/composables/useElementSize.ts +17 -2
  18. package/dist/custom/model/dashboard.types.d.ts +178 -61
  19. package/dist/custom/model/dashboard.types.js +67 -92
  20. package/dist/custom/model/dashboard.types.ts +327 -234
  21. package/dist/custom/queries/useDashboardConfig.d.ts +832 -66
  22. package/dist/custom/queries/useWidgetData.d.ts +828 -62
  23. package/dist/custom/skills/adminforth-dashboard/SKILL.md +6 -2
  24. package/dist/custom/widgets/chart/ChartWidget.vue +23 -55
  25. package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
  26. package/dist/custom/widgets/chart/chart.types.d.ts +13 -22
  27. package/dist/custom/widgets/chart/chart.types.js +2 -25
  28. package/dist/custom/widgets/chart/chart.types.ts +17 -66
  29. package/dist/custom/widgets/chart/chart.utils.d.ts +1 -0
  30. package/dist/custom/widgets/chart/chart.utils.js +7 -0
  31. package/dist/custom/widgets/chart/chart.utils.ts +11 -0
  32. package/dist/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  33. package/dist/custom/widgets/chart/line/LineChart.vue +23 -15
  34. package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  35. package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
  36. package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  37. package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
  38. package/dist/custom/widgets/table/TableWidget.vue +8 -3
  39. package/dist/endpoint/widgets.js +5 -2
  40. package/dist/schema/api.d.ts +2680 -736
  41. package/dist/schema/widget.d.ts +1588 -476
  42. package/dist/schema/widget.js +205 -139
  43. package/dist/services/widgetConfigValidator.js +16 -40
  44. package/dist/services/widgetDataService.js +359 -82
  45. package/endpoint/dashboard.ts +1 -1
  46. package/endpoint/widgets.ts +5 -2
  47. package/package.json +1 -1
  48. package/schema/widget.ts +222 -139
  49. package/services/widgetConfigValidator.ts +29 -53
  50. package/services/widgetDataService.ts +484 -100
@@ -1,42 +1,5 @@
1
1
  import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js'
2
2
 
3
- export type AggregationOperation = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median'
4
-
5
- export type AggregationRule = {
6
- operation: AggregationOperation
7
- field?: string
8
- }
9
-
10
- export type GroupByRule =
11
- | {
12
- type: 'field'
13
- field: string
14
- }
15
- | {
16
- type: 'date_trunc'
17
- field: string
18
- truncation: 'day' | 'week' | 'month' | 'year'
19
- timezone?: string
20
- }
21
-
22
- export type ResourceWidgetDataSource = {
23
- type: 'resource'
24
- resourceId: string
25
- columns?: string[]
26
- sort?: unknown
27
- filters?: unknown
28
- }
29
-
30
- export type AggregateWidgetDataSource = {
31
- type: 'aggregate'
32
- resourceId: string
33
- aggregations: Record<string, AggregationRule>
34
- groupBy?: GroupByRule
35
- filters?: unknown
36
- }
37
-
38
- export type WidgetDataSource = ResourceWidgetDataSource | AggregateWidgetDataSource
39
-
40
3
  export type DashboardConfig = {
41
4
  version: number
42
5
  groups: DashboardGroupConfig[]
@@ -50,18 +13,19 @@ export type DashboardGroupConfig = {
50
13
  }
51
14
 
52
15
  export type DashboardGroupMoveDirection = 'up' | 'down'
53
-
54
16
  export type DashboardWidgetMoveDirection = 'up' | 'down'
55
-
56
- export type DashboardWidgetTarget =
57
- | 'empty'
58
- | 'table'
59
- | 'chart'
60
- | 'kpi_card'
61
- | 'pivot_table'
62
- | 'gauge_card'
63
-
17
+ export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card'
64
18
  export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full'
19
+ export type QueryAggregateOperation = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median'
20
+ export type TimeGrain = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
21
+ export type ValueFormat =
22
+ | 'number'
23
+ | 'compact_number'
24
+ | 'currency'
25
+ | 'percent'
26
+ | 'percent_delta'
27
+ | 'number_delta'
28
+ | 'currency_delta'
65
29
 
66
30
  export type WidgetLayout = {
67
31
  size?: DashboardWidgetSize
@@ -71,7 +35,7 @@ export type WidgetLayout = {
71
35
  height?: number
72
36
  }
73
37
 
74
- export type DashboardWidgetConfig = {
38
+ export type WidgetBaseConfig = {
75
39
  id: string
76
40
  group_id: string
77
41
  label?: string
@@ -81,15 +45,200 @@ export type DashboardWidgetConfig = {
81
45
  minWidth?: number
82
46
  maxWidth?: number | null
83
47
  order: number
84
- target: DashboardWidgetTarget
85
- dataSource?: WidgetDataSource
86
- chart?: ChartWidgetConfig
87
- table?: unknown
88
- kpi_card?: unknown
89
- pivot_table?: unknown
90
- gauge_card?: unknown
91
48
  }
92
49
 
50
+ export type FilterExpression =
51
+ | { and: FilterExpression[] }
52
+ | { or: FilterExpression[] }
53
+ | Array<FilterExpression>
54
+ | {
55
+ 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
66
+ }
67
+
68
+ export type QueryFieldSelectItem = {
69
+ field: string
70
+ as?: string
71
+ grain?: TimeGrain
72
+ }
73
+
74
+ export type QueryAggregateSelectItem = {
75
+ agg: QueryAggregateOperation
76
+ field?: string
77
+ as: string
78
+ filters?: FilterExpression
79
+ }
80
+
81
+ export type QueryCalcSelectItem = {
82
+ calc: string
83
+ as: string
84
+ }
85
+
86
+ export type QuerySelectItem = QueryFieldSelectItem | QueryAggregateSelectItem | QueryCalcSelectItem
87
+
88
+ export type QueryGroupByItem =
89
+ | string
90
+ | {
91
+ field: string
92
+ as?: string
93
+ grain?: TimeGrain
94
+ timezone?: string
95
+ }
96
+
97
+ export type QueryOrderByItem = {
98
+ field: string
99
+ direction?: 'asc' | 'desc'
100
+ }
101
+
102
+ export type QueryConfig = {
103
+ resource: string
104
+ select?: QuerySelectItem[]
105
+ filters?: FilterExpression
106
+ groupBy?: QueryGroupByItem[]
107
+ orderBy?: QueryOrderByItem[]
108
+ limit?: number
109
+ offset?: number
110
+ timeSeries?: {
111
+ field: string
112
+ grain: TimeGrain
113
+ timezone?: string
114
+ }
115
+ period?: {
116
+ field: string
117
+ gte?: unknown
118
+ lt?: unknown
119
+ }
120
+ bucket?: {
121
+ field: string
122
+ buckets: Array<{ label: string, min?: number, max?: number }>
123
+ }
124
+ calcs?: QueryCalcSelectItem[]
125
+ formatting?: Record<string, unknown>
126
+ }
127
+
128
+ export type FunnelQueryConfig = {
129
+ steps: FunnelQueryStep[]
130
+ }
131
+
132
+ export type FunnelQueryStep = {
133
+ name: string
134
+ resource: string
135
+ metric: QueryAggregateSelectItem
136
+ filters?: FilterExpression
137
+ }
138
+
139
+ export type FieldRef = string | {
140
+ field: string
141
+ label?: string
142
+ format?: ValueFormat
143
+ }
144
+
145
+ export type TableViewConfig = {
146
+ columns?: FieldRef[]
147
+ pagination?: boolean
148
+ pageSize?: number
149
+ }
150
+
151
+ export type KpiCardViewConfig = {
152
+ title?: string
153
+ value: {
154
+ field: string
155
+ format?: ValueFormat
156
+ prefix?: string
157
+ suffix?: string
158
+ }
159
+ subtitle?: {
160
+ text?: string
161
+ field?: string
162
+ }
163
+ comparison?: unknown
164
+ sparkline?: unknown
165
+ }
166
+
167
+ export type GaugeCardViewConfig = {
168
+ title?: string
169
+ value: {
170
+ field: string
171
+ format?: ValueFormat
172
+ prefix?: string
173
+ suffix?: string
174
+ }
175
+ target?: {
176
+ value?: number
177
+ field?: string
178
+ label?: string
179
+ }
180
+ progress?: {
181
+ valueField: string
182
+ targetValue?: number
183
+ targetField?: string
184
+ format?: ValueFormat
185
+ }
186
+ color?: string
187
+ }
188
+
189
+ export type PivotTableViewConfig = {
190
+ rows: FieldRef[]
191
+ columns?: FieldRef[]
192
+ values: Array<{
193
+ field: string
194
+ label?: string
195
+ format?: ValueFormat
196
+ aggregation?: 'sum' | 'count' | 'avg' | 'min' | 'max'
197
+ }>
198
+ }
199
+
200
+ export type EmptyWidgetConfig = WidgetBaseConfig & {
201
+ target: 'empty'
202
+ }
203
+
204
+ export type TableWidgetConfig = WidgetBaseConfig & {
205
+ target: 'table'
206
+ table?: TableViewConfig
207
+ query: QueryConfig
208
+ }
209
+
210
+ export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
211
+ target: 'chart'
212
+ chart: ChartWidgetConfig
213
+ query: QueryConfig | FunnelQueryConfig
214
+ }
215
+
216
+ export type KpiCardWidgetConfig = WidgetBaseConfig & {
217
+ target: 'kpi_card'
218
+ card: KpiCardViewConfig
219
+ query: QueryConfig
220
+ }
221
+
222
+ export type GaugeCardWidgetConfig = WidgetBaseConfig & {
223
+ target: 'gauge_card'
224
+ card: GaugeCardViewConfig
225
+ query: QueryConfig
226
+ }
227
+
228
+ export type PivotTableWidgetConfig = WidgetBaseConfig & {
229
+ target: 'pivot_table'
230
+ pivot: PivotTableViewConfig
231
+ query: QueryConfig
232
+ }
233
+
234
+ export type DashboardWidgetConfig =
235
+ | EmptyWidgetConfig
236
+ | TableWidgetConfig
237
+ | ChartDashboardWidgetConfig
238
+ | KpiCardWidgetConfig
239
+ | GaugeCardWidgetConfig
240
+ | PivotTableWidgetConfig
241
+
93
242
  export type DashboardWidgetTableData = {
94
243
  kind?: 'table'
95
244
  columns: string[]
@@ -107,34 +256,16 @@ export type DashboardWidgetAggregateData = {
107
256
  columns: string[]
108
257
  rows: Record<string, unknown>[]
109
258
  values?: Record<string, unknown>
259
+ pagination?: {
260
+ page: number
261
+ pageSize: number
262
+ total: number
263
+ totalPages: number
264
+ }
110
265
  }
111
266
 
112
267
  export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
113
268
 
114
- export type NormalizedKpiCardWidgetConfig = {
115
- valueField?: string
116
- labelField?: string
117
- prefix?: string
118
- suffix?: string
119
- }
120
-
121
- export type NormalizedGaugeCardWidgetConfig = {
122
- valueField?: string
123
- min?: number | string
124
- max?: number | string
125
- minField?: string
126
- maxField?: string
127
- suffix?: string
128
- color?: string
129
- }
130
-
131
- export type NormalizedPivotTableWidgetConfig = {
132
- rowField?: string
133
- columnField?: string
134
- valueField?: string
135
- aggregation?: 'count' | 'sum'
136
- }
137
-
138
269
  export function normalizeDashboardConfig(config: unknown): DashboardConfig {
139
270
  const value = isRecord(config) ? config : {}
140
271
 
@@ -155,12 +286,20 @@ export function normalizeDashboardWidgetConfig(config: unknown) {
155
286
  const normalized: Record<string, unknown> = { ...config }
156
287
  normalizeWidgetLayoutConfig(normalized)
157
288
 
289
+ if (normalized.query !== undefined) {
290
+ normalized.query = normalizeQueryConfig(normalized.query)
291
+ }
292
+
158
293
  if (normalized.table !== undefined) {
159
294
  normalized.table = normalizeTableConfig(normalized.table)
160
295
  }
161
296
 
162
- if (normalized.data_source !== undefined) {
163
- normalized.dataSource = normalizeWidgetDataSource(normalized.data_source)
297
+ if (normalized.card !== undefined) {
298
+ normalized.card = normalizeCardConfig(normalized.card)
299
+ }
300
+
301
+ if (normalized.pivot !== undefined) {
302
+ normalized.pivot = normalizePivotConfig(normalized.pivot)
164
303
  }
165
304
 
166
305
  const target = normalizeDashboardWidgetTarget(normalized.target)
@@ -185,82 +324,31 @@ export function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetC
185
324
  delete serialized.maxWidth
186
325
  }
187
326
 
188
- if (widget.table !== undefined) {
189
- serialized.table = serializeTableConfigForEditor(widget.table)
190
- }
191
-
192
- if (widget.dataSource !== undefined) {
193
- serialized.data_source = serializeWidgetDataSourceForEditor(widget.dataSource)
194
- delete serialized.dataSource
327
+ if ('query' in widget) {
328
+ serialized.query = serializeQueryConfigForEditor(widget.query)
195
329
  }
196
330
 
197
- return serialized
198
- }
199
-
200
- export function normalizeKpiCardWidgetConfig(value: unknown): NormalizedKpiCardWidgetConfig | undefined {
201
- const config = asWidgetConfigRecord(value)
202
-
203
- if (!config) {
204
- return undefined
331
+ if ('table' in widget && widget.table !== undefined) {
332
+ serialized.table = serializeTableConfigForEditor(widget.table)
205
333
  }
206
334
 
207
- const valueField = getStringField(config, 'value_field')
208
- const labelField = getStringField(config, 'label_field')
209
- const prefix = getStringField(config, 'prefix')
210
- const suffix = getStringField(config, 'suffix')
211
-
212
- return {
213
- ...(valueField !== undefined ? { valueField } : {}),
214
- ...(labelField !== undefined ? { labelField } : {}),
215
- ...(prefix !== undefined ? { prefix } : {}),
216
- ...(suffix !== undefined ? { suffix } : {}),
335
+ if ('card' in widget && widget.card !== undefined) {
336
+ serialized.card = serializeCardConfigForEditor(widget.card)
217
337
  }
218
- }
219
338
 
220
- export function normalizeGaugeCardWidgetConfig(value: unknown): NormalizedGaugeCardWidgetConfig | undefined {
221
- const config = asWidgetConfigRecord(value)
222
-
223
- if (!config) {
224
- return undefined
339
+ if ('pivot' in widget && widget.pivot !== undefined) {
340
+ serialized.pivot = serializePivotConfigForEditor(widget.pivot)
225
341
  }
226
342
 
227
- const valueField = getStringField(config, 'value_field')
228
- const minField = getStringField(config, 'min_field')
229
- const maxField = getStringField(config, 'max_field')
230
- const suffix = getStringField(config, 'suffix')
231
- const color = getStringField(config, 'color')
232
-
233
- return {
234
- ...(valueField !== undefined ? { valueField } : {}),
235
- ...(config.min !== undefined ? { min: config.min as number | string } : {}),
236
- ...(config.max !== undefined ? { max: config.max as number | string } : {}),
237
- ...(minField !== undefined ? { minField } : {}),
238
- ...(maxField !== undefined ? { maxField } : {}),
239
- ...(suffix !== undefined ? { suffix } : {}),
240
- ...(color !== undefined ? { color } : {}),
241
- }
343
+ return serialized
242
344
  }
243
345
 
244
- export function normalizePivotTableWidgetConfig(value: unknown): NormalizedPivotTableWidgetConfig | undefined {
245
- const config = asWidgetConfigRecord(value)
246
-
247
- if (!config) {
248
- return undefined
249
- }
250
-
251
- const rowField = getStringField(config, 'row_field')
252
- const columnField = getStringField(config, 'column_field')
253
- const valueField = getStringField(config, 'value_field')
254
- const aggregation = config.aggregation === 'count' || config.aggregation === 'sum'
255
- ? config.aggregation
256
- : undefined
346
+ export function getFieldRefField(value: FieldRef | undefined) {
347
+ return typeof value === 'string' ? value : value?.field
348
+ }
257
349
 
258
- return {
259
- ...(rowField !== undefined ? { rowField } : {}),
260
- ...(columnField !== undefined ? { columnField } : {}),
261
- ...(valueField !== undefined ? { valueField } : {}),
262
- ...(aggregation !== undefined ? { aggregation } : {}),
263
- }
350
+ export function getFieldRefLabel(value: FieldRef | undefined) {
351
+ return typeof value === 'string' ? value : value?.label
264
352
  }
265
353
 
266
354
  function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
@@ -287,138 +375,143 @@ function normalizeWidgetLayoutConfig(value: Record<string, unknown>) {
287
375
  }
288
376
  }
289
377
 
290
- function normalizeTableConfig(value: unknown) {
378
+ function normalizeQueryConfig(value: unknown): unknown {
291
379
  if (!isRecord(value)) {
292
380
  return value
293
381
  }
294
382
 
295
- const normalized = { ...value }
296
-
297
- if (normalized.page_size !== undefined) {
298
- normalized.pageSize = normalized.page_size
383
+ if (Array.isArray(value.steps)) {
384
+ return {
385
+ steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
386
+ }
299
387
  }
300
388
 
301
- return normalized
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
+ }
302
395
  }
303
396
 
304
- function normalizeWidgetDataSource(value: unknown) {
305
- if (!isRecord(value) || typeof value.type !== 'string') {
397
+ function normalizeFunnelQueryStep(value: unknown) {
398
+ if (!isRecord(value)) {
306
399
  return value
307
400
  }
308
401
 
309
- const resourceId = typeof value.resource_id === 'string'
310
- ? value.resource_id
311
- : undefined
312
-
313
- if (value.type === 'resource') {
314
- return {
315
- type: 'resource',
316
- ...(resourceId !== undefined ? { resourceId } : {}),
317
- ...(value.columns !== undefined ? { columns: value.columns } : {}),
318
- ...(value.filters !== undefined ? { filters: value.filters } : {}),
319
- ...(value.sort !== undefined ? { sort: value.sort } : {}),
320
- }
402
+ return {
403
+ ...value,
404
+ ...(typeof value.resource_id === 'string' ? { resource: value.resource_id } : {}),
321
405
  }
406
+ }
322
407
 
323
- if (value.type === 'aggregate') {
324
- const groupBy = normalizeGroupByRule(value.group_by)
325
-
326
- return {
327
- type: 'aggregate',
328
- ...(resourceId !== undefined ? { resourceId } : {}),
329
- ...(value.aggregations !== undefined ? { aggregations: value.aggregations } : {}),
330
- ...(groupBy !== undefined ? { groupBy } : {}),
331
- ...(value.filters !== undefined ? { filters: value.filters } : {}),
332
- }
408
+ function normalizeTableConfig(value: unknown) {
409
+ if (!isRecord(value)) {
410
+ return value
333
411
  }
334
412
 
335
- return value
413
+ return {
414
+ ...value,
415
+ ...(value.page_size !== undefined ? { pageSize: value.page_size } : {}),
416
+ }
336
417
  }
337
418
 
338
- function normalizeGroupByRule(value: unknown) {
339
- if (!isRecord(value) || typeof value.type !== 'string') {
419
+ function normalizeCardConfig(value: unknown): unknown {
420
+ if (!isRecord(value)) {
340
421
  return value
341
422
  }
342
423
 
343
- if (value.type === 'field') {
344
- return {
345
- type: 'field',
346
- ...(value.field !== undefined ? { field: value.field } : {}),
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 } : {}),
347
432
  }
348
433
  }
349
434
 
350
- if (value.type === 'date_trunc') {
351
- return {
352
- type: 'date_trunc',
353
- ...(value.field !== undefined ? { field: value.field } : {}),
354
- ...(value.truncation !== undefined ? { truncation: value.truncation } : {}),
355
- ...(value.timezone !== undefined ? { timezone: value.timezone } : {}),
435
+ if (isRecord(normalized.comparison)) {
436
+ normalized.comparison = {
437
+ ...normalized.comparison,
438
+ ...(normalized.comparison.positive_is_good !== undefined ? { positiveIsGood: normalized.comparison.positive_is_good } : {}),
356
439
  }
357
440
  }
358
441
 
359
- return value
442
+ return normalized
360
443
  }
361
444
 
362
- function serializeTableConfigForEditor(value: unknown) {
363
- if (!isRecord(value)) {
364
- return value
365
- }
366
-
367
- const serialized = { ...value }
368
-
369
- if (Object.prototype.hasOwnProperty.call(serialized, 'pageSize')) {
370
- serialized.page_size = serialized.pageSize
371
- delete serialized.pageSize
372
- }
373
-
374
- return serialized
445
+ function normalizePivotConfig(value: unknown): unknown {
446
+ return value
375
447
  }
376
448
 
377
- function serializeWidgetDataSourceForEditor(value: WidgetDataSource) {
378
- if (value.type === 'resource') {
449
+ function serializeQueryConfigForEditor(value: QueryConfig | FunnelQueryConfig) {
450
+ if ('steps' in value) {
379
451
  return {
380
- type: 'resource',
381
- resource_id: value.resourceId,
382
- ...(value.columns !== undefined ? { columns: value.columns } : {}),
383
- ...(value.filters !== undefined ? { filters: value.filters } : {}),
384
- ...(value.sort !== undefined ? { sort: value.sort } : {}),
452
+ steps: value.steps.map((step) => ({
453
+ ...step,
454
+ resource_id: step.resource,
455
+ resource: undefined,
456
+ })).map((step) => removeUndefinedFields(step)),
385
457
  }
386
458
  }
387
459
 
388
- return {
389
- type: 'aggregate',
390
- resource_id: value.resourceId,
391
- aggregations: value.aggregations,
392
- ...(value.groupBy !== undefined ? { group_by: serializeGroupByRuleForEditor(value.groupBy) } : {}),
393
- ...(value.filters !== undefined ? { filters: value.filters } : {}),
394
- }
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
+ })
395
469
  }
396
470
 
397
- function serializeGroupByRuleForEditor(value: GroupByRule) {
398
- if (value.type === 'field') {
399
- return {
400
- type: 'field',
401
- field: value.field,
402
- }
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
+ })
403
492
  }
404
493
 
405
- return {
406
- type: 'date_trunc',
407
- field: value.field,
408
- truncation: value.truncation,
409
- ...(value.timezone !== undefined ? { timezone: value.timezone } : {}),
494
+ if (isRecord(serialized.comparison)) {
495
+ serialized.comparison = removeUndefinedFields({
496
+ ...serialized.comparison,
497
+ positive_is_good: serialized.comparison.positiveIsGood,
498
+ positiveIsGood: undefined,
499
+ })
410
500
  }
501
+
502
+ return removeUndefinedFields(serialized)
411
503
  }
412
504
 
413
- function asWidgetConfigRecord(value: unknown): Record<string, unknown> | undefined {
414
- return isRecord(value) ? value : undefined
505
+ function serializePivotConfigForEditor(value: PivotTableViewConfig) {
506
+ return value
415
507
  }
416
508
 
417
- function getStringField(record: Record<string, unknown>, key: string) {
418
- const value = record[key]
419
- return typeof value === 'string' ? value : undefined
509
+ function removeUndefinedFields<T extends Record<string, unknown>>(value: T) {
510
+ return Object.fromEntries(
511
+ Object.entries(value).filter(([, item]) => item !== undefined),
512
+ )
420
513
  }
421
514
 
422
- function isRecord(value: unknown): value is Record<string, unknown> {
515
+ function isRecord(value: unknown): value is Record<string, any> {
423
516
  return typeof value === 'object' && value !== null
424
517
  }