@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.
- package/README.md +116 -39
- package/custom/api/dashboardApi.ts +4 -0
- package/custom/composables/useElementSize.ts +17 -2
- package/custom/model/dashboard.types.ts +337 -236
- package/custom/skills/adminforth-dashboard/SKILL.md +113 -2
- package/custom/widgets/chart/ChartWidget.vue +38 -53
- package/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/custom/widgets/chart/chart.types.ts +17 -66
- package/custom/widgets/chart/chart.utils.ts +11 -0
- package/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
- package/custom/widgets/chart/line/LineChart.vue +23 -15
- package/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
- package/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
- package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
- package/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
- package/custom/widgets/table/TableWidget.vue +8 -3
- package/dist/custom/api/dashboardApi.d.ts +1 -0
- package/dist/custom/api/dashboardApi.js +5 -0
- package/dist/custom/api/dashboardApi.ts +4 -0
- package/dist/custom/composables/useElementSize.js +14 -2
- package/dist/custom/composables/useElementSize.ts +17 -2
- package/dist/custom/model/dashboard.types.d.ts +181 -61
- package/dist/custom/model/dashboard.types.js +82 -93
- package/dist/custom/model/dashboard.types.ts +337 -236
- package/dist/custom/queries/useDashboardConfig.d.ts +852 -66
- package/dist/custom/queries/useWidgetData.d.ts +848 -62
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +113 -2
- package/dist/custom/widgets/chart/ChartWidget.vue +38 -53
- package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/dist/custom/widgets/chart/chart.types.d.ts +13 -22
- package/dist/custom/widgets/chart/chart.types.js +2 -25
- package/dist/custom/widgets/chart/chart.types.ts +17 -66
- package/dist/custom/widgets/chart/chart.utils.d.ts +1 -0
- package/dist/custom/widgets/chart/chart.utils.js +7 -0
- package/dist/custom/widgets/chart/chart.utils.ts +11 -0
- package/dist/custom/widgets/chart/funnel/FunnelChart.vue +6 -4
- package/dist/custom/widgets/chart/line/LineChart.vue +23 -15
- package/dist/custom/widgets/chart/stacked-bar/StackedBarChart.vue +28 -43
- package/dist/custom/widgets/gauge-card/GaugeCardWidget.vue +7 -12
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -8
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +8 -7
- package/dist/custom/widgets/table/TableWidget.vue +8 -3
- package/dist/endpoint/dashboard.d.ts +7 -2
- package/dist/endpoint/dashboard.js +45 -1
- package/dist/endpoint/widgets.d.ts +2 -1
- package/dist/endpoint/widgets.js +6 -2
- package/dist/schema/api.d.ts +2773 -736
- package/dist/schema/api.js +5 -0
- package/dist/schema/widget.d.ts +1648 -476
- package/dist/schema/widget.js +208 -139
- package/dist/services/widgetConfigValidator.js +16 -40
- package/dist/services/widgetDataService.d.ts +2 -1
- package/dist/services/widgetDataService.js +389 -82
- package/endpoint/dashboard.ts +77 -4
- package/endpoint/widgets.ts +11 -4
- package/package.json +1 -1
- package/schema/api.ts +6 -0
- package/schema/widget.ts +225 -139
- package/services/widgetConfigValidator.ts +29 -53
- package/services/widgetDataService.ts +522 -100
package/dist/schema/widget.js
CHANGED
|
@@ -6,68 +6,165 @@ const DashboardWidgetSizeSchema = z.enum([
|
|
|
6
6
|
'wide',
|
|
7
7
|
'full',
|
|
8
8
|
]);
|
|
9
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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 ${
|
|
77
|
+
message: `field is required for ${item.agg}`,
|
|
26
78
|
});
|
|
27
79
|
}
|
|
28
80
|
});
|
|
29
|
-
|
|
30
|
-
z.
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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:
|
|
139
|
-
|
|
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
|
-
|
|
161
|
-
|
|
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: ['
|
|
165
|
-
message: '
|
|
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
|
-
|
|
186
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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 (
|
|
2
|
+
if (!('query' in widget)) {
|
|
4
3
|
return [];
|
|
5
4
|
}
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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>;
|