@adminforth/dashboard 1.11.1 → 1.12.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 (40) hide show
  1. package/README.md +43 -2
  2. package/custom/api/dashboardApi.ts +72 -10
  3. package/custom/model/dashboard.types.ts +6 -0
  4. package/custom/runtime/DashboardGroup.vue +15 -68
  5. package/custom/runtime/DashboardToolbarButton.vue +32 -0
  6. package/custom/runtime/DashboardToolbarIcon.vue +52 -0
  7. package/custom/runtime/WidgetShell.vue +15 -68
  8. package/custom/skills/adminforth-dashboard/SKILL.md +67 -13
  9. package/custom/widgets/chart/StackedBarChart.vue +64 -7
  10. package/dist/custom/api/dashboardApi.js +59 -10
  11. package/dist/custom/api/dashboardApi.ts +72 -10
  12. package/dist/custom/model/dashboard.types.d.ts +9 -0
  13. package/dist/custom/model/dashboard.types.ts +6 -0
  14. package/dist/custom/queries/useDashboardConfig.d.ts +80 -0
  15. package/dist/custom/queries/useWidgetData.d.ts +80 -0
  16. package/dist/custom/runtime/DashboardGroup.vue +15 -68
  17. package/dist/custom/runtime/DashboardToolbarButton.vue +32 -0
  18. package/dist/custom/runtime/DashboardToolbarIcon.vue +52 -0
  19. package/dist/custom/runtime/WidgetShell.vue +15 -68
  20. package/dist/custom/skills/adminforth-dashboard/SKILL.md +67 -13
  21. package/dist/custom/widgets/chart/StackedBarChart.vue +64 -7
  22. package/dist/schema/api.d.ts +742 -802
  23. package/dist/schema/api.js +2 -2
  24. package/dist/schema/widget.d.ts +75 -81
  25. package/dist/schema/widget.js +1 -1
  26. package/dist/schema/widgets/charts.d.ts +84 -160
  27. package/dist/schema/widgets/charts.js +2 -2
  28. package/dist/schema/widgets/common.d.ts +115 -0
  29. package/dist/schema/widgets/common.js +17 -1
  30. package/dist/schema/widgets/gauge-card.d.ts +8 -0
  31. package/dist/schema/widgets/kpi-card.d.ts +8 -0
  32. package/dist/schema/widgets/pivot-table.d.ts +8 -0
  33. package/dist/schema/widgets/table.d.ts +8 -0
  34. package/dist/services/widgetDataService.js +42 -0
  35. package/package.json +2 -2
  36. package/schema/api.ts +2 -1
  37. package/schema/widget.ts +2 -0
  38. package/schema/widgets/charts.ts +2 -1
  39. package/schema/widgets/common.ts +19 -1
  40. package/services/widgetDataService.ts +68 -0
@@ -117,6 +117,113 @@ export declare const QueryCalcItemSchema: z.ZodObject<{
117
117
  calc: z.ZodString;
118
118
  as: z.ZodString;
119
119
  }, z.core.$strict>;
