@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
@@ -7,142 +7,401 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { Aggregates, GroupBy, Sorts } from 'adminforth';
10
+ import { Filters, Sorts } from 'adminforth';
11
+ const NOW_MINUS_RE = /^(\d+)([dhw])$/;
12
+ const CALC_IDENTIFIER_RE = /\b[a-zA-Z_][a-zA-Z0-9_]*\b/g;
13
+ const SAFE_CALC_EXPRESSION_RE = /^[\d+\-*/().\s]+$/;
11
14
  export function getWidgetData(adminforth_1, widget_1) {
12
15
  return __awaiter(this, arguments, void 0, function* (adminforth, widget, options = {}) {
13
- const legacyQuery = getLegacyQueryConfig(widget.query);
14
- const dataSource = getWidgetDataSource(widget, legacyQuery);
15
- if (!dataSource) {
16
+ if (!('query' in widget)) {
16
17
  return null;
17
18
  }
18
- if (dataSource.type === 'aggregate') {
19
- return getAggregateWidgetData(adminforth, dataSource);
19
+ const data = 'steps' in widget.query
20
+ ? yield getFunnelWidgetData(adminforth, widget.query)
21
+ : yield getQueryWidgetData(adminforth, widget.query);
22
+ if (widget.target !== 'table' || !options.pagination) {
23
+ return data;
20
24
  }
21
- return getResourceWidgetData(adminforth, dataSource, legacyQuery, options);
25
+ const page = options.pagination.page;
26
+ const pageSize = options.pagination.pageSize;
27
+ const offset = (page - 1) * pageSize;
28
+ return Object.assign(Object.assign({}, data), { rows: data.rows.slice(offset, offset + pageSize), pagination: {
29
+ page,
30
+ pageSize,
31
+ total: data.rows.length,
32
+ totalPages: Math.max(Math.ceil(data.rows.length / pageSize), 1),
33
+ } });
22
34
  });
23
35
  }
24
- function getResourceWidgetData(adminforth, dataSource, legacyQuery, options) {
36
+ function getFunnelWidgetData(adminforth, query) {
25
37
  return __awaiter(this, void 0, void 0, function* () {
26
- var _a, _b, _c, _d;
27
- const resource = adminforth.resource(dataSource.resourceId);
28
- const filters = normalizeFilters(dataSource.filters);
29
- const sort = normalizeSort((_a = dataSource.sort) !== null && _a !== void 0 ? _a : legacyQuery === null || legacyQuery === void 0 ? void 0 : legacyQuery.order);
30
- const pagination = options.pagination;
31
- const offset = pagination ? (pagination.page - 1) * pagination.pageSize : 0;
32
- const queryLimit = legacyQuery === null || legacyQuery === void 0 ? void 0 : legacyQuery.limit;
33
- const limit = pagination
34
- ? Math.max(Math.min(pagination.pageSize, (queryLimit !== null && queryLimit !== void 0 ? queryLimit : Infinity) - offset), 0)
35
- : queryLimit;
36
- const rows = yield resource.list(filters, limit !== null && limit !== void 0 ? limit : undefined, offset, sort);
37
- const columns = (_c = (_b = dataSource.columns) !== null && _b !== void 0 ? _b : legacyQuery === null || legacyQuery === void 0 ? void 0 : legacyQuery.select) !== null && _c !== void 0 ? _c : Object.keys((_d = rows[0]) !== null && _d !== void 0 ? _d : {});
38
- const total = pagination ? Math.min(yield resource.count(filters), queryLimit !== null && queryLimit !== void 0 ? queryLimit : Infinity) : 0;
39
- return Object.assign({ columns, rows: rows.map((row) => (Object.fromEntries(columns.map((column) => [column, row[column]])))) }, (pagination ? {
40
- pagination: {
41
- page: pagination.page,
42
- pageSize: pagination.pageSize,
43
- total,
44
- totalPages: Math.max(Math.ceil(total / pagination.pageSize), 1),
45
- },
46
- } : {}));
38
+ const rows = yield Promise.all(query.steps.map((step) => __awaiter(this, void 0, void 0, function* () {
39
+ const valueField = step.metric.as;
40
+ const sourceRows = yield getResourceRows(adminforth, step.resource, step.filters);
41
+ return {
42
+ name: step.name,
43
+ [valueField]: calculateAggregate(sourceRows, step.metric),
44
+ };
45
+ })));
46
+ return {
47
+ kind: 'aggregate',
48
+ columns: ['name', ...Array.from(new Set(query.steps.map((step) => step.metric.as)))],
49
+ rows,
50
+ };
47
51
  });
48
52
  }
49
- function getAggregateWidgetData(adminforth, dataSource) {
53
+ function getQueryWidgetData(adminforth, query) {
50
54
  return __awaiter(this, void 0, void 0, function* () {
51
- var _a, _b;
52
- const resource = adminforth.resource(dataSource.resourceId);
53
- const rows = yield resource.aggregate(normalizeFilters(dataSource.filters), Object.fromEntries(Object.entries(dataSource.aggregations).map(([alias, rule]) => [
54
- alias,
55
- createAggregationRule(rule),
56
- ])), dataSource.groupBy ? createGroupByRule(dataSource.groupBy) : undefined);
57
- const columns = Object.keys((_a = rows[0]) !== null && _a !== void 0 ? _a : {});
58
- if (!dataSource.groupBy) {
59
- const values = (_b = rows[0]) !== null && _b !== void 0 ? _b : {};
60
- return {
61
- kind: 'aggregate',
62
- columns: Object.keys(values),
63
- rows: Object.keys(values).length ? [values] : [],
64
- values,
65
- };
55
+ var _a, _b, _c;
56
+ const rows = yield getResourceRows(adminforth, query.resource, query.filters, getBackendSort(query.orderBy));
57
+ const selectedRows = buildQueryRows(rows, query);
58
+ const orderedRows = sortRows(selectedRows, query.orderBy);
59
+ const slicedRows = typeof query.limit === 'number'
60
+ ? orderedRows.slice((_a = query.offset) !== null && _a !== void 0 ? _a : 0, ((_b = query.offset) !== null && _b !== void 0 ? _b : 0) + query.limit)
61
+ : orderedRows.slice((_c = query.offset) !== null && _c !== void 0 ? _c : 0);
62
+ const columns = getColumns(slicedRows, query);
63
+ if (isAggregateQuery(query)) {
64
+ const values = slicedRows.length === 1 ? slicedRows[0] : undefined;
65
+ return Object.assign({ kind: 'aggregate', columns, rows: slicedRows }, (values ? { values } : {}));
66
66
  }
67
67
  return {
68
- kind: 'aggregate',
68
+ kind: 'table',
69
69
  columns,
70
- rows,
70
+ rows: slicedRows,
71
71
  };
72
72
  });
73
73
  }
74
- function getLegacyQueryConfig(query) {
75
- if (!isRecord(query) || typeof query.resource !== 'string') {
76
- return undefined;
74
+ function getResourceRows(adminforth, resourceId, filters, sort) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ return adminforth.resource(resourceId).list(normalizeFilters(filters), undefined, 0, sort);
77
+ });
78
+ }
79
+ function buildQueryRows(rows, query) {
80
+ var _a, _b;
81
+ const select = (_a = query.select) !== null && _a !== void 0 ? _a : getDefaultSelect(rows);
82
+ const groupBy = (_b = query.groupBy) !== null && _b !== void 0 ? _b : [];
83
+ if (isAggregateQuery(query)) {
84
+ return buildGroupedRows(rows, select, groupBy, query.calcs);
85
+ }
86
+ return rows.map((row) => buildPlainRow(row, select, query.calcs));
87
+ }
88
+ function buildGroupedRows(rows, select, groupBy, calcs = []) {
89
+ var _a;
90
+ const groups = new Map();
91
+ const effectiveGroupBy = groupBy.length
92
+ ? groupBy
93
+ : select.filter(isFieldSelectItem).map((item) => ({ field: item.field, as: item.as, grain: item.grain }));
94
+ if (!effectiveGroupBy.length) {
95
+ const values = calculateGroupValues(rows, select, calcs);
96
+ return Object.keys(values).length ? [values] : [];
97
+ }
98
+ for (const row of rows) {
99
+ const values = Object.fromEntries(effectiveGroupBy.map((item) => {
100
+ const field = getGroupByField(item);
101
+ const alias = getGroupByAlias(item);
102
+ const grain = getGroupByGrain(item);
103
+ return [alias, formatGroupValue(row[field], grain)];
104
+ }));
105
+ const key = JSON.stringify(values);
106
+ const group = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : { values, rows: [] };
107
+ group.rows.push(row);
108
+ groups.set(key, group);
109
+ }
110
+ return Array.from(groups.values()).map((group) => (Object.assign(Object.assign({}, group.values), calculateGroupValues(group.rows, select, calcs))));
111
+ }
112
+ function calculateGroupValues(rows, select, calcs) {
113
+ const values = {};
114
+ for (const item of select) {
115
+ if (isAggregateSelectItem(item)) {
116
+ const filteredRows = item.filters
117
+ ? rows.filter((row) => matchesFilterExpression(row, item.filters))
118
+ : rows;
119
+ values[item.as] = calculateAggregate(filteredRows, item);
120
+ }
121
+ }
122
+ for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
123
+ values[item.as] = evaluateCalc(item.calc, values);
77
124
  }
78
- return query;
125
+ return values;
79
126
  }
80
- function getWidgetDataSource(widget, legacyQuery) {
81
- if (isWidgetDataSource(widget.dataSource)) {
82
- return widget.dataSource;
127
+ function buildPlainRow(row, select, calcs = []) {
128
+ var _a;
129
+ const values = {};
130
+ for (const item of select) {
131
+ if (isFieldSelectItem(item)) {
132
+ values[(_a = item.as) !== null && _a !== void 0 ? _a : item.field] = item.grain
133
+ ? formatGroupValue(row[item.field], item.grain)
134
+ : row[item.field];
135
+ }
83
136
  }
84
- if (!legacyQuery) {
137
+ for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
138
+ values[item.as] = evaluateCalc(item.calc, values);
139
+ }
140
+ return values;
141
+ }
142
+ function calculateAggregate(rows, item) {
143
+ switch (item.agg) {
144
+ case 'count':
145
+ return rows.length;
146
+ case 'count_distinct':
147
+ return new Set(rows.map((row) => row[item.field])).size;
148
+ case 'sum':
149
+ return aggregateNumbers(rows, item.field, (values) => values.reduce((sum, value) => sum + value, 0));
150
+ case 'avg':
151
+ return aggregateNumbers(rows, item.field, (values) => values.length
152
+ ? values.reduce((sum, value) => sum + value, 0) / values.length
153
+ : 0);
154
+ case 'min':
155
+ return aggregateNumbers(rows, item.field, (values) => values.length ? Math.min(...values) : 0);
156
+ case 'max':
157
+ return aggregateNumbers(rows, item.field, (values) => values.length ? Math.max(...values) : 0);
158
+ case 'median':
159
+ return aggregateNumbers(rows, item.field, calculateMedian);
160
+ default:
161
+ throw new Error(`Unsupported aggregation operation: ${item.agg}`);
162
+ }
163
+ }
164
+ function aggregateNumbers(rows, field, aggregate) {
165
+ return aggregate(rows.map((row) => toFiniteNumber(row[field])).filter(Number.isFinite));
166
+ }
167
+ function calculateMedian(values) {
168
+ if (!values.length) {
169
+ return 0;
170
+ }
171
+ const sorted = [...values].sort((left, right) => left - right);
172
+ const middle = Math.floor(sorted.length / 2);
173
+ return sorted.length % 2
174
+ ? sorted[middle]
175
+ : (sorted[middle - 1] + sorted[middle]) / 2;
176
+ }
177
+ function evaluateCalc(calc, values) {
178
+ const expression = calc.replace(CALC_IDENTIFIER_RE, (name) => String(toFiniteNumber(values[name])));
179
+ if (!SAFE_CALC_EXPRESSION_RE.test(expression)) {
180
+ throw new Error(`Unsupported calc expression: ${calc}`);
181
+ }
182
+ return Function(`"use strict"; return (${expression});`)();
183
+ }
184
+ function sortRows(rows, orderBy = []) {
185
+ if (!orderBy.length) {
186
+ return rows;
187
+ }
188
+ return [...rows].sort((left, right) => {
189
+ for (const order of orderBy) {
190
+ const direction = order.direction === 'asc' ? 1 : -1;
191
+ const result = compareValues(left[order.field], right[order.field]);
192
+ if (result !== 0) {
193
+ return result * direction;
194
+ }
195
+ }
196
+ return 0;
197
+ });
198
+ }
199
+ function compareValues(left, right) {
200
+ if (typeof left === 'number' && typeof right === 'number') {
201
+ return left - right;
202
+ }
203
+ return String(left !== null && left !== void 0 ? left : '').localeCompare(String(right !== null && right !== void 0 ? right : ''));
204
+ }
205
+ function getBackendSort(orderBy) {
206
+ if (!(orderBy === null || orderBy === void 0 ? void 0 : orderBy.length)) {
85
207
  return undefined;
86
208
  }
87
- return {
88
- type: 'resource',
89
- resourceId: legacyQuery.resource,
90
- columns: legacyQuery.select,
91
- sort: legacyQuery.order,
92
- };
209
+ return orderBy.map((order) => order.direction === 'asc'
210
+ ? Sorts.ASC(order.field)
211
+ : Sorts.DESC(order.field));
212
+ }
213
+ function getColumns(rows, query) {
214
+ var _a, _b, _c, _d;
215
+ const selectColumns = [
216
+ ...((_a = query.groupBy) !== null && _a !== void 0 ? _a : []).map(getGroupByAlias),
217
+ ...((_b = query.select) !== null && _b !== void 0 ? _b : []).map(getSelectAlias),
218
+ ...((_c = query.calcs) !== null && _c !== void 0 ? _c : []).map((item) => item.as),
219
+ ].filter(Boolean);
220
+ return Array.from(new Set(selectColumns.length ? selectColumns : Object.keys((_d = rows[0]) !== null && _d !== void 0 ? _d : {})));
221
+ }
222
+ function getDefaultSelect(rows) {
223
+ var _a;
224
+ return Object.keys((_a = rows[0]) !== null && _a !== void 0 ? _a : {}).map((field) => ({ field }));
225
+ }
226
+ function isAggregateQuery(query) {
227
+ var _a, _b;
228
+ return Boolean(((_a = query.groupBy) === null || _a === void 0 ? void 0 : _a.length)
229
+ || ((_b = query.select) === null || _b === void 0 ? void 0 : _b.some((item) => isAggregateSelectItem(item))));
93
230
  }
94
- function isWidgetDataSource(value) {
95
- return isRecord(value)
96
- && (value.type === 'resource' || value.type === 'aggregate')
97
- && typeof value.resourceId === 'string';
231
+ function isFieldSelectItem(item) {
232
+ return 'field' in item && !('agg' in item);
233
+ }
234
+ function isAggregateSelectItem(item) {
235
+ return 'agg' in item;
236
+ }
237
+ function isCalcSelectItem(item) {
238
+ return 'calc' in item;
239
+ }
240
+ function getSelectAlias(item) {
241
+ var _a;
242
+ if (isFieldSelectItem(item)) {
243
+ return (_a = item.as) !== null && _a !== void 0 ? _a : item.field;
244
+ }
245
+ return item.as;
246
+ }
247
+ function getGroupByField(item) {
248
+ return typeof item === 'string' ? item : item.field;
249
+ }
250
+ function getGroupByAlias(item) {
251
+ var _a;
252
+ return typeof item === 'string' ? item : (_a = item.as) !== null && _a !== void 0 ? _a : item.field;
253
+ }
254
+ function getGroupByGrain(item) {
255
+ return typeof item === 'string' ? undefined : item.grain;
256
+ }
257
+ function formatGroupValue(value, grain) {
258
+ if (!grain) {
259
+ return value;
260
+ }
261
+ const date = new Date(String(value));
262
+ if (!Number.isFinite(date.getTime())) {
263
+ return value;
264
+ }
265
+ if (grain === 'year') {
266
+ return `${date.getUTCFullYear()}`;
267
+ }
268
+ if (grain === 'quarter') {
269
+ return `${date.getUTCFullYear()}-Q${Math.floor(date.getUTCMonth() / 3) + 1}`;
270
+ }
271
+ const month = String(date.getUTCMonth() + 1).padStart(2, '0');
272
+ if (grain === 'month') {
273
+ return `${date.getUTCFullYear()}-${month}`;
274
+ }
275
+ const day = String(date.getUTCDate()).padStart(2, '0');
276
+ if (grain === 'week') {
277
+ const weekStart = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
278
+ weekStart.setUTCDate(weekStart.getUTCDate() - weekStart.getUTCDay());
279
+ return weekStart.toISOString().slice(0, 10);
280
+ }
281
+ if (grain === 'day') {
282
+ return `${date.getUTCFullYear()}-${month}-${day}`;
283
+ }
284
+ const hour = String(date.getUTCHours()).padStart(2, '0');
285
+ return `${date.getUTCFullYear()}-${month}-${day}T${hour}:00:00.000Z`;
98
286
  }
99
287
  function normalizeFilters(filters) {
100
288
  if (Array.isArray(filters)) {
101
- return filters;
289
+ return filters.map((filter) => normalizeFilterNode(filter));
102
290
  }
103
291
  if (isRecord(filters)) {
104
- return filters;
292
+ return normalizeFilterNode(filters);
105
293
  }
106
294
  return [];
107
295
  }
108
- function normalizeSort(sort) {
109
- if (Array.isArray(sort)) {
110
- return sort;
296
+ function normalizeFilterNode(filter) {
297
+ if (!isRecord(filter)) {
298
+ return filter;
111
299
  }
112
- if (!isRecord(sort) || typeof sort.field !== 'string') {
113
- return undefined;
300
+ if (Array.isArray(filter.and)) {
301
+ return Filters.AND(filter.and.map((item) => normalizeFilterNode(item)));
302
+ }
303
+ if (Array.isArray(filter.or)) {
304
+ return Filters.OR(filter.or.map((item) => normalizeFilterNode(item)));
305
+ }
306
+ if (typeof filter.field === 'string') {
307
+ if (Object.prototype.hasOwnProperty.call(filter, 'eq')) {
308
+ return Filters.EQ(filter.field, normalizeFilterValue(filter.eq));
309
+ }
310
+ if (Object.prototype.hasOwnProperty.call(filter, 'neq')) {
311
+ return Filters.NEQ(filter.field, normalizeFilterValue(filter.neq));
312
+ }
313
+ if (Object.prototype.hasOwnProperty.call(filter, 'gt')) {
314
+ return Filters.GT(filter.field, normalizeFilterValue(filter.gt));
315
+ }
316
+ if (Object.prototype.hasOwnProperty.call(filter, 'gte')) {
317
+ return Filters.GTE(filter.field, normalizeFilterValue(filter.gte));
318
+ }
319
+ if (Object.prototype.hasOwnProperty.call(filter, 'lt')) {
320
+ return Filters.LT(filter.field, normalizeFilterValue(filter.lt));
321
+ }
322
+ if (Object.prototype.hasOwnProperty.call(filter, 'lte')) {
323
+ return Filters.LTE(filter.field, normalizeFilterValue(filter.lte));
324
+ }
325
+ if (Object.prototype.hasOwnProperty.call(filter, 'in')) {
326
+ return Filters.IN(filter.field, normalizeFilterValue(filter.in));
327
+ }
328
+ if (Object.prototype.hasOwnProperty.call(filter, 'not_in')) {
329
+ return Filters.NOT_IN(filter.field, normalizeFilterValue(filter.not_in));
330
+ }
331
+ }
332
+ return filter;
333
+ }
334
+ function matchesFilterExpression(row, filter) {
335
+ var _a, _b, _c, _d;
336
+ if (Array.isArray(filter)) {
337
+ return filter.every((item) => matchesFilterExpression(row, item));
338
+ }
339
+ if ('and' in filter) {
340
+ return filter.and.every((item) => matchesFilterExpression(row, item));
341
+ }
342
+ if ('or' in filter) {
343
+ return filter.or.some((item) => matchesFilterExpression(row, item));
344
+ }
345
+ const value = row[filter.field];
346
+ if (Object.prototype.hasOwnProperty.call(filter, 'eq')) {
347
+ return value === normalizeFilterValue(filter.eq);
348
+ }
349
+ if (Object.prototype.hasOwnProperty.call(filter, 'neq')) {
350
+ return value !== normalizeFilterValue(filter.neq);
351
+ }
352
+ if (Object.prototype.hasOwnProperty.call(filter, 'gt')) {
353
+ return compareComparableValues(value, normalizeFilterValue(filter.gt)) > 0;
354
+ }
355
+ if (Object.prototype.hasOwnProperty.call(filter, 'gte')) {
356
+ return compareComparableValues(value, normalizeFilterValue(filter.gte)) >= 0;
357
+ }
358
+ if (Object.prototype.hasOwnProperty.call(filter, 'lt')) {
359
+ return compareComparableValues(value, normalizeFilterValue(filter.lt)) < 0;
360
+ }
361
+ if (Object.prototype.hasOwnProperty.call(filter, 'lte')) {
362
+ return compareComparableValues(value, normalizeFilterValue(filter.lte)) <= 0;
114
363
  }
115
- if (sort.direction === 'asc') {
116
- return [Sorts.ASC(sort.field)];
364
+ if (Object.prototype.hasOwnProperty.call(filter, 'in')) {
365
+ return (_b = (_a = filter.in) === null || _a === void 0 ? void 0 : _a.includes(value)) !== null && _b !== void 0 ? _b : false;
117
366
  }
118
- if (sort.direction === 'desc') {
119
- return [Sorts.DESC(sort.field)];
367
+ if (Object.prototype.hasOwnProperty.call(filter, 'not_in')) {
368
+ return !((_d = (_c = filter.not_in) === null || _c === void 0 ? void 0 : _c.includes(value)) !== null && _d !== void 0 ? _d : false);
120
369
  }
121
- return sort;
370
+ return true;
122
371
  }
123
- function createAggregationRule(rule) {
124
- switch (rule.operation) {
125
- case 'sum':
126
- return Aggregates.sum(rule.field);
127
- case 'count':
128
- return Aggregates.count();
129
- case 'avg':
130
- return Aggregates.avg(rule.field);
131
- case 'min':
132
- return Aggregates.min(rule.field);
133
- case 'max':
134
- return Aggregates.max(rule.field);
135
- case 'median':
136
- return Aggregates.median(rule.field);
137
- default:
138
- throw new Error(`Unsupported aggregation operation: ${rule.operation}`);
372
+ function compareComparableValues(left, right) {
373
+ const leftNumber = Number(left);
374
+ const rightNumber = Number(right);
375
+ if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
376
+ return leftNumber - rightNumber;
139
377
  }
378
+ return String(left !== null && left !== void 0 ? left : '').localeCompare(String(right !== null && right !== void 0 ? right : ''));
140
379
  }
141
- function createGroupByRule(rule) {
142
- if (rule.type === 'field') {
143
- return GroupBy.Field(rule.field);
380
+ function normalizeFilterValue(value) {
381
+ if (!isRecord(value) || typeof value.now_minus !== 'string') {
382
+ return value;
383
+ }
384
+ const match = value.now_minus.match(NOW_MINUS_RE);
385
+ if (!match) {
386
+ return value;
387
+ }
388
+ const amount = Number(match[1]);
389
+ const unit = match[2];
390
+ const date = new Date();
391
+ if (unit === 'h') {
392
+ date.setHours(date.getHours() - amount);
144
393
  }
145
- return GroupBy.DateTrunc(rule.field, rule.truncation, rule.timezone);
394
+ else if (unit === 'w') {
395
+ date.setDate(date.getDate() - amount * 7);
396
+ }
397
+ else {
398
+ date.setDate(date.getDate() - amount);
399
+ }
400
+ return date.toISOString();
401
+ }
402
+ function toFiniteNumber(value) {
403
+ const numberValue = typeof value === 'number' ? value : Number(value);
404
+ return Number.isFinite(numberValue) ? numberValue : 0;
146
405
  }
147
406
  function isRecord(value) {
148
407
  return typeof value === 'object' && value !== null;
@@ -29,4 +29,4 @@ export function registerDashboardEndpoints(
29
29
  return buildDashboardResponse(dashboard);
30
30
  },
31
31
  });
32
- }
32
+ }
@@ -37,11 +37,37 @@ type WidgetEndpointsContext = {
37
37
 
38
38
  function formatWidgetConfigValidationErrors(error: { issues: { path: PropertyKey[], message: string }[] }) {
39
39
  return error.issues.map((issue) => ({
40
- field: issue.path.length ? issue.path.map(String).join('.') : 'config',
40
+ field: issue.path.length ? formatWidgetConfigFieldPath(issue.path.map(String).join('.')) : 'config',
41
41
  message: issue.message,
42
42
  }));
43
43
  }
44
44
 
45
+ function formatWidgetConfigApiValidationErrors(errors: DashboardWidgetConfigValidationError[]) {
46
+ return errors.map((error) => ({
47
+ ...error,
48
+ field: formatWidgetConfigFieldPath(error.field),
49
+ }));
50
+ }
51
+
52
+ function formatWidgetConfigFieldPath(field: string) {
53
+ const fieldAliases = new Map([
54
+ ['minWidth', 'min_width'],
55
+ ['maxWidth', 'max_width'],
56
+ ['groupBy', 'group_by'],
57
+ ['orderBy', 'order_by'],
58
+ ['pageSize', 'page_size'],
59
+ ['timeSeries', 'time_series'],
60
+ ['valueField', 'value_field'],
61
+ ['targetValue', 'target_value'],
62
+ ['targetField', 'target_field'],
63
+ ]);
64
+
65
+ return field
66
+ .split('.')
67
+ .map((segment) => fieldAliases.get(segment) ?? segment)
68
+ .join('.');
69
+ }
70
+
45
71
  export function registerWidgetEndpoints(
46
72
  server: IHttpServer,
47
73
  ctx: WidgetEndpointsContext,
@@ -229,7 +255,7 @@ export function registerWidgetEndpoints(
229
255
  response.setStatus(422);
230
256
  return {
231
257
  error: 'Invalid widget config',
232
- validationErrors: apiValidationErrors,
258
+ validationErrors: formatWidgetConfigApiValidationErrors(apiValidationErrors),
233
259
  };
234
260
  }
235
261
 
@@ -250,7 +276,7 @@ export function registerWidgetEndpoints(
250
276
  server.endpoint({
251
277
  method: 'POST',
252
278
  path: '/dashboard/get_dashboard_widget_data',
253
- description: 'Loads query result data for one dashboard widget by dashboard slug and widget id.',
279
+ description: 'Loads widget data for one dashboard widget by dashboard slug and widget id.',
254
280
  request_schema: WidgetDataRequestSchema,
255
281
  response_schema: DashboardWidgetDataResponseSchema,
256
282
  handler: async ({ body, response }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/dashboard",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",