@adminforth/dashboard 1.2.0 → 1.4.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 (60) hide show
  1. package/README.md +116 -39
  2. package/custom/api/dashboardApi.ts +4 -0
  3. package/custom/composables/useElementSize.ts +17 -2
  4. package/custom/model/dashboard.types.ts +337 -236
  5. package/custom/skills/adminforth-dashboard/SKILL.md +113 -2
  6. package/custom/widgets/chart/ChartWidget.vue +38 -53
  7. package/custom/widgets/chart/bar/BarChart.vue +20 -12
  8. package/custom/widgets/chart/chart.types.ts +17 -66
  9. package/custom/widgets/chart/chart.utils.ts +11 -0
  10. package/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  11. package/custom/widgets/chart/line/LineChart.vue +23 -15
  12. package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  13. package/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
  14. package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  15. package/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
  16. package/custom/widgets/table/TableWidget.vue +8 -3
  17. package/dist/custom/api/dashboardApi.d.ts +1 -0
  18. package/dist/custom/api/dashboardApi.js +5 -0
  19. package/dist/custom/api/dashboardApi.ts +4 -0
  20. package/dist/custom/composables/useElementSize.js +14 -2
  21. package/dist/custom/composables/useElementSize.ts +17 -2
  22. package/dist/custom/model/dashboard.types.d.ts +181 -61
  23. package/dist/custom/model/dashboard.types.js +82 -93
  24. package/dist/custom/model/dashboard.types.ts +337 -236
  25. package/dist/custom/queries/useDashboardConfig.d.ts +852 -66
  26. package/dist/custom/queries/useWidgetData.d.ts +848 -62
  27. package/dist/custom/skills/adminforth-dashboard/SKILL.md +113 -2
  28. package/dist/custom/widgets/chart/ChartWidget.vue +38 -53
  29. package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
  30. package/dist/custom/widgets/chart/chart.types.d.ts +13 -22
  31. package/dist/custom/widgets/chart/chart.types.js +2 -25
  32. package/dist/custom/widgets/chart/chart.types.ts +17 -66
  33. package/dist/custom/widgets/chart/chart.utils.d.ts +1 -0
  34. package/dist/custom/widgets/chart/chart.utils.js +7 -0
  35. package/dist/custom/widgets/chart/chart.utils.ts +11 -0
  36. package/dist/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
  37. package/dist/custom/widgets/chart/line/LineChart.vue +23 -15
  38. package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
  39. package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
  40. package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
  41. package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
  42. package/dist/custom/widgets/table/TableWidget.vue +8 -3
  43. package/dist/endpoint/dashboard.d.ts +7 -2
  44. package/dist/endpoint/dashboard.js +45 -1
  45. package/dist/endpoint/widgets.d.ts +2 -1
  46. package/dist/endpoint/widgets.js +6 -2
  47. package/dist/schema/api.d.ts +2773 -736
  48. package/dist/schema/api.js +5 -0
  49. package/dist/schema/widget.d.ts +1648 -476
  50. package/dist/schema/widget.js +208 -139
  51. package/dist/services/widgetConfigValidator.js +16 -40
  52. package/dist/services/widgetDataService.d.ts +2 -1
  53. package/dist/services/widgetDataService.js +389 -82
  54. package/endpoint/dashboard.ts +77 -4
  55. package/endpoint/widgets.ts +11 -4
  56. package/package.json +1 -1
  57. package/schema/api.ts +6 -0
  58. package/schema/widget.ts +225 -139
  59. package/services/widgetConfigValidator.ts +29 -53
  60. package/services/widgetDataService.ts +522 -100
@@ -6,68 +6,165 @@ const DashboardWidgetSizeSchema = z.enum([
6
6
  'wide',
7
7
  'full',
8
8
  ]);
