@adminforth/dashboard 1.8.0 → 1.10.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/api/dashboardApi.ts +73 -36
- package/custom/model/dashboard.types.ts +6 -13
- package/custom/runtime/DashboardRuntime.vue +26 -22
- package/custom/skills/adminforth-dashboard/SKILL.md +13 -20
- package/dist/custom/api/dashboardApi.d.ts +24 -18
- package/dist/custom/api/dashboardApi.js +42 -18
- package/dist/custom/api/dashboardApi.ts +73 -36
- 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/runtime/DashboardRuntime.vue +26 -22
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +13 -20
- package/dist/endpoint/groups.js +22 -20
- package/dist/endpoint/widgets.js +28 -26
- package/dist/schema/api.d.ts +230 -3936
- package/dist/schema/api.js +7 -12
- 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 +2 -0
- package/dist/services/calc-evaluator.js +54 -0
- package/dist/services/dashboardFilterService.d.ts +5 -0
- package/dist/services/dashboardFilterService.js +125 -0
- package/dist/services/widgetDataService.js +15 -168
- package/endpoint/groups.ts +22 -20
- package/endpoint/widgets.ts +28 -26
- package/package.json +2 -1
- package/schema/api.ts +7 -12
- package/schema/widgets/common.ts +1 -11
- package/services/calc-evaluator.ts +71 -0
- package/services/dashboardFilterService.ts +162 -0
- package/services/widgetDataService.ts +26 -213
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Parser } from 'expr-eval-fork';
|
|
2
|
+
const LOOKUP_VARIABLE_PATH_RE = /lookup\(\s*\$variables((?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\s*,/g;
|
|
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
|
+
};
|
|
16
|
+
export function evaluateCalc(calc, values, variables = {}) {
|
|
17
|
+
const parser = createCalcParser(variables);
|
|
18
|
+
return parser.parse(normalizeLookupPaths(calc)).evaluate(normalizeCalcValues(values));
|
|
19
|
+
}
|
|
20
|
+
function createCalcParser(variables) {
|
|
21
|
+
const parser = new Parser(CALC_PARSER_OPTIONS);
|
|
22
|
+
parser.functions.lookup = (path, key, defaultValue = 0) => {
|
|
23
|
+
const map = resolveVariablePath(variables, String(path));
|
|
24
|
+
const value = isRecord(map) && Object.prototype.hasOwnProperty.call(map, String(key))
|
|
25
|
+
? map[String(key)]
|
|
26
|
+
: defaultValue;
|
|
27
|
+
return toFiniteNumber(value);
|
|
28
|
+
};
|
|
29
|
+
return parser;
|
|
30
|
+
}
|
|
31
|
+
function normalizeLookupPaths(calc) {
|
|
32
|
+
return calc.replace(LOOKUP_VARIABLE_PATH_RE, (_match, path) => {
|
|
33
|
+
return `lookup("${path.replace(/^\./, '')}",`;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function normalizeCalcValues(values) {
|
|
37
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
38
|
+
key,
|
|
39
|
+
typeof value === 'string' ? value : toFiniteNumber(value),
|
|
40
|
+
]));
|
|
41
|
+
}
|
|
42
|
+
function toFiniteNumber(value) {
|
|
43
|
+
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
44
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
45
|
+
}
|
|
46
|
+
function resolveVariablePath(variables, path) {
|
|
47
|
+
return path
|
|
48
|
+
.split('.')
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.reduce((current, segment) => isRecord(current) ? current[segment] : undefined, variables);
|
|
51
|
+
}
|
|
52
|
+
function isRecord(value) {
|
|
53
|
+
return typeof value === 'object' && value !== null;
|
|
54
|
+
}
|
|
@@ -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));
|
|
@@ -209,29 +194,6 @@ function buildPlainRow(row, select, calcs = [], variables) {
|
|
|
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/endpoint/groups.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
DashboardGroupConfig,
|
|
7
7
|
} from '../custom/model/dashboard.types.js';
|
|
8
8
|
import {
|
|
9
|
-
|
|
9
|
+
DashboardMutationResponseSchema,
|
|
10
10
|
GroupIdRequestSchema,
|
|
11
11
|
MoveGroupRequestSchema,
|
|
12
12
|
SetGroupConfigRequestSchema,
|
|
@@ -37,13 +37,14 @@ export function registerGroupEndpoints(
|
|
|
37
37
|
path: '/dashboard/add_dashboard_group',
|
|
38
38
|
description: 'Adds a new group to a dashboard configuration. Superadmin only.',
|
|
39
39
|
request_schema: SlugRequestSchema,
|
|
40
|
-
response_schema:
|
|
40
|
+
response_schema: DashboardMutationResponseSchema,
|
|
41
41
|
handler: async ({ body, adminUser, response }) => {
|
|
42
42
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
43
43
|
response.setStatus(403);
|
|
44
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
44
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
let groupId: string | null = null;
|
|
47
48
|
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
48
49
|
const nextOrder = config.groups.length + 1;
|
|
49
50
|
const group: DashboardGroupConfig = {
|
|
@@ -51,6 +52,7 @@ export function registerGroupEndpoints(
|
|
|
51
52
|
label: 'New group',
|
|
52
53
|
order: nextOrder,
|
|
53
54
|
};
|
|
55
|
+
groupId = group.id;
|
|
54
56
|
|
|
55
57
|
return {
|
|
56
58
|
...config,
|
|
@@ -60,10 +62,10 @@ export function registerGroupEndpoints(
|
|
|
60
62
|
|
|
61
63
|
if (!updatedDashboard) {
|
|
62
64
|
response.setStatus(404);
|
|
63
|
-
return { error: 'Dashboard not found' };
|
|
65
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
return
|
|
68
|
+
return { ok: true, groupId };
|
|
67
69
|
},
|
|
68
70
|
});
|
|
69
71
|
|
|
@@ -72,11 +74,11 @@ export function registerGroupEndpoints(
|
|
|
72
74
|
path: '/dashboard/set_dashboard_group_config',
|
|
73
75
|
description: 'Replaces editable JSON configuration for a dashboard group while preserving group id and order. Superadmin only.',
|
|
74
76
|
request_schema: SetGroupConfigRequestSchema,
|
|
75
|
-
response_schema:
|
|
77
|
+
response_schema: DashboardMutationResponseSchema,
|
|
76
78
|
handler: async ({ body, adminUser, response }) => {
|
|
77
79
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
78
80
|
response.setStatus(403);
|
|
79
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
81
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
const groupId = body.groupId;
|
|
@@ -105,15 +107,15 @@ export function registerGroupEndpoints(
|
|
|
105
107
|
|
|
106
108
|
if (!updatedDashboard) {
|
|
107
109
|
response.setStatus(404);
|
|
108
|
-
return { error: 'Dashboard not found' };
|
|
110
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
if (mutationError) {
|
|
112
114
|
response.setStatus(404);
|
|
113
|
-
return { error: mutationError };
|
|
115
|
+
return { ok: false, error: mutationError };
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
return
|
|
118
|
+
return { ok: true };
|
|
117
119
|
},
|
|
118
120
|
});
|
|
119
121
|
|
|
@@ -122,11 +124,11 @@ export function registerGroupEndpoints(
|
|
|
122
124
|
path: '/dashboard/move_dashboard_group',
|
|
123
125
|
description: 'Moves a dashboard group up or down in its dashboard. Superadmin only.',
|
|
124
126
|
request_schema: MoveGroupRequestSchema,
|
|
125
|
-
response_schema:
|
|
127
|
+
response_schema: DashboardMutationResponseSchema,
|
|
126
128
|
handler: async ({ body, adminUser, response }) => {
|
|
127
129
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
128
130
|
response.setStatus(403);
|
|
129
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
131
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
let mutationError: string | null = null;
|
|
@@ -157,15 +159,15 @@ export function registerGroupEndpoints(
|
|
|
157
159
|
|
|
158
160
|
if (!updatedDashboard) {
|
|
159
161
|
response.setStatus(404);
|
|
160
|
-
return { error: 'Dashboard not found' };
|
|
162
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
if (mutationError) {
|
|
164
166
|
response.setStatus(404);
|
|
165
|
-
return { error: mutationError };
|
|
167
|
+
return { ok: false, error: mutationError };
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
return
|
|
170
|
+
return { ok: true };
|
|
169
171
|
},
|
|
170
172
|
});
|
|
171
173
|
|
|
@@ -174,11 +176,11 @@ export function registerGroupEndpoints(
|
|
|
174
176
|
path: '/dashboard/remove_dashboard_group',
|
|
175
177
|
description: 'Removes a dashboard group and all widgets inside it. Superadmin only.',
|
|
176
178
|
request_schema: GroupIdRequestSchema,
|
|
177
|
-
response_schema:
|
|
179
|
+
response_schema: DashboardMutationResponseSchema,
|
|
178
180
|
handler: async ({ body, adminUser, response }) => {
|
|
179
181
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
180
182
|
response.setStatus(403);
|
|
181
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
183
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
const groupId = body.groupId;
|
|
@@ -200,15 +202,15 @@ export function registerGroupEndpoints(
|
|
|
200
202
|
|
|
201
203
|
if (!updatedDashboard) {
|
|
202
204
|
response.setStatus(404);
|
|
203
|
-
return { error: 'Dashboard not found' };
|
|
205
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
204
206
|
}
|
|
205
207
|
|
|
206
208
|
if (mutationError) {
|
|
207
209
|
response.setStatus(404);
|
|
208
|
-
return { error: mutationError };
|
|
210
|
+
return { ok: false, error: mutationError };
|
|
209
211
|
}
|
|
210
212
|
|
|
211
|
-
return
|
|
213
|
+
return { ok: true };
|
|
212
214
|
},
|
|
213
215
|
});
|
|
214
216
|
|