@adminforth/dashboard 1.0.0 → 1.2.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 (55) hide show
  1. package/README.md +99 -54
  2. package/custom/api/dashboardApi.ts +9 -0
  3. package/custom/model/dashboard.types.ts +353 -2
  4. package/custom/queries/useWidgetData.ts +8 -4
  5. package/custom/runtime/DashboardRuntime.vue +2 -1
  6. package/custom/runtime/WidgetRenderer.vue +2 -1
  7. package/custom/runtime/WidgetShell.vue +8 -4
  8. package/custom/skills/adminforth-dashboard/SKILL.md +4 -4
  9. package/custom/widgets/chart/ChartWidget.vue +45 -12
  10. package/custom/widgets/chart/chart.types.ts +83 -0
  11. package/custom/widgets/chart/chart.utils.ts +2 -2
  12. package/custom/widgets/gauge-card/GaugeCardWidget.vue +63 -12
  13. package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  14. package/custom/widgets/pivot-table/PivotTableWidget.vue +32 -12
  15. package/custom/widgets/table/TableWidget.vue +155 -30
  16. package/dist/custom/api/dashboardApi.d.ts +7 -1
  17. package/dist/custom/api/dashboardApi.js +4 -6
  18. package/dist/custom/api/dashboardApi.ts +9 -0
  19. package/dist/custom/model/dashboard.types.d.ts +70 -1
  20. package/dist/custom/model/dashboard.types.js +173 -1
  21. package/dist/custom/model/dashboard.types.ts +353 -2
  22. package/dist/custom/queries/useDashboardConfig.d.ts +42 -2
  23. package/dist/custom/queries/useWidgetData.d.ts +44 -3
  24. package/dist/custom/queries/useWidgetData.js +3 -3
  25. package/dist/custom/queries/useWidgetData.ts +8 -4
  26. package/dist/custom/runtime/DashboardRuntime.vue +2 -1
  27. package/dist/custom/runtime/WidgetRenderer.vue +2 -1
  28. package/dist/custom/runtime/WidgetShell.vue +8 -4
  29. package/dist/custom/skills/adminforth-dashboard/SKILL.md +4 -4
  30. package/dist/custom/widgets/chart/ChartWidget.vue +45 -12
  31. package/dist/custom/widgets/chart/chart.types.d.ts +15 -0
  32. package/dist/custom/widgets/chart/chart.types.js +46 -0
  33. package/dist/custom/widgets/chart/chart.types.ts +83 -0
  34. package/dist/custom/widgets/chart/chart.utils.d.ts +1 -1
  35. package/dist/custom/widgets/chart/chart.utils.js +2 -2
  36. package/dist/custom/widgets/chart/chart.utils.ts +2 -2
  37. package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +63 -12
  38. package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  39. package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +32 -12
  40. package/dist/custom/widgets/table/TableWidget.vue +155 -30
  41. package/dist/endpoint/widgets.d.ts +6 -1
  42. package/dist/endpoint/widgets.js +41 -6
  43. package/dist/schema/api.d.ts +874 -444
  44. package/dist/schema/api.js +11 -2
  45. package/dist/schema/widget.d.ts +538 -132
  46. package/dist/schema/widget.js +138 -14
  47. package/dist/services/widgetConfigValidator.js +26 -40
  48. package/dist/services/widgetDataService.d.ts +7 -14
  49. package/dist/services/widgetDataService.js +115 -11
  50. package/endpoint/widgets.ts +56 -6
  51. package/package.json +1 -1
  52. package/schema/api.ts +11 -1
  53. package/schema/widget.ts +145 -15
  54. package/services/widgetConfigValidator.ts +36 -44
  55. package/services/widgetDataService.ts +175 -28
@@ -24,6 +24,13 @@ export type DashboardWidgetDataResponse = {
24
24
  data: unknown
25
25
  }
26
26
 
