@adminforth/dashboard 1.8.0 → 1.9.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 +81 -55
- package/custom/model/dashboard.types.ts +6 -13
- package/custom/skills/adminforth-dashboard/SKILL.md +13 -20
- package/dist/custom/model/dashboard.types.d.ts +0 -5
- package/dist/custom/model/dashboard.types.ts +6 -13
- package/dist/custom/queries/useDashboardConfig.d.ts +20 -120
- package/dist/custom/queries/useWidgetData.d.ts +20 -120
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +13 -20
- package/dist/schema/api.d.ts +243 -2223
- package/dist/schema/widget.d.ts +20 -200
- package/dist/schema/widgets/charts.d.ts +24 -240
- package/dist/schema/widgets/common.d.ts +2 -20
- package/dist/schema/widgets/common.js +1 -10
- package/dist/schema/widgets/gauge-card.d.ts +2 -20
- package/dist/schema/widgets/kpi-card.d.ts +2 -20
- package/dist/schema/widgets/pivot-table.d.ts +2 -20
- package/dist/schema/widgets/table.d.ts +2 -20
- package/dist/services/calc-evaluator.d.ts +1 -0
- package/dist/services/calc-evaluator.js +28 -0
- package/dist/services/dashboardFilterService.d.ts +5 -0
- package/dist/services/dashboardFilterService.js +125 -0
- package/dist/services/widgetDataService.js +17 -170
- package/package.json +2 -1
- package/schema/widgets/common.ts +1 -11
- package/services/calc-evaluator.ts +33 -0
- package/services/dashboardFilterService.ts +162 -0
- package/services/widgetDataService.ts +28 -215
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IAdminForthAndOrFilter, IAdminForthSingleFilter } from 'adminforth';
|
|
2
|
+
import type { FilterExpression } from '../custom/model/dashboard.types.js';
|
|
3
|
+
export type DashboardQueryFilters = IAdminForthSingleFilter | IAdminForthAndOrFilter | Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>;
|
|
4
|
+
export declare function getAdminForthFilters(filters: FilterExpression | DashboardQueryFilters | undefined): DashboardQueryFilters;
|
|
5
|
+
export declare function mergeFilters(...filters: Array<FilterExpression | DashboardQueryFilters | undefined>): DashboardQueryFilters;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Filters } from 'adminforth';
|
|
2
|
+
const RELATIVE_DURATION_RE = /^(\d+)(h|d|w|mo|y)$/;
|
|
3
|
+
const FILTER_OPERATORS = {
|
|
4
|
+
eq: Filters.EQ,
|
|
5
|
+
neq: Filters.NEQ,
|
|
6
|
+
gt: Filters.GT,
|
|
7
|
+
gte: Filters.GTE,
|
|
8
|
+
lt: Filters.LT,
|
|
9
|
+
lte: Filters.LTE,
|
|
10
|
+
in: Filters.IN,
|
|
11
|
+
not_in: Filters.NOT_IN,
|
|
12
|
+
like: Filters.LIKE,
|
|
13
|
+
ilike: Filters.ILIKE,
|
|
14
|
+
};
|
|
15
|
+
export function getAdminForthFilters(filters) {
|
|
16
|
+
if (Array.isArray(filters)) {
|
|
17
|
+
return filters.map((filter) => isDashboardFilterExpression(filter)
|
|
18
|
+
? toAdminForthFilter(filter)
|
|
19
|
+
: filter);
|
|
20
|
+
}
|
|
21
|
+
if (isDashboardFilterExpression(filters)) {
|
|
22
|
+
return toAdminForthFilter(filters);
|
|
23
|
+
}
|
|
24
|
+
if (filters) {
|
|
25
|
+
return filters;
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
export function mergeFilters(...filters) {
|
|
30
|
+
const merged = [];
|
|
31
|
+
for (const filter of filters) {
|
|
32
|
+
const normalized = getAdminForthFilters(filter);
|
|
33
|
+
if (Array.isArray(normalized)) {
|
|
34
|
+
merged.push(...normalized);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (normalized) {
|
|
38
|
+
merged.push(normalized);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (!merged.length) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
return merged.length === 1 ? merged[0] : merged;
|
|
45
|
+
}
|
|
46
|
+
function isDashboardFilterExpression(value) {
|
|
47
|
+
if (Array.isArray(value)) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (!isRecord(value)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
return 'and' in value
|
|
54
|
+
|| 'or' in value
|
|
55
|
+
|| 'eq' in value
|
|
56
|
+
|| 'neq' in value
|
|
57
|
+
|| 'gt' in value
|
|
58
|
+
|| 'gte' in value
|
|
59
|
+
|| 'lt' in value
|
|
60
|
+
|| 'lte' in value
|
|
61
|
+
|| 'in' in value
|
|
62
|
+
|| 'not_in' in value
|
|
63
|
+
|| 'like' in value
|
|
64
|
+
|| 'ilike' in value;
|
|
65
|
+
}
|
|
66
|
+
function toAdminForthFilter(filter) {
|
|
67
|
+
if (Array.isArray(filter)) {
|
|
68
|
+
return Filters.AND(filter.map((item) => toAdminForthFilter(item)));
|
|
69
|
+
}
|
|
70
|
+
if ('and' in filter) {
|
|
71
|
+
return Filters.AND(filter.and.map((item) => toAdminForthFilter(item)));
|
|
72
|
+
}
|
|
73
|
+
if ('or' in filter) {
|
|
74
|
+
return Filters.OR(filter.or.map((item) => toAdminForthFilter(item)));
|
|
75
|
+
}
|
|
76
|
+
for (const [operator, createFilter] of Object.entries(FILTER_OPERATORS)) {
|
|
77
|
+
if (Object.prototype.hasOwnProperty.call(filter, operator)) {
|
|
78
|
+
return createFilter(filter.field, resolveFilterValue(filter[operator]));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return Filters.AND([]);
|
|
82
|
+
}
|
|
83
|
+
function resolveFilterValue(value) {
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map((item) => resolveFilterValue(item));
|
|
86
|
+
}
|
|
87
|
+
if (!isRecord(value)) {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
if (value.now === true) {
|
|
91
|
+
return new Date().toISOString();
|
|
92
|
+
}
|
|
93
|
+
if (typeof value.now_minus === 'string') {
|
|
94
|
+
return subtractDuration(new Date(), value.now_minus).toISOString();
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function subtractDuration(now, duration) {
|
|
99
|
+
const match = duration.match(RELATIVE_DURATION_RE);
|
|
100
|
+
if (!match) {
|
|
101
|
+
throw new Error(`Unsupported relative date duration: ${duration}`);
|
|
102
|
+
}
|
|
103
|
+
const amount = Number(match[1]);
|
|
104
|
+
const unit = match[2];
|
|
105
|
+
const date = new Date(now);
|
|
106
|
+
if (unit === 'h') {
|
|
107
|
+
date.setUTCHours(date.getUTCHours() - amount);
|
|
108
|
+
}
|
|
109
|
+
else if (unit === 'd') {
|
|
110
|
+
date.setUTCDate(date.getUTCDate() - amount);
|
|
111
|
+
}
|
|
112
|
+
else if (unit === 'w') {
|
|
113
|
+
date.setUTCDate(date.getUTCDate() - amount * 7);
|
|
114
|
+
}
|
|
115
|
+
else if (unit === 'mo') {
|
|
116
|
+
date.setUTCMonth(date.getUTCMonth() - amount);
|
|
117
|
+
}
|
|
118
|
+
else if (unit === 'y') {
|
|
119
|
+
date.setUTCFullYear(date.getUTCFullYear() - amount);
|
|
120
|
+
}
|
|
121
|
+
return date;
|
|
122
|
+
}
|
|
123
|
+
function isRecord(value) {
|
|
124
|
+
return typeof value === 'object' && value !== null;
|
|
125
|
+
}
|
|
@@ -7,24 +7,9 @@ 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 {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const VARIABLE_PATH_PREFIX_RE = /^\$variables\.?/;
|
|
14
|
-
const SAFE_CALC_EXPRESSION_RE = /^[\d+\-*/().\s?:<>=!]+$/;
|
|
15
|
-
const RELATIVE_DURATION_RE = /^(\d+)(h|d|w|mo|y)$/;
|
|
16
|
-
const FILTER_OPERATORS = {
|
|
17
|
-
eq: Filters.EQ,
|
|
18
|
-
neq: Filters.NEQ,
|
|
19
|
-
gt: Filters.GT,
|
|
20
|
-
gte: Filters.GTE,
|
|
21
|
-
lt: Filters.LT,
|
|
22
|
-
lte: Filters.LTE,
|
|
23
|
-
in: Filters.IN,
|
|
24
|
-
not_in: Filters.NOT_IN,
|
|
25
|
-
like: Filters.LIKE,
|
|
26
|
-
ilike: Filters.ILIKE,
|
|
27
|
-
};
|
|
10
|
+
import { Sorts } from 'adminforth';
|
|
11
|
+
import { getAdminForthFilters, mergeFilters, } from './dashboardFilterService.js';
|
|
12
|
+
import { evaluateCalc } from './calc-evaluator.js';
|
|
28
13
|
export function getWidgetData(adminforth_1, widget_1) {
|
|
29
14
|
return __awaiter(this, arguments, void 0, function* (adminforth, widget, options = {}) {
|
|
30
15
|
var _a;
|
|
@@ -52,9 +37,9 @@ function getQueryWidgetData(adminforth, query, variables) {
|
|
|
52
37
|
if (isStepsQuery(query)) {
|
|
53
38
|
return getStepsQueryData(adminforth, query, variables);
|
|
54
39
|
}
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
40
|
+
const singleAggregateSelect = getSingleAggregateSelectItem(query);
|
|
41
|
+
if (singleAggregateSelect) {
|
|
42
|
+
return getSingleAggregateWidgetData(adminforth, query, singleAggregateSelect);
|
|
58
43
|
}
|
|
59
44
|
const selectedRows = isAggregateQuery(query)
|
|
60
45
|
? yield buildAggregateQueryRows(adminforth, query, variables)
|
|
@@ -101,18 +86,18 @@ function getStepsQueryData(adminforth, query, variables) {
|
|
|
101
86
|
};
|
|
102
87
|
});
|
|
103
88
|
}
|
|
104
|
-
function
|
|
89
|
+
function getSingleAggregateWidgetData(adminforth, query, aggregate) {
|
|
105
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
106
91
|
var _a;
|
|
107
|
-
const [currentValues = {}] = yield getAggregateRows(adminforth, query.resource, query.filters, [
|
|
92
|
+
const [currentValues = {}] = yield getAggregateRows(adminforth, query.resource, query.filters, [aggregate], []);
|
|
108
93
|
const values = {
|
|
109
|
-
[
|
|
94
|
+
[aggregate.as]: (_a = currentValues[aggregate.as]) !== null && _a !== void 0 ? _a : 0,
|
|
110
95
|
};
|
|
111
96
|
const rows = query.sparkline
|
|
112
|
-
? yield
|
|
97
|
+
? yield getSingleAggregateSparklineRows(adminforth, query, aggregate, getAdminForthFilters(query.filters))
|
|
113
98
|
: [values];
|
|
114
99
|
const columns = Array.from(new Set([
|
|
115
|
-
|
|
100
|
+
aggregate.as,
|
|
116
101
|
...(query.sparkline ? [query.sparkline.as] : []),
|
|
117
102
|
]));
|
|
118
103
|
return {
|
|
@@ -123,7 +108,7 @@ function getMetricWidgetData(adminforth, query, metric) {
|
|
|
123
108
|
};
|
|
124
109
|
});
|
|
125
110
|
}
|
|
126
|
-
function
|
|
111
|
+
function getSingleAggregateSparklineRows(adminforth, query, aggregate, filters) {
|
|
127
112
|
return __awaiter(this, void 0, void 0, function* () {
|
|
128
113
|
const sparkline = query.sparkline;
|
|
129
114
|
const groupBy = [{
|
|
@@ -131,7 +116,7 @@ function getMetricSparklineRows(adminforth, query, metric, filters) {
|
|
|
131
116
|
as: sparkline.as,
|
|
132
117
|
grain: sparkline.grain,
|
|
133
118
|
}];
|
|
134
|
-
const rows = yield getAggregateRows(adminforth, query.resource, filters, [
|
|
119
|
+
const rows = yield getAggregateRows(adminforth, query.resource, filters, [aggregate], groupBy);
|
|
135
120
|
return rows.map((row) => {
|
|
136
121
|
var _a;
|
|
137
122
|
return (Object.assign(Object.assign({}, (_a = query.sparkline) === null || _a === void 0 ? void 0 : _a.fill_missing), row));
|
|
@@ -190,7 +175,7 @@ function getAggregateRows(adminforth, resourceId, baseFilters, select, groupBy)
|
|
|
190
175
|
function buildCalculatedRow(baseValues, select, calcs = [], variables) {
|
|
191
176
|
const values = Object.assign({}, baseValues);
|
|
192
177
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
193
|
-
values[item.as] = evaluateCalc(item.calc, values
|
|
178
|
+
values[item.as] = evaluateCalc(item.calc, values);
|
|
194
179
|
}
|
|
195
180
|
return values;
|
|
196
181
|
}
|
|
@@ -205,33 +190,10 @@ function buildPlainRow(row, select, calcs = [], variables) {
|
|
|
205
190
|
}
|
|
206
191
|
}
|
|
207
192
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
208
|
-
values[item.as] = evaluateCalc(item.calc, values
|
|
193
|
+
values[item.as] = evaluateCalc(item.calc, values);
|
|
209
194
|
}
|
|
210
195
|
return values;
|
|
211
196
|
}
|
|
212
|
-
function evaluateCalc(calc, values, variables) {
|
|
213
|
-
const expression = calc
|
|
214
|
-
.replace(LOOKUP_CALL_RE, (_match, path, keyField, defaultValue) => {
|
|
215
|
-
var _a;
|
|
216
|
-
const map = resolveVariablePath(variables, path);
|
|
217
|
-
const key = String((_a = values[keyField]) !== null && _a !== void 0 ? _a : '');
|
|
218
|
-
return String(toFiniteNumber(isRecord(map) && Object.prototype.hasOwnProperty.call(map, key)
|
|
219
|
-
? map[key]
|
|
220
|
-
: Number(defaultValue)));
|
|
221
|
-
})
|
|
222
|
-
.replace(CALC_IDENTIFIER_RE, (name) => String(toFiniteNumber(values[name])));
|
|
223
|
-
if (!SAFE_CALC_EXPRESSION_RE.test(expression)) {
|
|
224
|
-
throw new Error(`Unsupported calc expression: ${calc}`);
|
|
225
|
-
}
|
|
226
|
-
return Function(`"use strict"; return (${expression});`)();
|
|
227
|
-
}
|
|
228
|
-
function resolveVariablePath(variables, path) {
|
|
229
|
-
return path
|
|
230
|
-
.replace(VARIABLE_PATH_PREFIX_RE, '')
|
|
231
|
-
.split('.')
|
|
232
|
-
.filter(Boolean)
|
|
233
|
-
.reduce((current, segment) => isRecord(current) ? current[segment] : undefined, variables);
|
|
234
|
-
}
|
|
235
197
|
function sortRows(rows, orderBy = []) {
|
|
236
198
|
if (!orderBy.length) {
|
|
237
199
|
return rows;
|
|
@@ -279,7 +241,7 @@ function isAggregateQuery(query) {
|
|
|
279
241
|
return Boolean(((_a = query.group_by) === null || _a === void 0 ? void 0 : _a.length)
|
|
280
242
|
|| ((_b = query.select) === null || _b === void 0 ? void 0 : _b.some((item) => isAggregateSelectItem(item))));
|
|
281
243
|
}
|
|
282
|
-
function
|
|
244
|
+
function getSingleAggregateSelectItem(query) {
|
|
283
245
|
var _a, _b;
|
|
284
246
|
if ((_a = query.group_by) === null || _a === void 0 ? void 0 : _a.length) {
|
|
285
247
|
return undefined;
|
|
@@ -295,7 +257,7 @@ function isStepsQuery(query) {
|
|
|
295
257
|
return query.source === 'steps';
|
|
296
258
|
}
|
|
297
259
|
function getStepSelect(step) {
|
|
298
|
-
return
|
|
260
|
+
return step.select;
|
|
299
261
|
}
|
|
300
262
|
function isFieldSelectItem(item) {
|
|
301
263
|
return 'field' in item && !('agg' in item);
|
|
@@ -413,23 +375,6 @@ function getFilterCacheKey(filters) {
|
|
|
413
375
|
}
|
|
414
376
|
return JSON.stringify(filters);
|
|
415
377
|
}
|
|
416
|
-
function mergeFilters(...filters) {
|
|
417
|
-
const merged = [];
|
|
418
|
-
for (const filter of filters) {
|
|
419
|
-
const normalized = getAdminForthFilters(filter);
|
|
420
|
-
if (Array.isArray(normalized)) {
|
|
421
|
-
merged.push(...normalized);
|
|
422
|
-
continue;
|
|
423
|
-
}
|
|
424
|
-
if (normalized) {
|
|
425
|
-
merged.push(normalized);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (!merged.length) {
|
|
429
|
-
return [];
|
|
430
|
-
}
|
|
431
|
-
return merged.length === 1 ? merged[0] : merged;
|
|
432
|
-
}
|
|
433
378
|
function getHiddenAggregateAlias(groupBy, select) {
|
|
434
379
|
const usedAliases = new Set([
|
|
435
380
|
...groupBy.map((item) => item.as),
|
|
@@ -467,104 +412,6 @@ function formatGroupValue(value, grain) {
|
|
|
467
412
|
}
|
|
468
413
|
return value;
|
|
469
414
|
}
|
|
470
|
-
function getAdminForthFilters(filters) {
|
|
471
|
-
if (Array.isArray(filters)) {
|
|
472
|
-
return filters.map((filter) => isDashboardFilterExpression(filter)
|
|
473
|
-
? toAdminForthFilter(filter)
|
|
474
|
-
: filter);
|
|
475
|
-
}
|
|
476
|
-
if (isDashboardFilterExpression(filters)) {
|
|
477
|
-
return toAdminForthFilter(filters);
|
|
478
|
-
}
|
|
479
|
-
if (filters) {
|
|
480
|
-
return filters;
|
|
481
|
-
}
|
|
482
|
-
return [];
|
|
483
|
-
}
|
|
484
|
-
function isDashboardFilterExpression(value) {
|
|
485
|
-
if (Array.isArray(value)) {
|
|
486
|
-
return true;
|
|
487
|
-
}
|
|
488
|
-
if (!isRecord(value)) {
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
return 'and' in value
|
|
492
|
-
|| 'or' in value
|
|
493
|
-
|| 'eq' in value
|
|
494
|
-
|| 'neq' in value
|
|
495
|
-
|| 'gt' in value
|
|
496
|
-
|| 'gte' in value
|
|
497
|
-
|| 'lt' in value
|
|
498
|
-
|| 'lte' in value
|
|
499
|
-
|| 'in' in value
|
|
500
|
-
|| 'not_in' in value
|
|
501
|
-
|| 'like' in value
|
|
502
|
-
|| 'ilike' in value;
|
|
503
|
-
}
|
|
504
|
-
function toAdminForthFilter(filter) {
|
|
505
|
-
if (Array.isArray(filter)) {
|
|
506
|
-
return Filters.AND(filter.map((item) => toAdminForthFilter(item)));
|
|
507
|
-
}
|
|
508
|
-
if ('and' in filter) {
|
|
509
|
-
return Filters.AND(filter.and.map((item) => toAdminForthFilter(item)));
|
|
510
|
-
}
|
|
511
|
-
if ('or' in filter) {
|
|
512
|
-
return Filters.OR(filter.or.map((item) => toAdminForthFilter(item)));
|
|
513
|
-
}
|
|
514
|
-
for (const [operator, createFilter] of Object.entries(FILTER_OPERATORS)) {
|
|
515
|
-
if (Object.prototype.hasOwnProperty.call(filter, operator)) {
|
|
516
|
-
return createFilter(filter.field, resolveFilterValue(filter[operator]));
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
return Filters.AND([]);
|
|
520
|
-
}
|
|
521
|
-
function resolveFilterValue(value) {
|
|
522
|
-
if (Array.isArray(value)) {
|
|
523
|
-
return value.map((item) => resolveFilterValue(item));
|
|
524
|
-
}
|
|
525
|
-
if (!isRecord(value)) {
|
|
526
|
-
return value;
|
|
527
|
-
}
|
|
528
|
-
if (value.now === true) {
|
|
529
|
-
return new Date().toISOString();
|
|
530
|
-
}
|
|
531
|
-
if (typeof value.now_minus === 'string') {
|
|
532
|
-
return subtractDuration(new Date(), value.now_minus).toISOString();
|
|
533
|
-
}
|
|
534
|
-
return value;
|
|
535
|
-
}
|
|
536
|
-
function subtractDuration(now, duration) {
|
|
537
|
-
const match = duration.match(RELATIVE_DURATION_RE);
|
|
538
|
-
if (!match) {
|
|
539
|
-
throw new Error(`Unsupported relative date duration: ${duration}`);
|
|
540
|
-
}
|
|
541
|
-
const amount = Number(match[1]);
|
|
542
|
-
const unit = match[2];
|
|
543
|
-
const date = new Date(now);
|
|
544
|
-
if (unit === 'h') {
|
|
545
|
-
date.setUTCHours(date.getUTCHours() - amount);
|
|
546
|
-
}
|
|
547
|
-
else if (unit === 'd') {
|
|
548
|
-
date.setUTCDate(date.getUTCDate() - amount);
|
|
549
|
-
}
|
|
550
|
-
else if (unit === 'w') {
|
|
551
|
-
date.setUTCDate(date.getUTCDate() - amount * 7);
|
|
552
|
-
}
|
|
553
|
-
else if (unit === 'mo') {
|
|
554
|
-
date.setUTCMonth(date.getUTCMonth() - amount);
|
|
555
|
-
}
|
|
556
|
-
else if (unit === 'y') {
|
|
557
|
-
date.setUTCFullYear(date.getUTCFullYear() - amount);
|
|
558
|
-
}
|
|
559
|
-
return date;
|
|
560
|
-
}
|
|
561
|
-
function toFiniteNumber(value) {
|
|
562
|
-
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
563
|
-
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
564
|
-
}
|
|
565
|
-
function isRecord(value) {
|
|
566
|
-
return typeof value === 'object' && value !== null;
|
|
567
|
-
}
|
|
568
415
|
export function createWidgetDataService(adminforth) {
|
|
569
416
|
return {
|
|
570
417
|
getWidgetData: (widget, options) => getWidgetData(adminforth, widget, options),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
}
|
|
57
57
|
],
|
|
58
58
|
"dependencies": {
|
|
59
|
+
"expr-eval-fork": "^3.0.3",
|
|
59
60
|
"vue": "^3.5.34",
|
|
60
61
|
"vue-router": "^5.0.7",
|
|
61
62
|
"yaml": "^2.9.0",
|
package/schema/widgets/common.ts
CHANGED
|
@@ -160,13 +160,6 @@ const ResourceQueryConfigSchema = z.object({
|
|
|
160
160
|
formatting: z.record(z.string(), z.unknown()).optional(),
|
|
161
161
|
}).strict()
|
|
162
162
|
|
|
163
|
-
const StepsQueryMetricStepSchema = z.object({
|
|
164
|
-
name: z.string(),
|
|
165
|
-
resource: z.string(),
|
|
166
|
-
metric: QueryAggregateSelectItemSchema,
|
|
167
|
-
filters: FilterExpressionSchema.optional(),
|
|
168
|
-
}).strict()
|
|
169
|
-
|
|
170
163
|
const StepsQuerySelectStepSchema = z.object({
|
|
171
164
|
name: z.string(),
|
|
172
165
|
resource: z.string(),
|
|
@@ -178,10 +171,7 @@ export const QueryConfigSchema = z.union([
|
|
|
178
171
|
ResourceQueryConfigSchema,
|
|
179
172
|
z.object({
|
|
180
173
|
source: z.literal('steps'),
|
|
181
|
-
steps: z.array(
|
|
182
|
-
StepsQueryMetricStepSchema,
|
|
183
|
-
StepsQuerySelectStepSchema,
|
|
184
|
-
])).min(1),
|
|
174
|
+
steps: z.array(StepsQuerySelectStepSchema).min(1),
|
|
185
175
|
calcs: z.array(QueryCalcItemSchema).optional(),
|
|
186
176
|
order_by: z.array(QueryOrderByItemSchema).optional(),
|
|
187
177
|
limit: z.number().int().positive().optional(),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Parser } from 'expr-eval-fork';
|
|
2
|
+
|
|
3
|
+
const CALC_PARSER_OPTIONS = {
|
|
4
|
+
allowMemberAccess: false,
|
|
5
|
+
operators: {
|
|
6
|
+
assignment: false,
|
|
7
|
+
concatenate: false,
|
|
8
|
+
conditional: true,
|
|
9
|
+
comparison: true,
|
|
10
|
+
fndef: false,
|
|
11
|
+
in: false,
|
|
12
|
+
logical: true,
|
|
13
|
+
random: false,
|
|
14
|
+
},
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
const CALC_PARSER = new Parser(CALC_PARSER_OPTIONS);
|
|
18
|
+
|
|
19
|
+
export function evaluateCalc(calc: string, values: Record<string, unknown>) {
|
|
20
|
+
return CALC_PARSER.parse(calc).evaluate(normalizeCalcValues(values));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeCalcValues(values: Record<string, unknown>) {
|
|
24
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
25
|
+
key,
|
|
26
|
+
typeof value === 'string' ? value : toFiniteNumber(value),
|
|
27
|
+
]));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function toFiniteNumber(value: unknown) {
|
|
31
|
+
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
32
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
33
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { Filters } from 'adminforth';
|
|
2
|
+
import type {
|
|
3
|
+
IAdminForthAndOrFilter,
|
|
4
|
+
IAdminForthSingleFilter,
|
|
5
|
+
} from 'adminforth';
|
|
6
|
+
import type { FilterExpression } from '../custom/model/dashboard.types.js';
|
|
7
|
+
|
|
8
|
+
export type DashboardQueryFilters =
|
|
9
|
+
| IAdminForthSingleFilter
|
|
10
|
+
| IAdminForthAndOrFilter
|
|
11
|
+
| Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>;
|
|
12
|
+
|
|
13
|
+
const RELATIVE_DURATION_RE = /^(\d+)(h|d|w|mo|y)$/;
|
|
14
|
+
|
|
15
|
+
const FILTER_OPERATORS = {
|
|
16
|
+
eq: Filters.EQ,
|
|
17
|
+
neq: Filters.NEQ,
|
|
18
|
+
gt: Filters.GT,
|
|
19
|
+
gte: Filters.GTE,
|
|
20
|
+
lt: Filters.LT,
|
|
21
|
+
lte: Filters.LTE,
|
|
22
|
+
in: Filters.IN,
|
|
23
|
+
not_in: Filters.NOT_IN,
|
|
24
|
+
like: Filters.LIKE,
|
|
25
|
+
ilike: Filters.ILIKE,
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export function getAdminForthFilters(filters: FilterExpression | DashboardQueryFilters | undefined): DashboardQueryFilters {
|
|
29
|
+
if (Array.isArray(filters)) {
|
|
30
|
+
return filters.map((filter) => isDashboardFilterExpression(filter)
|
|
31
|
+
? toAdminForthFilter(filter)
|
|
32
|
+
: filter);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isDashboardFilterExpression(filters)) {
|
|
36
|
+
return toAdminForthFilter(filters);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (filters) {
|
|
40
|
+
return filters;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function mergeFilters(...filters: Array<FilterExpression | DashboardQueryFilters | undefined>) {
|
|
47
|
+
const merged: Array<IAdminForthSingleFilter | IAdminForthAndOrFilter> = [];
|
|
48
|
+
|
|
49
|
+
for (const filter of filters) {
|
|
50
|
+
const normalized = getAdminForthFilters(filter);
|
|
51
|
+
|
|
52
|
+
if (Array.isArray(normalized)) {
|
|
53
|
+
merged.push(...normalized);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (normalized) {
|
|
58
|
+
merged.push(normalized);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!merged.length) {
|
|
63
|
+
return [] as DashboardQueryFilters;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return merged.length === 1 ? merged[0] : merged;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isDashboardFilterExpression(value: unknown): value is FilterExpression {
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!isRecord(value)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return 'and' in value
|
|
79
|
+
|| 'or' in value
|
|
80
|
+
|| 'eq' in value
|
|
81
|
+
|| 'neq' in value
|
|
82
|
+
|| 'gt' in value
|
|
83
|
+
|| 'gte' in value
|
|
84
|
+
|| 'lt' in value
|
|
85
|
+
|| 'lte' in value
|
|
86
|
+
|| 'in' in value
|
|
87
|
+
|| 'not_in' in value
|
|
88
|
+
|| 'like' in value
|
|
89
|
+
|| 'ilike' in value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function toAdminForthFilter(filter: FilterExpression): IAdminForthSingleFilter | IAdminForthAndOrFilter {
|
|
93
|
+
if (Array.isArray(filter)) {
|
|
94
|
+
return Filters.AND(filter.map((item) => toAdminForthFilter(item)));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if ('and' in filter) {
|
|
98
|
+
return Filters.AND(filter.and.map((item) => toAdminForthFilter(item)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if ('or' in filter) {
|
|
102
|
+
return Filters.OR(filter.or.map((item) => toAdminForthFilter(item)));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const [operator, createFilter] of Object.entries(FILTER_OPERATORS)) {
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(filter, operator)) {
|
|
107
|
+
return createFilter(filter.field, resolveFilterValue(filter[operator as keyof typeof FILTER_OPERATORS]));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Filters.AND([]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolveFilterValue(value: unknown): unknown {
|
|
115
|
+
if (Array.isArray(value)) {
|
|
116
|
+
return value.map((item) => resolveFilterValue(item));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!isRecord(value)) {
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (value.now === true) {
|
|
124
|
+
return new Date().toISOString();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (typeof value.now_minus === 'string') {
|
|
128
|
+
return subtractDuration(new Date(), value.now_minus).toISOString();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function subtractDuration(now: Date, duration: string) {
|
|
135
|
+
const match = duration.match(RELATIVE_DURATION_RE);
|
|
136
|
+
|
|
137
|
+
if (!match) {
|
|
138
|
+
throw new Error(`Unsupported relative date duration: ${duration}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const amount = Number(match[1]);
|
|
142
|
+
const unit = match[2];
|
|
143
|
+
const date = new Date(now);
|
|
144
|
+
|
|
145
|
+
if (unit === 'h') {
|
|
146
|
+
date.setUTCHours(date.getUTCHours() - amount);
|
|
147
|
+
} else if (unit === 'd') {
|
|
148
|
+
date.setUTCDate(date.getUTCDate() - amount);
|
|
149
|
+
} else if (unit === 'w') {
|
|
150
|
+
date.setUTCDate(date.getUTCDate() - amount * 7);
|
|
151
|
+
} else if (unit === 'mo') {
|
|
152
|
+
date.setUTCMonth(date.getUTCMonth() - amount);
|
|
153
|
+
} else if (unit === 'y') {
|
|
154
|
+
date.setUTCFullYear(date.getUTCFullYear() - amount);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return date;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isRecord(value: unknown): value is Record<string, any> {
|
|
161
|
+
return typeof value === 'object' && value !== null;
|
|
162
|
+
}
|