9
- export const AggregationOperationZodSchema = z.enum([
9
+ const ValueFormatSchema = z.enum([
10
+ 'number',
11
+ 'compact_number',
12
+ 'currency',
13
+ 'percent',
14
+ 'percent_delta',
15
+ 'number_delta',
16
+ 'currency_delta',
17
+ ]).optional();
18
+ const ChartFieldRefSchema = z.object({
19
+ field: z.string(),
20
+ label: z.string().optional(),
21
+ format: ValueFormatSchema,
22
+ }).strict();
23
+ const FieldRefSchema = z.union([
24
+ z.string(),
25
+ z.object({
26
+ field: z.string(),
27
+ label: z.string().optional(),
28
+ format: ValueFormatSchema,
29
+ }).strict(),
30
+ ]);
31
+ const FilterExpressionSchema = z.lazy(() => z.union([
32
+ z.array(FilterExpressionSchema),
33
+ z.object({
34
+ and: z.array(FilterExpressionSchema),
35
+ }).strict(),
36
+ z.object({
37
+ or: z.array(FilterExpressionSchema),
38
+ }).strict(),
39
+ z.object({
40
+ field: z.string(),
41
+ eq: z.unknown().optional(),
42
+ neq: z.unknown().optional(),
43
+ gt: z.unknown().optional(),
44
+ gte: z.unknown().optional(),
45
+ lt: z.unknown().optional(),
46
+ lte: z.unknown().optional(),
47
+ in: z.array(z.unknown()).optional(),
48
+ not_in: z.array(z.unknown()).optional(),
49
+ like: z.unknown().optional(),
50
+ ilike: z.unknown().optional(),
51
+ }).strict(),
52
+ ]));
53
+ const QueryAggregateOperationSchema = z.enum([
10
54
  'sum',
11
55
  'count',
56
+ 'count_distinct',
12
57
  'avg',
13
58
  'min',
14
59
  'max',
15
60
  'median',
16
61
  ]);
17
- export const AggregationRuleZodSchema = z.object({
18
- operation: AggregationOperationZodSchema,
62
+ const QueryFieldSelectItemSchema = z.object({
63
+ field: z.string(),
64
+ as: z.string().optional(),
65
+ grain: z.enum(['hour', 'day', 'week', 'month', 'quarter', 'year']).optional(),
66
+ }).strict();
67
+ const QueryAggregateSelectItemSchema = z.object({
68
+ agg: QueryAggregateOperationSchema,
19
69
  field: z.string().optional(),
20
- }).superRefine((rule, ctx) => {
21
- if (rule.operation !== 'count' && !rule.field) {
70
+ as: z.string(),
71
+ filters: FilterExpressionSchema.optional(),
72
+ }).strict().superRefine((item, ctx) => {
73
+ if (!['count'].includes(item.agg) && !item.field) {
22
74
  ctx.addIssue({
23
75
  code: z.ZodIssueCode.custom,
24
76
  path: ['field'],
25
- message: `field is required for ${rule.operation}`,
77
+ message: `field is required for ${item.agg}`,
26
78
  });
27
79
  }
28
80
  });
29
- export const GroupByRuleZodSchema = z.discriminatedUnion('type', [
30
- z.object({
31
- type: z.literal('field'),
32
- field: z.string(),
33
- }),
81
+ const QueryCalcSelectItemSchema = z.object({
82
+ calc: z.string(),
83
+ as: z.string(),
84
+ }).strict();
85
+ const QuerySelectItemSchema = z.union([
86
+ QueryFieldSelectItemSchema,
87
+ QueryAggregateSelectItemSchema,
88
+ QueryCalcSelectItemSchema,
89
+ ]);
90
+ const QueryGroupByItemSchema = z.union([
91
+ z.string(),
34
92
  z.object({
35
- type: z.literal('date_trunc'),
36
93
  field: z.string(),
37
- truncation: z.enum(['day', 'week', 'month', 'year']),
94
+ as: z.string().optional(),
95
+ grain: z.enum(['hour', 'day', 'week', 'month', 'quarter', 'year']).optional(),
38
96
  timezone: z.string().optional(),
39
- }),
97
+ }).strict(),
40
98
  ]);
41
- export const AggregateDataSourceZodSchema = z.object({
42
- type: z.literal('aggregate'),
43
- resourceId: z.string(),
44
- aggregations: z.record(z.string(), AggregationRuleZodSchema),
45
- groupBy: GroupByRuleZodSchema.optional(),
46
- filters: z.unknown().optional(),
99
+ const QueryOrderByItemSchema = z.object({
100
+ field: z.string(),
101
+ direction: z.enum(['asc', 'desc']).optional(),
47
102
  }).strict();
48
- export const ResourceDataSourceZodSchema = z.object({
49
- type: z.literal('resource'),
50
- resourceId: z.string(),
51
- columns: z.array(z.string()).optional(),
52
- filters: z.unknown().optional(),
53
- sort: z.unknown().optional(),
103
+ const TimeSeriesConfigSchema = z.object({
104
+ field: z.string(),
105
+ grain: z.enum(['hour', 'day', 'week', 'month', 'quarter', 'year']),
106
+ timezone: z.string().optional(),
107
+ }).strict();
108
+ const PeriodConfigSchema = z.object({
109
+ field: z.string(),
110
+ gte: z.unknown().optional(),
111
+ lt: z.unknown().optional(),
112
+ }).strict();
113
+ const BucketConfigSchema = z.object({
114
+ field: z.string(),
115
+ buckets: z.array(z.object({
116
+ label: z.string(),
117
+ min: z.number().optional(),
118
+ max: z.number().optional(),
119
+ }).strict()),
120
+ }).strict();
121
+ const QueryCalcItemSchema = z.object({
122
+ calc: z.string(),
123
+ as: z.string(),
124
+ }).strict();
125
+ const FormattingConfigSchema = z.record(z.string(), z.unknown());
126
+ const VariablesConfigSchema = z.record(z.string(), z.unknown());
127
+ export const QueryConfigSchema = z.object({
128
+ resource: z.string(),
129
+ select: z.array(QuerySelectItemSchema).optional(),
130
+ filters: FilterExpressionSchema.optional(),
131
+ groupBy: z.array(QueryGroupByItemSchema).optional(),
132
+ orderBy: z.array(QueryOrderByItemSchema).optional(),
133
+ limit: z.number().int().positive().optional(),
134
+ offset: z.number().int().nonnegative().optional(),
135
+ timeSeries: TimeSeriesConfigSchema.optional(),
136
+ period: PeriodConfigSchema.optional(),
137
+ bucket: BucketConfigSchema.optional(),
138
+ calcs: z.array(QueryCalcItemSchema).optional(),
139
+ formatting: FormattingConfigSchema.optional(),
140
+ }).strict();
141
+ const FunnelQueryStepSchema = z.object({
142
+ name: z.string(),
143
+ resource: z.string(),
144
+ metric: QueryAggregateSelectItemSchema,
145
+ filters: FilterExpressionSchema.optional(),
146
+ }).strict();
147
+ export const FunnelQueryConfigSchema = z.object({
148
+ steps: z.array(FunnelQueryStepSchema).min(1),
149
+ calcs: z.array(QueryCalcItemSchema).optional(),
54
150
  }).strict();
55
- export const WidgetDataSourceZodSchema = z.discriminatedUnion('type', [
56
- ResourceDataSourceZodSchema,
57
- AggregateDataSourceZodSchema,
58
- ]);
59
151
  const WidgetBaseSchema = z.object({
60
152
  id: z.string().optional(),
61
153
  group_id: z.string().optional(),
62
154
  label: z.string().optional(),
155
+ variables: VariablesConfigSchema.optional(),
63
156
  size: DashboardWidgetSizeSchema.optional(),
64
157
  width: z.number().positive('Width must be greater than 0').optional(),
65
158
  height: z.number().positive('Height must be greater than 0').optional(),
66
159
  minWidth: z.number().nonnegative('Min width must be a non-negative number').optional(),
67
160
  maxWidth: z.number().nonnegative('Max width must be a non-negative number').nullable().optional(),
68
161
  order: z.number().optional(),
69
- dataSource: WidgetDataSourceZodSchema.optional(),
70
162
  });
163
+ const TableViewConfigSchema = z.object({
164
+ columns: z.array(FieldRefSchema).optional(),
165
+ pagination: z.boolean().optional(),
166
+ pageSize: z.number().int().positive().optional(),
167
+ }).strict();
71
168
  const ChartBaseSchema = z.object({
72
169
  title: z.string().optional(),
73
170
  });
@@ -75,51 +172,49 @@ const ChartBucketSchema = z.object({
75
172
  label: z.string().min(1, 'Bucket label is required'),
76
173
  min: z.number().optional(),
77
174
  max: z.number().optional(),
78
- });
79
- const ChartSeriesSchema = z.object({
80
- name: z.string().min(1, 'Series name is required'),
81
- field: z.string().min(1, 'Series field is required'),
82
- color: z.string().optional(),
83
- });
175
+ }).strict();
176
+ const ChartSeriesRefSchema = z.object({
177
+ field: z.string(),
178
+ label: z.string().optional(),
179
+ }).strict();
84
180
  const LineChartSchema = ChartBaseSchema.extend({
85
181
  type: z.literal('line'),
86
- x_field: z.string().optional(),
87
- y_field: z.string().optional(),
88
- series_name: z.string().optional(),
182
+ x: ChartFieldRefSchema,
183
+ y: z.array(ChartFieldRefSchema).min(1),
184
+ series: ChartSeriesRefSchema.optional(),
89
185
  color: z.string().optional(),
186
+ colors: z.array(z.string()).optional(),
90
187
  });
91
188
  const BarChartSchema = ChartBaseSchema.extend({
92
189
  type: z.literal('bar'),
93
- label_field: z.string().optional(),
94
- value_field: z.string().optional(),
95
- bucket_field: z.string().optional(),
96
- buckets: z.array(ChartBucketSchema).optional(),
190
+ x: ChartFieldRefSchema,
191
+ y: ChartFieldRefSchema,
97
192
  color: z.string().optional(),
98
193
  });
99
194
  const StackedBarChartSchema = ChartBaseSchema.extend({
100
195
  type: z.literal('stacked_bar'),
101
- x_field: z.string().optional(),
102
- series: z.array(ChartSeriesSchema).optional(),
196
+ x: ChartFieldRefSchema,
197
+ y: z.union([ChartFieldRefSchema, z.array(ChartFieldRefSchema).min(1)]),
198
+ series: ChartSeriesRefSchema.optional(),
103
199
  colors: z.array(z.string()).optional(),
104
200
  });
105
201
  const PieChartSchema = ChartBaseSchema.extend({
106
202
  type: z.literal('pie'),
107
- label_field: z.string().optional(),
108
- value_field: z.string().optional(),
203
+ label: ChartFieldRefSchema,
204
+ value: ChartFieldRefSchema,
109
205
  colors: z.array(z.string()).optional(),
110
206
  });
111
207
  const HistogramChartSchema = ChartBaseSchema.extend({
112
208
  type: z.literal('histogram'),
113
- label_field: z.string().optional(),
114
- value_field: z.string().optional(),
115
- bucket_field: z.string().optional(),
209
+ x: ChartFieldRefSchema,
210
+ y: ChartFieldRefSchema,
116
211
  buckets: z.array(ChartBucketSchema).optional(),
117
212
  color: z.string().optional(),
118
213
  });
119
214
  const FunnelChartSchema = ChartBaseSchema.extend({
120
215
  type: z.literal('funnel'),
121
- label_field: z.string().optional(),
122
- value_field: z.string().optional(),
216
+ label: ChartFieldRefSchema.optional(),
217
+ value: ChartFieldRefSchema.optional(),
123
218
  colors: z.array(z.string()).optional(),
124
219
  });
125
220
  export const ChartConfigSchema = z.discriminatedUnion('type', [
@@ -130,115 +225,89 @@ export const ChartConfigSchema = z.discriminatedUnion('type', [
130
225
  HistogramChartSchema,
131
226
  FunnelChartSchema,
132
227
  ]);
228
+ const KpiCardViewConfigSchema = z.object({
229
+ title: z.string().optional(),
230
+ value: z.object({
231
+ field: z.string(),
232
+ format: ValueFormatSchema,
233
+ prefix: z.string().optional(),
234
+ suffix: z.string().optional(),
235
+ }).strict(),
236
+ subtitle: z.object({
237
+ text: z.string().optional(),
238
+ field: z.string().optional(),
239
+ }).strict().optional(),
240
+ comparison: z.unknown().optional(),
241
+ sparkline: z.unknown().optional(),
242
+ }).strict();
243
+ const GaugeCardViewConfigSchema = z.object({
244
+ title: z.string().optional(),
245
+ value: z.object({
246
+ field: z.string(),
247
+ format: ValueFormatSchema,
248
+ prefix: z.string().optional(),
249
+ suffix: z.string().optional(),
250
+ }).strict(),
251
+ target: z.object({
252
+ value: z.number().optional(),
253
+ field: z.string().optional(),
254
+ label: z.string().optional(),
255
+ }).strict().optional(),
256
+ progress: z.object({
257
+ valueField: z.string(),
258
+ targetValue: z.number().optional(),
259
+ targetField: z.string().optional(),
260
+ format: ValueFormatSchema,
261
+ }).strict().optional(),
262
+ color: z.string().optional(),
263
+ }).strict();
264
+ const PivotTableViewConfigSchema = z.object({
265
+ rows: z.array(FieldRefSchema).min(1),
266
+ columns: z.array(FieldRefSchema).optional(),
267
+ values: z.array(z.object({
268
+ field: z.string(),
269
+ label: z.string().optional(),
270
+ format: ValueFormatSchema,
271
+ aggregation: z.enum(['sum', 'count', 'avg', 'min', 'max']).optional(),
272
+ }).strict()).min(1),
273
+ }).strict();
133
274
  export const EmptyWidgetConfigSchema = WidgetBaseSchema.extend({
134
275
  target: z.literal('empty'),
135
276
  });
136
277
  const TableWidgetConfigSchema = WidgetBaseSchema.extend({
137
278
  target: z.literal('table'),
138
- table: z.unknown().optional(),
139
- }).superRefine((widget, ctx) => {
140
- var _a;
141
- if (!widget.dataSource) {
142
- ctx.addIssue({
143
- code: z.ZodIssueCode.custom,
144
- path: ['dataSource'],
145
- message: 'Table widget must have dataSource config',
146
- });
147
- }
148
- if (((_a = widget.dataSource) === null || _a === void 0 ? void 0 : _a.type) === 'aggregate') {
149
- ctx.addIssue({
150
- code: z.ZodIssueCode.custom,
151
- path: ['dataSource'],
152
- message: 'Table widget dataSource must use resource type',
153
- });
154
- }
279
+ table: TableViewConfigSchema.optional(),
280
+ query: QueryConfigSchema,
155
281
  });
156
282
  const ChartWidgetTargetConfigSchema = WidgetBaseSchema.extend({
157
283
  target: z.literal('chart'),
158
284
  chart: ChartConfigSchema,
285
+ query: z.union([QueryConfigSchema, FunnelQueryConfigSchema]),
159
286
  }).superRefine((widget, ctx) => {
160
- var _a, _b;
161
- if (!widget.dataSource) {
287
+ const isFunnelChart = widget.chart.type === 'funnel';
288
+ const isFunnelQuery = 'steps' in widget.query;
289
+ if (isFunnelChart && !isFunnelQuery) {
162
290
  ctx.addIssue({
163
291
  code: z.ZodIssueCode.custom,
164
- path: ['dataSource'],
165
- message: 'Chart widget must have dataSource config',
166
- });
167
- }
168
- if (((_a = widget.dataSource) === null || _a === void 0 ? void 0 : _a.type) === 'resource') {
169
- ctx.addIssue({
170
- code: z.ZodIssueCode.custom,
171
- path: ['dataSource'],
172
- message: 'Chart widget dataSource must use aggregate type',
173
- });
174
- }
175
- if (((_b = widget.dataSource) === null || _b === void 0 ? void 0 : _b.type) === 'aggregate' && !widget.dataSource.groupBy) {
176
- ctx.addIssue({
177
- code: z.ZodIssueCode.custom,
178
- path: ['dataSource', 'groupBy'],
179
- message: 'Chart widget aggregate dataSource must define groupBy',
292
+ path: ['query'],
293
+ message: 'Funnel charts must use steps query',
180
294
  });
181
295
  }
182
296
  });
183
297
  const KpiCardWidgetConfigSchema = WidgetBaseSchema.extend({
184
298
  target: z.literal('kpi_card'),
185
- kpi_card: z.unknown().optional(),
186
- }).superRefine((widget, ctx) => {
187
- var _a;
188
- if (!widget.dataSource) {
189
- ctx.addIssue({
190
- code: z.ZodIssueCode.custom,
191
- path: ['dataSource'],
192
- message: 'KPI card widget must have dataSource config',
193
- });
194
- }
195
- if (((_a = widget.dataSource) === null || _a === void 0 ? void 0 : _a.type) === 'aggregate' && widget.dataSource.groupBy) {
196
- ctx.addIssue({
197
- code: z.ZodIssueCode.custom,
198
- path: ['dataSource', 'groupBy'],
199
- message: 'KPI card aggregate dataSource must not define groupBy',
200
- });
201
- }
299
+ card: KpiCardViewConfigSchema,
300
+ query: QueryConfigSchema,
202
301
  });
203
302
  const GaugeCardWidgetConfigSchema = WidgetBaseSchema.extend({
204
303
  target: z.literal('gauge_card'),
205
- gauge_card: z.unknown().optional(),
206
- }).superRefine((widget, ctx) => {
207
- var _a;
208
- if (!widget.dataSource) {
209
- ctx.addIssue({
210
- code: z.ZodIssueCode.custom,
211
- path: ['dataSource'],
212
- message: 'Gauge card widget must have dataSource config',
213
- });
214
- }
215
- if (((_a = widget.dataSource) === null || _a === void 0 ? void 0 : _a.type) === 'aggregate' && widget.dataSource.groupBy) {
216
- ctx.addIssue({
217
- code: z.ZodIssueCode.custom,
218
- path: ['dataSource', 'groupBy'],
219
- message: 'Gauge card aggregate dataSource must not define groupBy',
220
- });
221
- }
304
+ card: GaugeCardViewConfigSchema,
305
+ query: QueryConfigSchema,
222
306
  });
223
307
  const PivotTableWidgetConfigSchema = WidgetBaseSchema.extend({
224
308
  target: z.literal('pivot_table'),
225
- pivot_table: z.unknown().optional(),
226
- }).superRefine((widget, ctx) => {
227
- var _a;
228
- if (!widget.dataSource) {
229
- ctx.addIssue({
230
- code: z.ZodIssueCode.custom,
231
- path: ['dataSource'],
232
- message: 'Pivot table widget must have dataSource config',
233
- });
234
- }
235
- if (((_a = widget.dataSource) === null || _a === void 0 ? void 0 : _a.type) === 'aggregate' && !widget.dataSource.groupBy) {
236
- ctx.addIssue({
237
- code: z.ZodIssueCode.custom,
238
- path: ['dataSource', 'groupBy'],
239
- message: 'Pivot table aggregate dataSource must define groupBy',
240
- });
241
- }
309
+ pivot: PivotTableViewConfigSchema,
310
+ query: QueryConfigSchema,
242
311
  });
243
312
  export const WidgetConfigSchema = z.discriminatedUnion('target', [
244
313
  TableWidgetConfigSchema,
@@ -1,51 +1,27 @@
1
- import { normalizeChartWidgetConfig } from '../custom/widgets/chart/chart.types.js';
2
1
  export function validateDashboardWidgetApiConfig(adminforth, widget) {
3
- if (widget.target !== 'chart') {
2
+ if (!('query' in widget)) {
4
3
  return [];
5
4
  }
6
- const errors = [];
7
- const chart = normalizeChartWidgetConfig(widget.chart);
8
- if (!chart) {
9
- errors.push({
10
- field: 'chart',
11
- message: 'Chart widget must have chart config',
12
- });
13
- return errors;
5
+ if ('steps' in widget.query) {
6
+ return widget.query.steps.flatMap((step, index) => validateResource(adminforth, step.resource, `query.steps.${index}.resource`));
14
7
  }
15
- const aggregateDataSource = getAggregateDataSource(widget.dataSource);
16
- if (aggregateDataSource) {
17
- const resource = adminforth.config.resources.find((item) => item.resourceId === aggregateDataSource.resourceId);
18
- if (!resource) {
19
- errors.push({
20
- field: 'data_source.resource_id',
21
- message: `Resource "${aggregateDataSource.resourceId}" is not registered`,
22
- });
23
- }
24
- if (!aggregateDataSource.groupBy) {
25
- errors.push({
26
- field: 'data_source.group_by',
27
- message: 'Chart aggregate dataSource must define groupBy',
28
- });
29
- }
30
- return errors;
8
+ return validateQueryConfig(adminforth, widget.query, 'query');
9
+ }
10
+ function validateQueryConfig(adminforth, query, fieldPrefix) {
11
+ return validateResource(adminforth, query.resource, `${fieldPrefix}.resource`);
12
+ }
13
+ function validateResource(adminforth, resourceId, field) {
14
+ const resource = adminforth.config.resources.find((item) => item.resourceId === resourceId);
15
+ if (resource) {
16
+ return [];
31
17
  }
32
- errors.push({
33
- field: 'data_source',
34
- message: 'Chart widget must have aggregate dataSource config',
35
- });
36
- return errors;
18
+ return [{
19
+ field,
20
+ message: `Resource "${resourceId}" is not registered`,
21
+ }];
37
22
  }
38
23
  export function createWidgetConfigValidatorService(adminforth) {
39
24
  return {
40
25
  validateDashboardWidgetApiConfig: (widget) => validateDashboardWidgetApiConfig(adminforth, widget),
41
26
  };
42
27
  }
43
- function getAggregateDataSource(dataSource) {
44
- if (typeof dataSource !== 'object'
45
- || dataSource === null
46
- || dataSource.type !== 'aggregate'
47
- || typeof dataSource.resourceId !== 'string') {
48
- return null;
49
- }
50
- return dataSource;
51
- }
@@ -1,10 +1,11 @@
1
1
  import type { IAdminForth } from 'adminforth';
2
- import type { DashboardWidgetConfig, DashboardWidgetData } from '../custom/model/dashboard.types.js';
2
+ import type { DashboardWidgetConfig, DashboardWidgetData, DashboardVariables } from '../custom/model/dashboard.types.js';
3
3
  export type DashboardWidgetDataOptions = {
4
4
  pagination?: {
5
5
  page: number;
6
6
  pageSize: number;
7
7
  };
8
+ variables?: DashboardVariables;
8
9
  };
9
10
  export type WidgetDataService = {
10
11
  getWidgetData: (widget: DashboardWidgetConfig, options?: DashboardWidgetDataOptions) => Promise<DashboardWidgetData | null>;