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