@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
@@ -2,7 +2,7 @@
2
2
  import { computed, watch } from 'vue'
3
3
  import { useWidgetData } from '../../queries/useWidgetData.js'
4
4
  import {
5
- normalizePivotTableWidgetConfig,
5
+ getFieldRefField,
6
6
  } from '../../model/dashboard.types.js'
7
7
  import type { DashboardWidgetConfig, DashboardWidgetData } from '../../model/dashboard.types.js'
8
8
  import { formatChartLabel, formatChartValue, toFiniteNumber } from '../chart/chart.utils.js'
@@ -29,16 +29,17 @@ watch(
29
29
  { deep: true },
30
30
  )
31
31
 
32
- const pivotConfig = computed(() => normalizePivotTableWidgetConfig(props.widget.pivot_table))
32
+ const pivotConfig = computed(() => props.widget.target === 'pivot_table' ? props.widget.pivot : undefined)
33
33
  const widgetData = computed(() => data.value?.data as DashboardWidgetData | null)
34
34
  const rows = computed(() => widgetData.value?.rows ?? [])
35
35
  const columns = computed(() => widgetData.value?.columns ?? [])
36
36
  const isAggregateData = computed(() => widgetData.value?.kind === 'aggregate')
37
- const shouldRenderAggregateMatrix = computed(() => isAggregateData.value && !pivotConfig.value?.columnField)
38
- const rowField = computed(() => pivotConfig.value?.rowField || (isAggregateData.value ? 'group' : columns.value[0]))
39
- const columnField = computed(() => pivotConfig.value?.columnField || columns.value[1])
40
- const valueField = computed(() => pivotConfig.value?.valueField || columns.value[2] || columns.value[1])
41
- const aggregation = computed(() => pivotConfig.value?.aggregation || (valueField.value ? 'sum' : 'count'))
37
+ const shouldRenderAggregateMatrix = computed(() => isAggregateData.value && !pivotConfig.value?.columns?.length)
38
+ const rowField = computed(() => getFieldRefField(pivotConfig.value?.rows[0]) || columns.value[0])
39
+ const columnField = computed(() => getFieldRefField(pivotConfig.value?.columns?.[0]) || columns.value[1])
40
+ const valueConfig = computed(() => pivotConfig.value?.values[0])
41
+ const valueField = computed(() => valueConfig.value?.field || columns.value[2] || columns.value[1])
42
+ const aggregation = computed(() => valueConfig.value?.aggregation || (valueField.value ? 'sum' : 'count'))
42
43
  const pivotColumnLabels = computed(() => {
43
44
  if (shouldRenderAggregateMatrix.value) {
44
45
  return columns.value.filter((column) => column !== rowField.value)
@@ -113,10 +113,11 @@
113
113
  <script setup lang="ts">
114
114
  import { computed, ref, watch } from 'vue'
115
115
  import { useWidgetData } from '../../queries/useWidgetData.js'
116
- import type { DashboardWidgetConfig, DashboardWidgetTableData } from '../../model/dashboard.types.js'
116
+ import { getFieldRefField } from '../../model/dashboard.types.js'
117
+ import type { DashboardWidgetConfig, DashboardWidgetTableData, FieldRef } from '../../model/dashboard.types.js'
117
118
 
118
119
  type TableWidgetConfig = {
119
- columns?: string[]
120
+ columns?: FieldRef[]
120
121
  pagination?: boolean
121
122
  pageSize?: number
122
123
  }
@@ -168,7 +169,11 @@ const tableData = computed(() => {
168
169
 
169
170
  const columns = computed(() => {
170
171
  const configuredColumns = tableConfig.value?.columns
171
- return configuredColumns ?? tableData.value?.columns ?? []
172
+ if (configuredColumns) {
173
+ return configuredColumns.map((column) => getFieldRefField(column)).filter(Boolean) as string[]
174
+ }
175
+
176
+ return tableData.value?.columns ?? []
172
177
  })
173
178
 
174
179
  const pagination = computed(() => tableData.value?.pagination)
@@ -7,19 +7,31 @@ function useElementSize() {
7
7
  const width = (0, vue_1.ref)(0);
8
8
  const height = (0, vue_1.ref)(0);
9
9
  let observer;
10
+ let frameId;
10
11
  (0, vue_1.onMounted)(() => {
11
12
  observer = new ResizeObserver(([entry]) => {
12
13
  if (!entry) {
13
14
  return;
14
15
  }
15
- width.value = Math.floor(entry.contentRect.width);
16
- height.value = Math.floor(entry.contentRect.height);
16
+ const nextWidth = Math.floor(entry.contentRect.width);
17
+ const nextHeight = Math.floor(entry.contentRect.height);
18
+ if (frameId !== undefined) {
19
+ cancelAnimationFrame(frameId);
20
+ }
21
+ frameId = requestAnimationFrame(() => {
22
+ frameId = undefined;
23
+ width.value = nextWidth;
24
+ height.value = nextHeight;
25
+ });
17
26
  });
18
27
  if (el.value) {
19
28
  observer.observe(el.value);
20
29
  }
21
30
  });
22
31
  (0, vue_1.onBeforeUnmount)(() => {
32
+ if (frameId !== undefined) {
33
+ cancelAnimationFrame(frameId);
34
+ }
23
35
  observer === null || observer === void 0 ? void 0 : observer.disconnect();
24
36
  });
25
37
  return {
@@ -13,6 +13,7 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
13
13
  const height = ref(0)
14
14
 
15
15
  let observer: ResizeObserver | undefined
16
+ let frameId: number | undefined
16
17
 
17
18
  onMounted(() => {
18
19
  observer = new ResizeObserver(([entry]) => {
@@ -20,8 +21,18 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
20
21
  return
21
22
  }
22
23
 
23
- width.value = Math.floor(entry.contentRect.width)
24
- height.value = Math.floor(entry.contentRect.height)
24
+ const nextWidth = Math.floor(entry.contentRect.width)
25
+ const nextHeight = Math.floor(entry.contentRect.height)
26
+
27
+ if (frameId !== undefined) {
28
+ cancelAnimationFrame(frameId)
29
+ }
30
+
31
+ frameId = requestAnimationFrame(() => {
32
+ frameId = undefined
33
+ width.value = nextWidth
34
+ height.value = nextHeight
35
+ })
25
36
  })
26
37
 
27
38
  if (el.value) {
@@ -30,6 +41,10 @@ export function useElementSize<T extends HTMLElement>(): ElementSizeState<T> {
30
41
  })
31
42
 
32
43
  onBeforeUnmount(() => {
44
+ if (frameId !== undefined) {
45
+ cancelAnimationFrame(frameId)
46
+ }
47
+
33
48
  observer?.disconnect()
34
49
  })
35
50
 
@@ -1,33 +1,4 @@
1
1
  import type { ChartWidgetConfig } from '../widgets/chart/chart.types.js';
2
- export type AggregationOperation = 'sum' | 'count' | 'avg' | 'min' | 'max' | 'median';
3
- export type AggregationRule = {
4
- operation: AggregationOperation;
5
- field?: string;
6
- };
7
- export type GroupByRule = {
8
- type: 'field';
9
- field: string;
10
- } | {
11
- type: 'date_trunc';
12
- field: string;
13
- truncation: 'day' | 'week' | 'month' | 'year';
14
- timezone?: string;
15
- };
16
- export type ResourceWidgetDataSource = {
17
- type: 'resource';
18
- resourceId: string;
19
- columns?: string[];
20
- sort?: unknown;
21
- filters?: unknown;
22
- };
23
- export type AggregateWidgetDataSource = {
24
- type: 'aggregate';
25
- resourceId: string;
26
- aggregations: Record<string, AggregationRule>;
27
- groupBy?: GroupByRule;
28
- filters?: unknown;
29
- };
30
- export type WidgetDataSource = ResourceWidgetDataSource | AggregateWidgetDataSource;
31
2
  export type DashboardConfig = {
32
3
  version: number;
33
4
  groups: DashboardGroupConfig[];
@@ -42,6 +13,9 @@ export type DashboardGroupMoveDirection = 'up' | 'down';
42
13
  export type DashboardWidgetMoveDirection = 'up' | 'down';
43
14
  export type DashboardWidgetTarget = 'empty' | 'table' | 'chart' | 'kpi_card' | 'pivot_table' | 'gauge_card';
44
15
  export type DashboardWidgetSize = 'small' | 'medium' | 'large' | 'wide' | 'full';
16
+ export type QueryAggregateOperation = 'sum' | 'count' | 'count_distinct' | 'avg' | 'min' | 'max' | 'median';
17
+ export type TimeGrain = 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
18
+ export type ValueFormat = 'number' | 'compact_number' | 'currency' | 'percent' | 'percent_delta' | 'number_delta' | 'currency_delta';
45
19
  export type WidgetLayout = {
46
20
  size?: DashboardWidgetSize;
47
21
  width?: number;
@@ -49,7 +23,7 @@ export type WidgetLayout = {
49
23
  maxWidth?: number | null;
50
24
  height?: number;
51
25
  };
52
- export type DashboardWidgetConfig = {
26
+ export type WidgetBaseConfig = {
53
27
  id: string;
54
28
  group_id: string;
55
29
  label?: string;
@@ -59,14 +33,173 @@ export type DashboardWidgetConfig = {
59
33
  minWidth?: number;
60
34
  maxWidth?: number | null;
61
35
  order: number;
62
- target: DashboardWidgetTarget;
63
- dataSource?: WidgetDataSource;
64
- chart?: ChartWidgetConfig;
65
- table?: unknown;
66
- kpi_card?: unknown;
67
- pivot_table?: unknown;
68
- gauge_card?: unknown;
69
36
  };
37
+ export type FilterExpression = {
38
+ and: FilterExpression[];
39
+ } | {
40
+ or: FilterExpression[];
41
+ } | Array<FilterExpression> | {
42
+ field: string;
43
+ eq?: unknown;
44
+ neq?: unknown;
45
+ gt?: unknown;
46
+ gte?: unknown;
47
+ lt?: unknown;
48
+ lte?: unknown;
49
+ in?: unknown[];
50
+ not_in?: unknown[];
51
+ like?: unknown;
52
+ ilike?: unknown;
53
+ };
54
+ export type QueryFieldSelectItem = {
55
+ field: string;
56
+ as?: string;
57
+ grain?: TimeGrain;
58
+ };
59
+ export type QueryAggregateSelectItem = {
60
+ agg: QueryAggregateOperation;
61
+ field?: string;
62
+ as: string;
63
+ filters?: FilterExpression;
64
+ };
65
+ export type QueryCalcSelectItem = {
66
+ calc: string;
67
+ as: string;
68
+ };
69
+ export type QuerySelectItem = QueryFieldSelectItem | QueryAggregateSelectItem | QueryCalcSelectItem;
70
+ export type QueryGroupByItem = string | {
71
+ field: string;
72
+ as?: string;
73
+ grain?: TimeGrain;
74
+ timezone?: string;
75
+ };
76
+ export type QueryOrderByItem = {
77
+ field: string;
78
+ direction?: 'asc' | 'desc';
79
+ };
80
+ export type QueryConfig = {
81
+ resource: string;
82
+ select?: QuerySelectItem[];
83
+ filters?: FilterExpression;
84
+ groupBy?: QueryGroupByItem[];
85
+ orderBy?: QueryOrderByItem[];
86
+ limit?: number;
87
+ offset?: number;
88
+ timeSeries?: {
89
+ field: string;
90
+ grain: TimeGrain;
91
+ timezone?: string;
92
+ };
93
+ period?: {
94
+ field: string;
95
+ gte?: unknown;
96
+ lt?: unknown;
97
+ };
98
+ bucket?: {
99
+ field: string;
100
+ buckets: Array<{
101
+ label: string;
102
+ min?: number;
103
+ max?: number;
104
+ }>;
105
+ };
106
+ calcs?: QueryCalcSelectItem[];
107
+ formatting?: Record<string, unknown>;
108
+ };
109
+ export type FunnelQueryConfig = {
110
+ steps: FunnelQueryStep[];
111
+ };
112
+ export type FunnelQueryStep = {
113
+ name: string;
114
+ resource: string;
115
+ metric: QueryAggregateSelectItem;
116
+ filters?: FilterExpression;
117
+ };
118
+ export type FieldRef = string | {
119
+ field: string;
120
+ label?: string;
121
+ format?: ValueFormat;
122
+ };
123
+ export type TableViewConfig = {
124
+ columns?: FieldRef[];
125
+ pagination?: boolean;
126
+ pageSize?: number;
127
+ };
128
+ export type KpiCardViewConfig = {
129
+ title?: string;
130
+ value: {
131
+ field: string;
132
+ format?: ValueFormat;
133
+ prefix?: string;
134
+ suffix?: string;
135
+ };
136
+ subtitle?: {
137
+ text?: string;
138
+ field?: string;
139
+ };
140
+ comparison?: unknown;
141
+ sparkline?: unknown;
142
+ };
143
+ export type GaugeCardViewConfig = {
144
+ title?: string;
145
+ value: {
146
+ field: string;
147
+ format?: ValueFormat;
148
+ prefix?: string;
149
+ suffix?: string;
150
+ };
151
+ target?: {
152
+ value?: number;
153
+ field?: string;
154
+ label?: string;
155
+ };
156
+ progress?: {
157
+ valueField: string;
158
+ targetValue?: number;
159
+ targetField?: string;
160
+ format?: ValueFormat;
161
+ };
162
+ color?: string;
163
+ };
164
+ export type PivotTableViewConfig = {
165
+ rows: FieldRef[];
166
+ columns?: FieldRef[];
167
+ values: Array<{
168
+ field: string;
169
+ label?: string;
170
+ format?: ValueFormat;
171
+ aggregation?: 'sum' | 'count' | 'avg' | 'min' | 'max';
172
+ }>;
173
+ };
174
+ export type EmptyWidgetConfig = WidgetBaseConfig & {
175
+ target: 'empty';
176
+ };
177
+ export type TableWidgetConfig = WidgetBaseConfig & {
178
+ target: 'table';
179
+ table?: TableViewConfig;
180
+ query: QueryConfig;
181
+ };
182
+ export type ChartDashboardWidgetConfig = WidgetBaseConfig & {
183
+ target: 'chart';
184
+ chart: ChartWidgetConfig;
185
+ query: QueryConfig | FunnelQueryConfig;
186
+ };
187
+ export type KpiCardWidgetConfig = WidgetBaseConfig & {
188
+ target: 'kpi_card';
189
+ card: KpiCardViewConfig;
190
+ query: QueryConfig;
191
+ };
192
+ export type GaugeCardWidgetConfig = WidgetBaseConfig & {
193
+ target: 'gauge_card';
194
+ card: GaugeCardViewConfig;
195
+ query: QueryConfig;
196
+ };
197
+ export type PivotTableWidgetConfig = WidgetBaseConfig & {
198
+ target: 'pivot_table';
199
+ pivot: PivotTableViewConfig;
200
+ query: QueryConfig;
201
+ };
202
+ export type DashboardWidgetConfig = EmptyWidgetConfig | TableWidgetConfig | ChartDashboardWidgetConfig | KpiCardWidgetConfig | GaugeCardWidgetConfig | PivotTableWidgetConfig;
70
203
  export type DashboardWidgetTableData = {
71
204
  kind?: 'table';
72
205
  columns: string[];
@@ -83,32 +216,16 @@ export type DashboardWidgetAggregateData = {
83
216
  columns: string[];
84
217
  rows: Record<string, unknown>[];
85
218
  values?: Record<string, unknown>;
219
+ pagination?: {
220
+ page: number;
221
+ pageSize: number;
222
+ total: number;
223
+ totalPages: number;
224
+ };
86
225
  };
87
226
  export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData;
88
- export type NormalizedKpiCardWidgetConfig = {
89
- valueField?: string;
90
- labelField?: string;
91
- prefix?: string;
92
- suffix?: string;
93
- };
94
- export type NormalizedGaugeCardWidgetConfig = {
95
- valueField?: string;
96
- min?: number | string;
97
- max?: number | string;
98
- minField?: string;
99
- maxField?: string;
100
- suffix?: string;
101
- color?: string;
102
- };
103
- export type NormalizedPivotTableWidgetConfig = {
104
- rowField?: string;
105
- columnField?: string;
106
- valueField?: string;
107
- aggregation?: 'count' | 'sum';
108
- };
109
227
  export declare function normalizeDashboardConfig(config: unknown): DashboardConfig;
110
228
  export declare function normalizeDashboardWidgetConfig(config: unknown): unknown;
111
229
  export declare function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig): Record<string, unknown>;
112
- export declare function normalizeKpiCardWidgetConfig(value: unknown): NormalizedKpiCardWidgetConfig | undefined;
113
- export declare function normalizeGaugeCardWidgetConfig(value: unknown): NormalizedGaugeCardWidgetConfig | undefined;
114
- export declare function normalizePivotTableWidgetConfig(value: unknown): NormalizedPivotTableWidgetConfig | undefined;
230
+ export declare function getFieldRefField(value: FieldRef | undefined): string | undefined;
231
+ export declare function getFieldRefLabel(value: FieldRef | undefined): string | undefined;
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeDashboardConfig = normalizeDashboardConfig;
4
4
  exports.normalizeDashboardWidgetConfig = normalizeDashboardWidgetConfig;
5
5
  exports.serializeDashboardWidgetConfigForEditor = serializeDashboardWidgetConfigForEditor;
6
- exports.normalizeKpiCardWidgetConfig = normalizeKpiCardWidgetConfig;
7
- exports.normalizeGaugeCardWidgetConfig = normalizeGaugeCardWidgetConfig;
8
- exports.normalizePivotTableWidgetConfig = normalizePivotTableWidgetConfig;
6
+ exports.getFieldRefField = getFieldRefField;
7
+ exports.getFieldRefLabel = getFieldRefLabel;
9
8
  function normalizeDashboardConfig(config) {
10
9
  const value = isRecord(config) ? config : {};
11
10
  return {
@@ -22,11 +21,17 @@ function normalizeDashboardWidgetConfig(config) {
22
21
  }
23
22
  const normalized = Object.assign({}, config);
24
23
  normalizeWidgetLayoutConfig(normalized);
24
+ if (normalized.query !== undefined) {
25
+ normalized.query = normalizeQueryConfig(normalized.query);
26
+ }
25
27
  if (normalized.table !== undefined) {
26
28
  normalized.table = normalizeTableConfig(normalized.table);
27
29
  }
28
- if (normalized.data_source !== undefined) {
29
- normalized.dataSource = normalizeWidgetDataSource(normalized.data_source);
30
+ if (normalized.card !== undefined) {
31
+ normalized.card = normalizeCardConfig(normalized.card);
32
+ }
33
+ if (normalized.pivot !== undefined) {
34
+ normalized.pivot = normalizePivotConfig(normalized.pivot);
30
35
  }
31
36
  const target = normalizeDashboardWidgetTarget(normalized.target);
32
37
  if (target !== undefined) {
@@ -44,50 +49,25 @@ function serializeDashboardWidgetConfigForEditor(widget) {
44
49
  serialized.max_width = widget.maxWidth;
45
50
  delete serialized.maxWidth;
46
51
  }
47
- if (widget.table !== undefined) {
52
+ if ('query' in widget) {
53
+ serialized.query = serializeQueryConfigForEditor(widget.query);
54
+ }
55
+ if ('table' in widget && widget.table !== undefined) {
48
56
  serialized.table = serializeTableConfigForEditor(widget.table);
49
57
  }
50
- if (widget.dataSource !== undefined) {
51
- serialized.data_source = serializeWidgetDataSourceForEditor(widget.dataSource);
52
- delete serialized.dataSource;
58
+ if ('card' in widget && widget.card !== undefined) {
59
+ serialized.card = serializeCardConfigForEditor(widget.card);
60
+ }
61
+ if ('pivot' in widget && widget.pivot !== undefined) {
62
+ serialized.pivot = serializePivotConfigForEditor(widget.pivot);
53
63
  }
54
64
  return serialized;
55
65
  }
56
- function normalizeKpiCardWidgetConfig(value) {
57
- const config = asWidgetConfigRecord(value);
58
- if (!config) {
59
- return undefined;
60
- }
61
- const valueField = getStringField(config, 'value_field');
62
- const labelField = getStringField(config, 'label_field');
63
- const prefix = getStringField(config, 'prefix');
64
- const suffix = getStringField(config, 'suffix');
65
- return Object.assign(Object.assign(Object.assign(Object.assign({}, (valueField !== undefined ? { valueField } : {})), (labelField !== undefined ? { labelField } : {})), (prefix !== undefined ? { prefix } : {})), (suffix !== undefined ? { suffix } : {}));
66
- }
67
- function normalizeGaugeCardWidgetConfig(value) {
68
- const config = asWidgetConfigRecord(value);
69
- if (!config) {
70
- return undefined;
71
- }
72
- const valueField = getStringField(config, 'value_field');
73
- const minField = getStringField(config, 'min_field');
74
- const maxField = getStringField(config, 'max_field');
75
- const suffix = getStringField(config, 'suffix');
76
- const color = getStringField(config, 'color');
77
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (valueField !== undefined ? { valueField } : {})), (config.min !== undefined ? { min: config.min } : {})), (config.max !== undefined ? { max: config.max } : {})), (minField !== undefined ? { minField } : {})), (maxField !== undefined ? { maxField } : {})), (suffix !== undefined ? { suffix } : {})), (color !== undefined ? { color } : {}));
78
- }
79
- function normalizePivotTableWidgetConfig(value) {
80
- const config = asWidgetConfigRecord(value);
81
- if (!config) {
82
- return undefined;
83
- }
84
- const rowField = getStringField(config, 'row_field');
85
- const columnField = getStringField(config, 'column_field');
86
- const valueField = getStringField(config, 'value_field');
87
- const aggregation = config.aggregation === 'count' || config.aggregation === 'sum'
88
- ? config.aggregation
89
- : undefined;
90
- return Object.assign(Object.assign(Object.assign(Object.assign({}, (rowField !== undefined ? { rowField } : {})), (columnField !== undefined ? { columnField } : {})), (valueField !== undefined ? { valueField } : {})), (aggregation !== undefined ? { aggregation } : {}));
66
+ function getFieldRefField(value) {
67
+ return typeof value === 'string' ? value : value === null || value === void 0 ? void 0 : value.field;
68
+ }
69
+ function getFieldRefLabel(value) {
70
+ return typeof value === 'string' ? value : value === null || value === void 0 ? void 0 : value.label;
91
71
  }
92
72
  function normalizeDashboardWidgetTarget(value) {
93
73
  switch (value) {
@@ -110,76 +90,71 @@ function normalizeWidgetLayoutConfig(value) {
110
90
  value.maxWidth = value.max_width;
111
91
  }
112
92
  }
113
- function normalizeTableConfig(value) {
93
+ function normalizeQueryConfig(value) {
114
94
  if (!isRecord(value)) {
115
95
  return value;
116
96
  }
117
- const normalized = Object.assign({}, value);
118
- if (normalized.page_size !== undefined) {
119
- normalized.pageSize = normalized.page_size;
97
+ if (Array.isArray(value.steps)) {
98
+ return {
99
+ steps: value.steps.map((step) => normalizeFunnelQueryStep(step)),
100
+ };
120
101
  }
121
- return normalized;
102
+ 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 } : {}));
122
103
  }
123
- function normalizeWidgetDataSource(value) {
124
- if (!isRecord(value) || typeof value.type !== 'string') {
104
+ function normalizeFunnelQueryStep(value) {
105
+ if (!isRecord(value)) {
125
106
  return value;
126
107
  }
127
- const resourceId = typeof value.resource_id === 'string'
128
- ? value.resource_id
129
- : undefined;
130
- if (value.type === 'resource') {
131
- return Object.assign(Object.assign(Object.assign(Object.assign({ type: 'resource' }, (resourceId !== undefined ? { resourceId } : {})), (value.columns !== undefined ? { columns: value.columns } : {})), (value.filters !== undefined ? { filters: value.filters } : {})), (value.sort !== undefined ? { sort: value.sort } : {}));
132
- }
133
- if (value.type === 'aggregate') {
134
- const groupBy = normalizeGroupByRule(value.group_by);
135
- return Object.assign(Object.assign(Object.assign(Object.assign({ type: 'aggregate' }, (resourceId !== undefined ? { resourceId } : {})), (value.aggregations !== undefined ? { aggregations: value.aggregations } : {})), (groupBy !== undefined ? { groupBy } : {})), (value.filters !== undefined ? { filters: value.filters } : {}));
136
- }
137
- return value;
108
+ return Object.assign(Object.assign({}, value), (typeof value.resource_id === 'string' ? { resource: value.resource_id } : {}));
138
109
  }
139
- function normalizeGroupByRule(value) {
140
- if (!isRecord(value) || typeof value.type !== 'string') {
110
+ function normalizeTableConfig(value) {
111
+ if (!isRecord(value)) {
141
112
  return value;
142
113
  }
143
- if (value.type === 'field') {
144
- return Object.assign({ type: 'field' }, (value.field !== undefined ? { field: value.field } : {}));
145
- }
146
- if (value.type === 'date_trunc') {
147
- return Object.assign(Object.assign(Object.assign({ type: 'date_trunc' }, (value.field !== undefined ? { field: value.field } : {})), (value.truncation !== undefined ? { truncation: value.truncation } : {})), (value.timezone !== undefined ? { timezone: value.timezone } : {}));
148
- }
149
- return value;
114
+ return Object.assign(Object.assign({}, value), (value.page_size !== undefined ? { pageSize: value.page_size } : {}));
150
115
  }
151
- function serializeTableConfigForEditor(value) {
116
+ function normalizeCardConfig(value) {
152
117
  if (!isRecord(value)) {
153
118
  return value;
154
119
  }
155
- const serialized = Object.assign({}, value);
156
- if (Object.prototype.hasOwnProperty.call(serialized, 'pageSize')) {
157
- serialized.page_size = serialized.pageSize;
158
- delete serialized.pageSize;
120
+ const normalized = Object.assign({}, value);
121
+ if (isRecord(normalized.progress)) {
122
+ normalized.progress = Object.assign(Object.assign(Object.assign(Object.assign({}, normalized.progress), (normalized.progress.value_field !== undefined ? { valueField: normalized.progress.value_field } : {})), (normalized.progress.target_value !== undefined ? { targetValue: normalized.progress.target_value } : {})), (normalized.progress.target_field !== undefined ? { targetField: normalized.progress.target_field } : {}));
159
123
  }
160
- return serialized;
161
- }
162
- function serializeWidgetDataSourceForEditor(value) {
163
- if (value.type === 'resource') {
164
- return Object.assign(Object.assign(Object.assign({ type: 'resource', resource_id: value.resourceId }, (value.columns !== undefined ? { columns: value.columns } : {})), (value.filters !== undefined ? { filters: value.filters } : {})), (value.sort !== undefined ? { sort: value.sort } : {}));
124
+ if (isRecord(normalized.comparison)) {
125
+ normalized.comparison = Object.assign(Object.assign({}, normalized.comparison), (normalized.comparison.positive_is_good !== undefined ? { positiveIsGood: normalized.comparison.positive_is_good } : {}));
165
126
  }
166
- return Object.assign(Object.assign({ type: 'aggregate', resource_id: value.resourceId, aggregations: value.aggregations }, (value.groupBy !== undefined ? { group_by: serializeGroupByRuleForEditor(value.groupBy) } : {})), (value.filters !== undefined ? { filters: value.filters } : {}));
127
+ return normalized;
167
128
  }
168
- function serializeGroupByRuleForEditor(value) {
169
- if (value.type === 'field') {
129
+ function normalizePivotConfig(value) {
130
+ return value;
131
+ }
132
+ function serializeQueryConfigForEditor(value) {
133
+ if ('steps' in value) {
170
134
  return {
171
- type: 'field',
172
- field: value.field,
135
+ steps: value.steps.map((step) => (Object.assign(Object.assign({}, step), { resource_id: step.resource, resource: undefined }))).map((step) => removeUndefinedFields(step)),
173
136
  };
174
137
  }
175
- return Object.assign({ type: 'date_trunc', field: value.field, truncation: value.truncation }, (value.timezone !== undefined ? { timezone: value.timezone } : {}));
138
+ 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
+ }
140
+ function serializeTableConfigForEditor(value) {
141
+ return removeUndefinedFields(Object.assign(Object.assign({}, value), { page_size: value.pageSize, pageSize: undefined }));
142
+ }
143
+ function serializeCardConfigForEditor(value) {
144
+ const serialized = Object.assign({}, value);
145
+ if (isRecord(serialized.progress)) {
146
+ serialized.progress = removeUndefinedFields(Object.assign(Object.assign({}, serialized.progress), { value_field: serialized.progress.valueField, valueField: undefined, target_value: serialized.progress.targetValue, targetValue: undefined, target_field: serialized.progress.targetField, targetField: undefined }));
147
+ }
148
+ if (isRecord(serialized.comparison)) {
149
+ serialized.comparison = removeUndefinedFields(Object.assign(Object.assign({}, serialized.comparison), { positive_is_good: serialized.comparison.positiveIsGood, positiveIsGood: undefined }));
150
+ }
151
+ return removeUndefinedFields(serialized);
176
152
  }
177
- function asWidgetConfigRecord(value) {
178
- return isRecord(value) ? value : undefined;
153
+ function serializePivotConfigForEditor(value) {
154
+ return value;
179
155
  }
180
- function getStringField(record, key) {
181
- const value = record[key];
182
- return typeof value === 'string' ? value : undefined;
156
+ function removeUndefinedFields(value) {
157
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
183
158
  }
184
159
  function isRecord(value) {
185
160
  return typeof value === 'object' && value !== null;