@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.
- package/README.md +43 -52
- package/custom/composables/useElementSize.ts +17 -2
- package/custom/model/dashboard.types.ts +385 -98
- package/custom/runtime/DashboardRuntime.vue +2 -1
- package/custom/runtime/WidgetRenderer.vue +2 -1
- package/custom/skills/adminforth-dashboard/SKILL.md +8 -4
- package/custom/widgets/chart/ChartWidget.vue +36 -35
- package/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/custom/widgets/chart/chart.types.ts +42 -8
- 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 -43
- package/custom/widgets/kpi-card/KpiCardWidget.vue +6 -10
- package/custom/widgets/pivot-table/PivotTableWidget.vue +10 -11
- package/custom/widgets/table/TableWidget.vue +9 -4
- package/dist/custom/composables/useElementSize.js +14 -2
- package/dist/custom/composables/useElementSize.ts +17 -2
- package/dist/custom/model/dashboard.types.d.ts +179 -38
- package/dist/custom/model/dashboard.types.js +108 -42
- package/dist/custom/model/dashboard.types.ts +385 -98
- package/dist/custom/queries/useDashboardConfig.d.ts +832 -68
- package/dist/custom/queries/useWidgetData.d.ts +828 -64
- package/dist/custom/runtime/DashboardRuntime.vue +2 -1
- package/dist/custom/runtime/WidgetRenderer.vue +2 -1
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +8 -4
- package/dist/custom/widgets/chart/ChartWidget.vue +36 -35
- package/dist/custom/widgets/chart/bar/BarChart.vue +20 -12
- package/dist/custom/widgets/chart/chart.types.d.ts +14 -8
- package/dist/custom/widgets/chart/chart.types.js +23 -0
- package/dist/custom/widgets/chart/chart.types.ts +42 -8
- 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 -43
- package/dist/custom/widgets/kpi-card/KpiCardWidget.vue +6 -10
- package/dist/custom/widgets/pivot-table/PivotTableWidget.vue +10 -11
- package/dist/custom/widgets/table/TableWidget.vue +9 -4
- package/dist/endpoint/widgets.js +23 -3
- package/dist/schema/api.d.ts +2637 -933
- package/dist/schema/widget.d.ts +1562 -582
- package/dist/schema/widget.js +207 -127
- package/dist/services/widgetConfigValidator.js +16 -80
- package/dist/services/widgetDataService.d.ts +0 -9
- package/dist/services/widgetDataService.js +356 -97
- package/endpoint/dashboard.ts +1 -1
- package/endpoint/widgets.ts +29 -3
- package/package.json +1 -1
- package/schema/widget.ts +221 -121
- package/services/widgetConfigValidator.ts +29 -100
- package/services/widgetDataService.ts +478 -129
package/dist/schema/widget.js
CHANGED
|
@@ -6,56 +6,146 @@ 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
|
-
}),
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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:
|
|
148
|
-
query:
|
|
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:
|
|
282
|
+
query: z.union([QueryConfigSchema, FunnelQueryConfigSchema]),
|
|
163
283
|
}).superRefine((widget, ctx) => {
|
|
164
|
-
|
|
165
|
-
|
|
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: ['
|
|
169
|
-
message: '
|
|
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
|
-
|
|
190
|
-
query:
|
|
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
|
-
|
|
204
|
-
query:
|
|
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
|
-
|
|
218
|
-
query:
|
|
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
|
-
|
|
3
|
-
if (widget.target !== 'chart') {
|
|
2
|
+
if (!('query' in widget)) {
|
|
4
3
|
return [];
|
|
5
4
|
}
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
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;
|