27
+ export type DashboardWidgetDataRequest = {
28
+ pagination?: {
29
+ page: number
30
+ pageSize: number
31
+ }
32
+ }
33
+
27
34
  export class DashboardApiError extends Error {
28
35
  validationErrors: DashboardWidgetConfigValidationError[]
29
36
 
@@ -204,10 +211,12 @@ export const dashboardApi = {
204
211
  async getDashboardWidgetData(
205
212
  slug: string,
206
213
  widgetId: string,
214
+ request: DashboardWidgetDataRequest = {},
207
215
  ): Promise<DashboardWidgetDataResponse> {
208
216
  return callDashboardWidgetDataApi('/adminapi/v1/dashboard/get_dashboard_widget_data', {
209
217
  slug,
210
218
  widgetId,
219
+ ...request,
211
220
  })
212
221
  },
213
222
  }
@@ -1,4 +1,33 @@
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;
2
31
  export type DashboardConfig = {
3
32
  version: number;
4
33
  groups: DashboardGroupConfig[];
@@ -31,15 +60,55 @@ export type DashboardWidgetConfig = {
31
60
  maxWidth?: number | null;
32
61
  order: number;
33
62
  target: DashboardWidgetTarget;
63
+ dataSource?: WidgetDataSource;
34
64
  chart?: ChartWidgetConfig;
35
65
  table?: unknown;
36
66
  kpi_card?: unknown;
37
67
  pivot_table?: unknown;
38
68
  gauge_card?: unknown;
39
- query?: unknown;
40
69
  };
41
70
  export type DashboardWidgetTableData = {
71
+ kind?: 'table';
72
+ columns: string[];
73
+ rows: Record<string, unknown>[];
74
+ pagination?: {
75
+ page: number;
76
+ pageSize: number;
77
+ total: number;
78
+ totalPages: number;
79
+ };
80
+ };
81
+ export type DashboardWidgetAggregateData = {
82
+ kind: 'aggregate';
42
83
  columns: string[];
43
84
  rows: Record<string, unknown>[];
85
+ values?: Record<string, unknown>;
86
+ };
87
+ 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';
44
108
  };
45
109
  export declare function normalizeDashboardConfig(config: unknown): DashboardConfig;
110
+ export declare function normalizeDashboardWidgetConfig(config: unknown): unknown;
111
+ 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;
@@ -1,14 +1,186 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.normalizeDashboardConfig = normalizeDashboardConfig;
4
+ exports.normalizeDashboardWidgetConfig = normalizeDashboardWidgetConfig;
5
+ exports.serializeDashboardWidgetConfigForEditor = serializeDashboardWidgetConfigForEditor;
6
+ exports.normalizeKpiCardWidgetConfig = normalizeKpiCardWidgetConfig;
7
+ exports.normalizeGaugeCardWidgetConfig = normalizeGaugeCardWidgetConfig;
8
+ exports.normalizePivotTableWidgetConfig = normalizePivotTableWidgetConfig;
4
9
  function normalizeDashboardConfig(config) {
5
10
  const value = isRecord(config) ? config : {};
6
11
  return {
7
12
  version: typeof value.version === 'number' ? value.version : 1,
8
13
  groups: Array.isArray(value.groups) ? value.groups : [],
9
- widgets: Array.isArray(value.widgets) ? value.widgets : [],
14
+ widgets: Array.isArray(value.widgets)
15
+ ? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget))
16
+ : [],
10
17
  };
11
18
  }
19
+ function normalizeDashboardWidgetConfig(config) {
20
+ if (!isRecord(config)) {
21
+ return config;
22
+ }
23
+ const normalized = Object.assign({}, config);
24
+ normalizeWidgetLayoutConfig(normalized);
25
+ if (normalized.table !== undefined) {
26
+ normalized.table = normalizeTableConfig(normalized.table);
27
+ }
28
+ if (normalized.data_source !== undefined) {
29
+ normalized.dataSource = normalizeWidgetDataSource(normalized.data_source);
30
+ }
31
+ const target = normalizeDashboardWidgetTarget(normalized.target);
32
+ if (target !== undefined) {
33
+ normalized.target = target;
34
+ }
35
+ return normalized;
36
+ }
37
+ function serializeDashboardWidgetConfigForEditor(widget) {
38
+ const serialized = Object.assign({}, widget);
39
+ if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
40
+ serialized.min_width = widget.minWidth;
41
+ delete serialized.minWidth;
42
+ }
43
+ if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
44
+ serialized.max_width = widget.maxWidth;
45
+ delete serialized.maxWidth;
46
+ }
47
+ if (widget.table !== undefined) {
48
+ serialized.table = serializeTableConfigForEditor(widget.table);
49
+ }
50
+ if (widget.dataSource !== undefined) {
51
+ serialized.data_source = serializeWidgetDataSourceForEditor(widget.dataSource);
52
+ delete serialized.dataSource;
53
+ }
54
+ return serialized;
55
+ }
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 } : {}));
91
+ }
92
+ function normalizeDashboardWidgetTarget(value) {
93
+ switch (value) {
94
+ case 'empty':
95
+ case 'table':
96
+ case 'chart':
97
+ case 'kpi_card':
98
+ case 'pivot_table':
99
+ case 'gauge_card':
100
+ return value;
101
+ default:
102
+ return undefined;
103
+ }
104
+ }
105
+ function normalizeWidgetLayoutConfig(value) {
106
+ if (value.min_width !== undefined) {
107
+ value.minWidth = value.min_width;
108
+ }
109
+ if (value.max_width !== undefined) {
110
+ value.maxWidth = value.max_width;
111
+ }
112
+ }
113
+ function normalizeTableConfig(value) {
114
+ if (!isRecord(value)) {
115
+ return value;
116
+ }
117
+ const normalized = Object.assign({}, value);
118
+ if (normalized.page_size !== undefined) {
119
+ normalized.pageSize = normalized.page_size;
120
+ }
121
+ return normalized;
122
+ }
123
+ function normalizeWidgetDataSource(value) {
124
+ if (!isRecord(value) || typeof value.type !== 'string') {
125
+ return value;
126
+ }
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;
138
+ }
139
+ function normalizeGroupByRule(value) {
140
+ if (!isRecord(value) || typeof value.type !== 'string') {
141
+ return value;
142
+ }
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;
150
+ }
151
+ function serializeTableConfigForEditor(value) {
152
+ if (!isRecord(value)) {
153
+ return value;
154
+ }
155
+ const serialized = Object.assign({}, value);
156
+ if (Object.prototype.hasOwnProperty.call(serialized, 'pageSize')) {
157
+ serialized.page_size = serialized.pageSize;
158
+ delete serialized.pageSize;
159
+ }
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 } : {}));
165
+ }
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 } : {}));
167
+ }
168
+ function serializeGroupByRuleForEditor(value) {
169
+ if (value.type === 'field') {
170
+ return {
171
+ type: 'field',
172
+ field: value.field,
173
+ };
174
+ }
175
+ return Object.assign({ type: 'date_trunc', field: value.field, truncation: value.truncation }, (value.timezone !== undefined ? { timezone: value.timezone } : {}));
176
+ }
177
+ function asWidgetConfigRecord(value) {
178
+ return isRecord(value) ? value : undefined;
179
+ }
180
+ function getStringField(record, key) {
181
+ const value = record[key];
182
+ return typeof value === 'string' ? value : undefined;
183
+ }
12
184
  function isRecord(value) {
13
185
  return typeof value === 'object' && value !== null;
14
186
  }
