@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
@@ -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
- }),
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
+ 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),
54
148
  }).strict();
55
- export const WidgetDataSourceZodSchema = z.discriminatedUnion('type', [
56
- ResourceDataSourceZodSchema,
57
- AggregateDataSourceZodSchema,
58
- ]);
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,115 +222,89 @@ export const ChartConfigSchema = z.discriminatedUnion('type', [
130
222
  HistogramChartSchema,
131
223
  FunnelChartSchema,
132
224
  ]);
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();
133
271
  export const EmptyWidgetConfigSchema = WidgetBaseSchema.extend({
134
272
  target: z.literal('empty'),
135
273
  });
136
274
  const TableWidgetConfigSchema = WidgetBaseSchema.extend({
137
275
  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
- }
276
+ table: TableViewConfigSchema.optional(),
277
+ query: QueryConfigSchema,
155
278
  });
156
279
  const ChartWidgetTargetConfigSchema = WidgetBaseSchema.extend({
157
280
  target: z.literal('chart'),
158
281
  chart: ChartConfigSchema,
282
+ query: z.union([QueryConfigSchema, FunnelQueryConfigSchema]),
159
283
  }).superRefine((widget, ctx) => {
160
- var _a, _b;
161
- if (!widget.dataSource) {
284
+ const isFunnelChart = widget.chart.type === 'funnel';
285
+ const isFunnelQuery = 'steps' in widget.query;
286
+ if (isFunnelChart !== isFunnelQuery) {
162
287
  ctx.addIssue({
163
288
  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',
289
+ path: ['query'],
290
+ message: 'Funnel charts must use steps query, other charts must use resource query',
180
291
  });
181
292
  }
182
293
  });
183
294
  const KpiCardWidgetConfigSchema = WidgetBaseSchema.extend({
184
295
  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
- }
296
+ card: KpiCardViewConfigSchema,
297
+ query: QueryConfigSchema,
202
298
  });
203
299
  const GaugeCardWidgetConfigSchema = WidgetBaseSchema.extend({
204
300
  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
- }
301
+ card: GaugeCardViewConfigSchema,
302
+ query: QueryConfigSchema,
222
303
  });
223
304
  const PivotTableWidgetConfigSchema = WidgetBaseSchema.extend({
224
305
  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
- }
306
+ pivot: PivotTableViewConfigSchema,
307
+ query: QueryConfigSchema,
242
308
  });
243
309
  export const WidgetConfigSchema = z.discriminatedUnion('target', [
244
310
  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
- }