120
+ export declare const ResourceQueryConfigSchema: z.ZodObject<{
121
+ source: z.ZodOptional<z.ZodLiteral<"resource">>;
122
+ resource: z.ZodString;
123
+ select: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
124
+ field: z.ZodString;
125
+ as: z.ZodOptional<z.ZodString>;
126
+ grain: z.ZodOptional<z.ZodEnum<{
127
+ day: "day";
128
+ week: "week";
129
+ month: "month";
130
+ year: "year";
131
+ }>>;
132
+ }, z.core.$strict>, z.ZodObject<{
133
+ agg: z.ZodEnum<{
134
+ sum: "sum";
135
+ count: "count";
136
+ count_distinct: "count_distinct";
137
+ avg: "avg";
138
+ min: "min";
139
+ max: "max";
140
+ median: "median";
141
+ }>;
142
+ field: z.ZodOptional<z.ZodString>;
143
+ as: z.ZodString;
144
+ filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
145
+ }, z.core.$strict>, z.ZodObject<{
146
+ calc: z.ZodString;
147
+ as: z.ZodString;
148
+ }, z.core.$strict>]>>>;
149
+ sparkline: z.ZodOptional<z.ZodObject<{
150
+ field: z.ZodString;
151
+ grain: z.ZodEnum<{
152
+ day: "day";
153
+ week: "week";
154
+ month: "month";
155
+ year: "year";
156
+ }>;
157
+ as: z.ZodString;
158
+ fill_missing: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
159
+ }, z.core.$strict>>;
160
+ filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
161
+ group_by: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
162
+ field: z.ZodString;
163
+ as: z.ZodOptional<z.ZodString>;
164
+ grain: z.ZodOptional<z.ZodEnum<{
165
+ day: "day";
166
+ week: "week";
167
+ month: "month";
168
+ year: "year";
169
+ }>>;
170
+ timezone: z.ZodOptional<z.ZodString>;
171
+ }, z.core.$strict>]>>>;
172
+ order_by: z.ZodOptional<z.ZodArray<z.ZodObject<{
173
+ field: z.ZodString;
174
+ direction: z.ZodOptional<z.ZodEnum<{
175
+ asc: "asc";
176
+ desc: "desc";
177
+ }>>;
178
+ }, z.core.$strict>>>;
179
+ limit: z.ZodOptional<z.ZodNumber>;
180
+ offset: z.ZodOptional<z.ZodNumber>;
181
+ bucket: z.ZodOptional<z.ZodObject<{
182
+ field: z.ZodString;
183
+ buckets: z.ZodArray<z.ZodObject<{
184
+ label: z.ZodString;
185
+ min: z.ZodOptional<z.ZodNumber>;
186
+ max: z.ZodOptional<z.ZodNumber>;
187
+ }, z.core.$strict>>;
188
+ }, z.core.$strict>>;
189
+ calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
190
+ calc: z.ZodString;
191
+ as: z.ZodString;
192
+ }, z.core.$strict>>>;
193
+ formatting: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
194
+ }, z.core.$strict>;
195
+ export declare const HistogramResourceQueryConfigSchema: z.ZodObject<{
196
+ source: z.ZodOptional<z.ZodLiteral<"resource">>;
197
+ resource: z.ZodString;
198
+ select: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
199
+ field: z.ZodString;
200
+ as: z.ZodOptional<z.ZodString>;
201
+ grain: z.ZodOptional<z.ZodEnum<{
202
+ day: "day";
203
+ week: "week";
204
+ month: "month";
205
+ year: "year";
206
+ }>>;
207
+ }, z.core.$strict>, z.ZodObject<{
208
+ calc: z.ZodString;
209
+ as: z.ZodString;
210
+ }, z.core.$strict>]>>>;
211
+ filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
212
+ order_by: z.ZodOptional<z.ZodArray<z.ZodObject<{
213
+ field: z.ZodString;
214
+ direction: z.ZodOptional<z.ZodEnum<{
215
+ asc: "asc";
216
+ desc: "desc";
217
+ }>>;
218
+ }, z.core.$strict>>>;
219
+ limit: z.ZodOptional<z.ZodNumber>;
220
+ offset: z.ZodOptional<z.ZodNumber>;
221
+ calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
222
+ calc: z.ZodString;
223
+ as: z.ZodString;
224
+ }, z.core.$strict>>>;
225
+ formatting: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
226
+ }, z.core.$strict>;
120
227
  export declare const QueryConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
121
228
  source: z.ZodOptional<z.ZodLiteral<"resource">>;
122
229
  resource: z.ZodString;
@@ -212,6 +319,14 @@ export declare const QueryConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
212
319
  }, z.core.$strict>>;
213
320
  filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
214
321
  }, z.core.$strict>>;
322
+ bucket: z.ZodOptional<z.ZodObject<{
323
+ field: z.ZodString;
324
+ buckets: z.ZodArray<z.ZodObject<{
325
+ label: z.ZodString;
326
+ min: z.ZodOptional<z.ZodNumber>;
327
+ max: z.ZodOptional<z.ZodNumber>;
328
+ }, z.core.$strict>>;
329
+ }, z.core.$strict>>;
215
330
  calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
216
331
  calc: z.ZodString;
217
332
  as: z.ZodString;
@@ -123,7 +123,7 @@ export const QueryCalcItemSchema = z.object({
123
123
  calc: z.string(),
124
124
  as: z.string(),
125
125
  }).strict();