@@ -1,5 +1,42 @@
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
+
3
40
  export type DashboardConfig = {
4
41
  version: number
5
42
  groups: DashboardGroupConfig[]
@@ -45,17 +82,57 @@ export type DashboardWidgetConfig = {
45
82
  maxWidth?: number | null
46
83
  order: number
47
84
  target: DashboardWidgetTarget
85
+ dataSource?: WidgetDataSource
48
86
  chart?: ChartWidgetConfig
49
87
  table?: unknown
50
88
  kpi_card?: unknown
51
89
  pivot_table?: unknown
52
90
  gauge_card?: unknown
53
- query?: unknown
54
91
  }
55
92
 
56
93
  export type DashboardWidgetTableData = {
94
+ kind?: 'table'
95
+ columns: string[]
96
+ rows: Record<string, unknown>[]
97
+ pagination?: {
98
+ page: number
99
+ pageSize: number
100
+ total: number
101
+ totalPages: number
102
+ }
103
+ }
104
+
105
+ export type DashboardWidgetAggregateData = {
106
+ kind: 'aggregate'
57
107
  columns: string[]
58
108
  rows: Record<string, unknown>[]
109
+ values?: Record<string, unknown>
110
+ }
111
+
112
+ export type DashboardWidgetData = DashboardWidgetTableData | DashboardWidgetAggregateData
113
+
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'
59
136
  }
60
137
 