126
- const ResourceQueryConfigSchema = z.object({
126
+ export const ResourceQueryConfigSchema = z.object({
127
127
  source: z.literal('resource').optional(),
128
128
  resource: z.string(),
129
129
  select: z.array(QuerySelectItemSchema).optional(),
@@ -142,6 +142,21 @@ const ResourceQueryConfigSchema = z.object({
142
142
  calcs: z.array(QueryCalcItemSchema).optional(),
143
143
  formatting: z.record(z.string(), z.unknown()).optional(),
144
144
  }).strict();
145
+ const HistogramQuerySelectItemSchema = z.union([
146
+ QueryFieldSelectItemSchema,
147
+ QueryCalcSelectItemSchema,
148
+ ]);
149
+ export const HistogramResourceQueryConfigSchema = z.object({
150
+ source: z.literal('resource').optional(),
151
+ resource: z.string(),
152
+ select: z.array(HistogramQuerySelectItemSchema).optional(),
153
+ filters: FilterExpressionSchema.optional(),
154
+ order_by: z.array(QueryOrderByItemSchema).optional(),
155
+ limit: z.number().int().positive().optional(),
156
+ offset: z.number().int().nonnegative().optional(),
157
+ calcs: z.array(QueryCalcItemSchema).optional(),
158
+ formatting: z.record(z.string(), z.unknown()).optional(),
159
+ }).strict();
145
160
  const StepsQuerySelectStepSchema = z.object({
146
161
  name: z.string(),
147
162
  resource: z.string(),
@@ -153,6 +168,7 @@ export const QueryConfigSchema = z.union([
153
168
  z.object({
154
169
  source: z.literal('steps'),
155
170
  steps: z.array(StepsQuerySelectStepSchema).min(1),
171
+ bucket: BucketConfigSchema.optional(),
156
172
  calcs: z.array(QueryCalcItemSchema).optional(),
157
173
  order_by: z.array(QueryOrderByItemSchema).optional(),
158
174
  limit: z.number().int().positive().optional(),
@@ -190,6 +190,14 @@ export declare const GaugeCardWidgetConfigSchema: z.ZodObject<{
190
190
  }, z.core.$strict>>;
191
191
  filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
192
192
  }, z.core.$strict>>;
193
+ bucket: z.ZodOptional<z.ZodObject<{
194
+ field: z.ZodString;
195
+ buckets: z.ZodArray<z.ZodObject<{
196
+ label: z.ZodString;
197
+ min: z.ZodOptional<z.ZodNumber>;
198
+ max: z.ZodOptional<z.ZodNumber>;
199
+ }, z.core.$strict>>;
200
+ }, z.core.$strict>>;
193
201
  calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
194
202
  calc: z.ZodString;
195
203
  as: z.ZodString;
@@ -230,6 +230,14 @@ export declare const KpiCardWidgetConfigSchema: z.ZodObject<{
230
230
  }, z.core.$strict>>;
231
231
  filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
232
232
  }, z.core.$strict>>;
233
+ bucket: z.ZodOptional<z.ZodObject<{
234
+ field: z.ZodString;
235
+ buckets: z.ZodArray<z.ZodObject<{
236
+ label: z.ZodString;
237
+ min: z.ZodOptional<z.ZodNumber>;
238
+ max: z.ZodOptional<z.ZodNumber>;
239
+ }, z.core.$strict>>;
240
+ }, z.core.$strict>>;
233
241
  calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
234
242
  calc: z.ZodString;
235
243
  as: z.ZodString;
@@ -214,6 +214,14 @@ export declare const PivotTableWidgetConfigSchema: z.ZodObject<{
214
214
  }, z.core.$strict>>;
215
215
  filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
216
216
  }, z.core.$strict>>;
217
+ bucket: z.ZodOptional<z.ZodObject<{
218
+ field: z.ZodString;
219
+ buckets: z.ZodArray<z.ZodObject<{
220
+ label: z.ZodString;
221
+ min: z.ZodOptional<z.ZodNumber>;
222
+ max: z.ZodOptional<z.ZodNumber>;
223
+ }, z.core.$strict>>;
224
+ }, z.core.$strict>>;
217
225
  calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
218
226
  calc: z.ZodString;
219
227
  as: z.ZodString;
@@ -148,6 +148,14 @@ export declare const TableWidgetConfigSchema: z.ZodObject<{
148
148
  }, z.core.$strict>>;
149
149
  filters: z.ZodOptional<z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>>;
150
150
  }, z.core.$strict>>;
151
+ bucket: z.ZodOptional<z.ZodObject<{
152
+ field: z.ZodString;
153
+ buckets: z.ZodArray<z.ZodObject<{
154
+ label: z.ZodString;
155
+ min: z.ZodOptional<z.ZodNumber>;
156
+ max: z.ZodOptional<z.ZodNumber>;
157
+ }, z.core.$strict>>;
158
+ }, z.core.$strict>>;
151
159
  calcs: z.ZodOptional<z.ZodArray<z.ZodObject<{
152
160
  calc: z.ZodString;
153
161
  as: z.ZodString;
@@ -63,6 +63,9 @@ function getQueryWidgetData(adminforth, query, variables) {
63
63
  function getStepsQueryData(adminforth, query, variables) {
64
64
  return __awaiter(this, void 0, void 0, function* () {
65
65
  var _a, _b, _c, _d;
66
+ if (query.bucket) {
67
+ return getBucketedStepsQueryData(adminforth, query, query.bucket, variables);
68
+ }
66
69
  const rows = yield Promise.all(query.steps.map((step) => __awaiter(this, void 0, void 0, function* () {
67
70
  const select = getStepSelect(step);
68
71
  const [values = {}] = yield getAggregateRows(adminforth, step.resource, step.filters, select, []);
@@ -86,6 +89,35 @@ function getStepsQueryData(adminforth, query, variables) {
86
89
  };
87
90
  });
88
91
  }
92
+ function getBucketedStepsQueryData(adminforth, query, bucketConfig, variables) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ var _a, _b, _c, _d;
95
+ const rows = (yield Promise.all(query.steps.map((step) => __awaiter(this, void 0, void 0, function* () {
96
+ const select = getStepSelect(step);
97
+ const stepRows = yield Promise.all(bucketConfig.buckets.map((bucket) => __awaiter(this, void 0, void 0, function* () {
98
+ const [values = {}] = yield getAggregateRows(adminforth, step.resource, mergeFilters(step.filters, getBucketFilter(bucketConfig, bucket)), select, []);
99
+ return buildCalculatedRow(Object.assign({ label: bucket.label, name: step.name, resource: step.resource }, values), select, query.calcs, variables);
100
+ })));
101
+ return stepRows;
102
+ })))).flat();
103
+ const orderedRows = sortRows(rows, query.order_by);
104
+ const slicedRows = typeof query.limit === 'number'
105
+ ? orderedRows.slice((_a = query.offset) !== null && _a !== void 0 ? _a : 0, ((_b = query.offset) !== null && _b !== void 0 ? _b : 0) + query.limit)
106
+ : orderedRows.slice((_c = query.offset) !== null && _c !== void 0 ? _c : 0);
107
+ const columns = Array.from(new Set([
108
+ 'label',
109
+ 'name',
110
+ 'resource',
111
+ ...query.steps.flatMap((step) => getStepSelect(step).map((item) => item.as)),
112
+ ...((_d = query.calcs) !== null && _d !== void 0 ? _d : []).map((item) => item.as),
113
+ ]));
114
+ return {
115
+ kind: 'aggregate',
116
+ columns,
117
+ rows: slicedRows,
118
+ };
119
+ });
120
+ }
89
121
  function getSingleAggregateWidgetData(adminforth, query, aggregate) {
90
122
  return __awaiter(this, void 0, void 0, function* () {
91
123
  var _a;
@@ -256,6 +288,16 @@ function getSingleAggregateSelectItem(query) {
256
288
  function isStepsQuery(query) {
257
289
  return query.source === 'steps';
258
290
  }
291
+ function getBucketFilter(bucketConfig, bucket) {
292
+ const filters = [];
293
+ if (typeof bucket.min === 'number') {
294
+ filters.push({ field: bucketConfig.field, gte: bucket.min });
295
+ }
296
+ if (typeof bucket.max === 'number') {
297
+ filters.push({ field: bucketConfig.field, lt: bucket.max });
298
+ }
299
+ return filters.length ? { and: filters } : undefined;
300
+ }
259
301
  function getStepSelect(step) {
260
302
  return step.select;
261
303
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/dashboard",
3
- "version": "1.11.1",
3
+ "version": "1.12.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  "description": "Dashboard plugin for AdminForth",
22
22
  "devDependencies": {
23
23
  "@types/node": "latest",
24
- "adminforth": "2.61.0-next.9",
24
+ "adminforth": "2.71.0",
25
25
  "semantic-release": "^24.2.1",
26
26
  "semantic-release-slack-bot": "^4.0.2",
27
27
  "typescript": "^5.7.3",
package/schema/api.ts CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  BarChartSchema,
4
4
  FunnelChartSchema,
5
5
  GaugeCardViewConfigSchema,
6
+ HistogramResourceQueryConfigSchema,
6
7
  HistogramChartSchema,
7
8
  KpiCardViewConfigSchema,
8
9
  LineChartSchema,
@@ -151,7 +152,7 @@ const ConfigurablePieChartWidgetConfigSchema = WidgetEditableBaseSchema.extend({
151
152
  const ConfigurableHistogramChartWidgetConfigSchema = WidgetEditableBaseSchema.extend({
152
153
  target: z.literal('chart'),
153
154
  chart: HistogramChartSchema,
154
- query: QueryConfigSchema,
155
+ query: HistogramResourceQueryConfigSchema,
155
156
  })
156
157
 
157
158
  const ConfigurableFunnelChartWidgetConfigSchema = WidgetEditableBaseSchema.extend({
package/schema/widget.ts CHANGED
@@ -33,7 +33,9 @@ export {
33
33
  } from './widgets/table.js'
34
34
  export {
35
35
  EmptyWidgetConfigSchema,
36
+ HistogramResourceQueryConfigSchema,
36
37
  QueryConfigSchema,
38
+ ResourceQueryConfigSchema,
37
39
  WidgetEditableBaseSchema,
38
40
  } from './widgets/common.js'
39
41
  import { ChartWidgetTargetConfigSchema } from './widgets/charts.js'
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod'
2
2
  import {
3
3
  ChartFieldRefSchema,
4
+ HistogramResourceQueryConfigSchema,
4
5
  QueryConfigSchema,
5
6
  WidgetBaseSchema,
6
7
  } from './common.js'
@@ -93,7 +94,7 @@ export const PieChartWidgetConfigSchema = WidgetBaseSchema.extend({
93
94
  export const HistogramChartWidgetConfigSchema = WidgetBaseSchema.extend({
94
95
  target: z.literal('chart'),
95
96
  chart: HistogramChartSchema,
96
- query: QueryConfigSchema,
97
+ query: HistogramResourceQueryConfigSchema,
97
98
  })
98
99
 
99
100
  export const FunnelChartWidgetConfigSchema = WidgetBaseSchema.extend({
@@ -140,7 +140,7 @@ export const QueryCalcItemSchema = z.object({
140
140
  as: z.string(),
141
141
  }).strict()
142
142
 
143
- const ResourceQueryConfigSchema = z.object({
143
+ export const ResourceQueryConfigSchema = z.object({
144
144
  source: z.literal('resource').optional(),
145
145
  resource: z.string(),
146
146
  select: z.array(QuerySelectItemSchema).optional(),
@@ -160,6 +160,23 @@ const ResourceQueryConfigSchema = z.object({
160
160
  formatting: z.record(z.string(), z.unknown()).optional(),
161
161
  }).strict()
162
162
 
163
+ const HistogramQuerySelectItemSchema = z.union([
164
+ QueryFieldSelectItemSchema,
165
+ QueryCalcSelectItemSchema,
166
+ ])
167
+
168
+ export const HistogramResourceQueryConfigSchema = z.object({
169
+ source: z.literal('resource').optional(),
170
+ resource: z.string(),
171
+ select: z.array(HistogramQuerySelectItemSchema).optional(),
172
+ filters: FilterExpressionSchema.optional(),
173
+ order_by: z.array(QueryOrderByItemSchema).optional(),
174
+ limit: z.number().int().positive().optional(),
175
+ offset: z.number().int().nonnegative().optional(),
176
+ calcs: z.array(QueryCalcItemSchema).optional(),
177
+ formatting: z.record(z.string(), z.unknown()).optional(),
178
+ }).strict()
179
+
163
180
  const StepsQuerySelectStepSchema = z.object({
164
181
  name: z.string(),
165
182
  resource: z.string(),
@@ -172,6 +189,7 @@ export const QueryConfigSchema = z.union([
172
189
  z.object({
173
190
  source: z.literal('steps'),
174
191
  steps: z.array(StepsQuerySelectStepSchema).min(1),
192
+ bucket: BucketConfigSchema.optional(),
175
193
  calcs: z.array(QueryCalcItemSchema).optional(),
176
194
  order_by: z.array(QueryOrderByItemSchema).optional(),
177
195
  limit: z.number().int().positive().optional(),
@@ -10,6 +10,7 @@ import type {
10
10
  FilterExpression,
11
11
  QueryAggregateSelectItem,
12
12
  QueryCalcSelectItem,
13
+ QueryBucketConfig,
13
14
  QueryConfig,
14
15
  QueryFieldSelectItem,
15
16
  QueryGroupByItem,
@@ -153,6 +154,10 @@ async function getStepsQueryData(
153
154
  query: Extract<QueryConfig, { source: 'steps' }>,
154
155
  variables: DashboardVariables,
155
156
  ): Promise<DashboardWidgetData> {
157
+ if (query.bucket) {
158
+ return getBucketedStepsQueryData(adminforth, query, query.bucket, variables);
159
+ }
160
+
156
161
  const rows = await Promise.all(query.steps.map(async (step) => {
157
162
  const select = getStepSelect(step);
158
163
  const [values = {}] = await getAggregateRows(
@@ -188,6 +193,52 @@ async function getStepsQueryData(
188
193
  };
189
194
  }
190
195
 
196
+ async function getBucketedStepsQueryData(
197
+ adminforth: IAdminForth,
198
+ query: Extract<QueryConfig, { source: 'steps' }>,
199
+ bucketConfig: QueryBucketConfig,
200
+ variables: DashboardVariables,
201
+ ): Promise<DashboardWidgetData> {
202
+ const rows = (await Promise.all(query.steps.map(async (step) => {
203
+ const select = getStepSelect(step);
204
+ const stepRows = await Promise.all(bucketConfig.buckets.map(async (bucket) => {
205
+ const [values = {}] = await getAggregateRows(
206
+ adminforth,
207
+ step.resource,
208
+ mergeFilters(step.filters, getBucketFilter(bucketConfig, bucket)),
209
+ select,
210
+ [],
211
+ );
212
+
213
+ return buildCalculatedRow({
214
+ label: bucket.label,
215
+ name: step.name,
216
+ resource: step.resource,
217
+ ...values,
218
+ }, select, query.calcs, variables);
219
+ }));
220
+
221
+ return stepRows;
222
+ }))).flat();
223
+ const orderedRows = sortRows(rows, query.order_by);
224
+ const slicedRows = typeof query.limit === 'number'
225
+ ? orderedRows.slice(query.offset ?? 0, (query.offset ?? 0) + query.limit)
226
+ : orderedRows.slice(query.offset ?? 0);
227
+ const columns = Array.from(new Set([
228
+ 'label',
229
+ 'name',
230
+ 'resource',
231
+ ...query.steps.flatMap((step) => getStepSelect(step).map((item) => item.as)),
232
+ ...(query.calcs ?? []).map((item) => item.as),
233
+ ]));
234
+
235
+ return {
236
+ kind: 'aggregate',
237
+ columns,
238
+ rows: slicedRows,
239
+ };
240
+ }
241
+
191
242
  async function getSingleAggregateWidgetData(
192
243
  adminforth: IAdminForth,
193
244
  query: ResourceQueryConfig,
@@ -447,6 +498,23 @@ function isStepsQuery(query: QueryConfig): query is Extract<QueryConfig, { sourc
447
498
  return query.source === 'steps';
448
499
  }
449
500
 
501
+ function getBucketFilter(
502
+ bucketConfig: QueryBucketConfig,
503
+ bucket: QueryBucketConfig['buckets'][number],
504
+ ): FilterExpression | undefined {
505
+ const filters: FilterExpression[] = [];
506
+
507
+ if (typeof bucket.min === 'number') {
508
+ filters.push({ field: bucketConfig.field, gte: bucket.min });
509
+ }
510
+
511
+ if (typeof bucket.max === 'number') {
512
+ filters.push({ field: bucketConfig.field, lt: bucket.max });
513
+ }
514
+
515
+ return filters.length ? { and: filters } : undefined;
516
+ }
517
+
450
518
  function getStepSelect(step: StepsQueryStepConfig): QueryAggregateSelectItem[] {
451
519
  return step.select;
452
520
  }