61
138
  export function normalizeDashboardConfig(config: unknown): DashboardConfig {
@@ -64,8 +141,282 @@ export function normalizeDashboardConfig(config: unknown): DashboardConfig {
64
141
  return {
65
142
  version: typeof value.version === 'number' ? value.version : 1,
66
143
  groups: Array.isArray(value.groups) ? (value.groups as DashboardGroupConfig[]) : [],
67
- widgets: Array.isArray(value.widgets) ? (value.widgets as DashboardWidgetConfig[]) : [],
144
+ widgets: Array.isArray(value.widgets)
145
+ ? value.widgets.map((widget) => normalizeDashboardWidgetConfig(widget) as DashboardWidgetConfig)
146
+ : [],
147
+ }
148
+ }
149
+
150
+ export function normalizeDashboardWidgetConfig(config: unknown) {
151
+ if (!isRecord(config)) {
152
+ return config
153
+ }
154
+
155
+ const normalized: Record<string, unknown> = { ...config }
156
+ normalizeWidgetLayoutConfig(normalized)
157
+
158
+ if (normalized.table !== undefined) {
159
+ normalized.table = normalizeTableConfig(normalized.table)
160
+ }
161
+
162
+ if (normalized.data_source !== undefined) {
163
+ normalized.dataSource = normalizeWidgetDataSource(normalized.data_source)
164
+ }
165
+
166
+ const target = normalizeDashboardWidgetTarget(normalized.target)
167
+
168
+ if (target !== undefined) {
169
+ normalized.target = target
170
+ }
171
+
172
+ return normalized
173
+ }
174
+
175
+ export function serializeDashboardWidgetConfigForEditor(widget: DashboardWidgetConfig) {
176
+ const serialized: Record<string, unknown> = { ...widget }
177
+
178
+ if (Object.prototype.hasOwnProperty.call(serialized, 'minWidth')) {
179
+ serialized.min_width = widget.minWidth
180
+ delete serialized.minWidth
181
+ }
182
+
183
+ if (Object.prototype.hasOwnProperty.call(serialized, 'maxWidth')) {
184
+ serialized.max_width = widget.maxWidth
185
+ delete serialized.maxWidth
186
+ }
187
+
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
195
+ }
196
+
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
205
+ }
206
+
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 } : {}),
217
+ }
218
+ }
219
+
220
+ export function normalizeGaugeCardWidgetConfig(value: unknown): NormalizedGaugeCardWidgetConfig | undefined {
221
+ const config = asWidgetConfigRecord(value)
222
+
223
+ if (!config) {
224
+ return undefined
225
+ }
226
+
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
+ }
242
+ }
243
+
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
257
+
258
+ return {
259
+ ...(rowField !== undefined ? { rowField } : {}),
260
+ ...(columnField !== undefined ? { columnField } : {}),
261
+ ...(valueField !== undefined ? { valueField } : {}),
262
+ ...(aggregation !== undefined ? { aggregation } : {}),
263
+ }
264
+ }
265
+
266
+ function normalizeDashboardWidgetTarget(value: unknown): DashboardWidgetTarget | undefined {
267
+ switch (value) {
268
+ case 'empty':
269
+ case 'table':
270
+ case 'chart':
271
+ case 'kpi_card':
272
+ case 'pivot_table':
273
+ case 'gauge_card':
274
+ return value
275
+ default:
276
+ return undefined
277
+ }
278
+ }
279
+
280
+ function normalizeWidgetLayoutConfig(value: Record<string, unknown>) {
281
+ if (value.min_width !== undefined) {
282
+ value.minWidth = value.min_width
283
+ }
284
+
285
+ if (value.max_width !== undefined) {
286
+ value.maxWidth = value.max_width
287
+ }
288
+ }
289
+
290
+ function normalizeTableConfig(value: unknown) {
291
+ if (!isRecord(value)) {
292
+ return value
293
+ }
294
+
295
+ const normalized = { ...value }
296
+
297
+ if (normalized.page_size !== undefined) {
298
+ normalized.pageSize = normalized.page_size
299
+ }
300
+
301
+ return normalized
302
+ }
303
+
304
+ function normalizeWidgetDataSource(value: unknown) {
305
+ if (!isRecord(value) || typeof value.type !== 'string') {
306
+ return value
307
+ }
308
+
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
+ }
321
+ }
322
+
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
+ }
333
+ }
334
+
335
+ return value
336
+ }
337
+
338
+ function normalizeGroupByRule(value: unknown) {
339
+ if (!isRecord(value) || typeof value.type !== 'string') {
340
+ return value
341
+ }
342
+
343
+ if (value.type === 'field') {
344
+ return {
345
+ type: 'field',
346
+ ...(value.field !== undefined ? { field: value.field } : {}),
347
+ }
348
+ }
349
+
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 } : {}),
356
+ }
357
+ }
358
+
359
+ return value
360
+ }
361
+
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
68
372
  }
373
+
374
+ return serialized
375
+ }
376
+
377
+ function serializeWidgetDataSourceForEditor(value: WidgetDataSource) {
378
+ if (value.type === 'resource') {
379
+ 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 } : {}),
385
+ }
386
+ }
387
+
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
+ }
395
+ }
396
+
397
+ function serializeGroupByRuleForEditor(value: GroupByRule) {
398
+ if (value.type === 'field') {
399
+ return {
400
+ type: 'field',
401
+ field: value.field,
402
+ }
403
+ }
404
+
405
+ return {
406
+ type: 'date_trunc',
407
+ field: value.field,
408
+ truncation: value.truncation,
409
+ ...(value.timezone !== undefined ? { timezone: value.timezone } : {}),
410
+ }
411
+ }
412
+
413
+ function asWidgetConfigRecord(value: unknown): Record<string, unknown> | undefined {
414
+ return isRecord(value) ? value : undefined
415
+ }
416
+
417
+ function getStringField(record: Record<string, unknown>, key: string) {
418
+ const value = record[key]
419
+ return typeof value === 'string' ? value : undefined
69
420
  }
70
421
 
71
422
  function isRecord(value: unknown): value is Record<string, unknown> {