@adminforth/dashboard 1.7.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 +17 -9
- package/custom/skills/adminforth-dashboard/SKILL.md +28 -12
- package/dist/custom/model/dashboard.types.d.ts +15 -8
- package/dist/custom/model/dashboard.types.ts +17 -9
- package/dist/custom/queries/useDashboardConfig.d.ts +222 -4
- package/dist/custom/queries/useWidgetData.d.ts +222 -4
- package/dist/custom/skills/adminforth-dashboard/SKILL.md +28 -12
- package/dist/schema/api.d.ts +5440 -941
- package/dist/schema/api.js +2 -2
- package/dist/schema/widget.d.ts +432 -23
- package/dist/schema/widget.js +1 -1
- package/dist/schema/widgets/charts.d.ts +558 -28
- package/dist/schema/widgets/charts.js +2 -2
- package/dist/schema/widgets/common.d.ts +17 -6
- package/dist/schema/widgets/common.js +16 -7
- package/dist/schema/widgets/gauge-card.d.ts +38 -2
- package/dist/schema/widgets/kpi-card.d.ts +38 -2
- package/dist/schema/widgets/pivot-table.d.ts +38 -2
- package/dist/schema/widgets/table.d.ts +38 -2
- 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 +53 -201
- package/package.json +2 -1
- package/schema/api.ts +1 -2
- package/schema/widget.ts +0 -1
- package/schema/widgets/charts.ts +1 -2
- package/schema/widgets/common.ts +16 -7
- package/services/calc-evaluator.ts +33 -0
- package/services/dashboardFilterService.ts +162 -0
- package/services/widgetDataService.ts +88 -263
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Sorts } from 'adminforth';
|
|
2
2
|
import type {
|
|
3
3
|
IAdminForth,
|
|
4
|
-
IAdminForthAndOrFilter,
|
|
5
|
-
IAdminForthSingleFilter,
|
|
6
4
|
IAdminForthSort,
|
|
7
5
|
} from 'adminforth';
|
|
8
6
|
import type {
|
|
9
7
|
DashboardWidgetConfig,
|
|
10
8
|
DashboardWidgetData,
|
|
11
9
|
DashboardVariables,
|
|
12
|
-
FunnelQueryConfig,
|
|
13
10
|
FilterExpression,
|
|
14
11
|
QueryAggregateSelectItem,
|
|
15
12
|
QueryCalcSelectItem,
|
|
@@ -17,9 +14,17 @@ import type {
|
|
|
17
14
|
QueryFieldSelectItem,
|
|
18
15
|
QueryGroupByItem,
|
|
19
16
|
QueryOrderByItem,
|
|
17
|
+
ResourceQueryConfig,
|
|
18
|
+
StepsQueryStepConfig,
|
|
20
19
|
QuerySelectItem,
|
|
21
20
|
TimeGrain,
|
|
22
21
|
} from '../custom/model/dashboard.types.js';
|
|
22
|
+
import {
|
|
23
|
+
getAdminForthFilters,
|
|
24
|
+
mergeFilters,
|
|
25
|
+
type DashboardQueryFilters,
|
|
26
|
+
} from './dashboardFilterService.js';
|
|
27
|
+
import { evaluateCalc } from './calc-evaluator.js';
|
|
23
28
|
|
|
24
29
|
export type DashboardWidgetDataOptions = {
|
|
25
30
|
pagination?: {
|
|
@@ -29,11 +34,6 @@ export type DashboardWidgetDataOptions = {
|
|
|
29
34
|
variables?: DashboardVariables;
|
|
30
35
|
};
|
|
31
36
|
|
|
32
|
-
type DashboardWidgetFilters =
|
|
33
|
-
| IAdminForthSingleFilter
|
|
34
|
-
| IAdminForthAndOrFilter
|
|
35
|
-
| Array<IAdminForthSingleFilter | IAdminForthAndOrFilter>;
|
|
36
|
-
|
|
37
37
|
type AggregateRule =
|
|
38
38
|
| { operation: 'count' }
|
|
39
39
|
| { operation: Exclude<QueryAggregateSelectItem['agg'], 'count'>; field: string };
|
|
@@ -54,7 +54,7 @@ type AggregateGroupByRule =
|
|
|
54
54
|
|
|
55
55
|
type AggregateResource = {
|
|
56
56
|
aggregate: (
|
|
57
|
-
filters:
|
|
57
|
+
filters: DashboardQueryFilters,
|
|
58
58
|
aggregations: Record<string, AggregateRule>,
|
|
59
59
|
groupBy?: AggregateGroupByRule | AggregateGroupByRule[],
|
|
60
60
|
) => Promise<Record<string, unknown>[]>;
|
|
@@ -67,24 +67,6 @@ type EffectiveGroupByItem = {
|
|
|
67
67
|
timezone?: string;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const CALC_IDENTIFIER_RE = /\b[a-zA-Z_][a-zA-Z0-9_]*\b/g;
|
|
71
|
-
const LOOKUP_CALL_RE = /lookup\(\s*(\$variables(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*,\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
72
|
-
const VARIABLE_PATH_PREFIX_RE = /^\$variables\.?/;
|
|
73
|
-
const SAFE_CALC_EXPRESSION_RE = /^[\d+\-*/().\s?:<>=!]+$/;
|
|
74
|
-
const RELATIVE_DURATION_RE = /^(\d+)(h|d|w|mo|y)$/;
|
|
75
|
-
const FILTER_OPERATORS = {
|
|
76
|
-
eq: Filters.EQ,
|
|
77
|
-
neq: Filters.NEQ,
|
|
78
|
-
gt: Filters.GT,
|
|
79
|
-
gte: Filters.GTE,
|
|
80
|
-
lt: Filters.LT,
|
|
81
|
-
lte: Filters.LTE,
|
|
82
|
-
in: Filters.IN,
|
|
83
|
-
not_in: Filters.NOT_IN,
|
|
84
|
-
like: Filters.LIKE,
|
|
85
|
-
ilike: Filters.ILIKE,
|
|
86
|
-
} as const;
|
|
87
|
-
|
|
88
70
|
export type WidgetDataService = {
|
|
89
71
|
getWidgetData: (widget: DashboardWidgetConfig, options?: DashboardWidgetDataOptions) => Promise<DashboardWidgetData | null>;
|
|
90
72
|
};
|
|
@@ -98,9 +80,7 @@ export async function getWidgetData(
|
|
|
98
80
|
return null;
|
|
99
81
|
}
|
|
100
82
|
|
|
101
|
-
const data =
|
|
102
|
-
? await getFunnelWidgetData(adminforth, widget.query, options.variables ?? {})
|
|
103
|
-
: await getQueryWidgetData(adminforth, widget.query, options.variables ?? {});
|
|
83
|
+
const data = await getQueryWidgetData(adminforth, widget.query, options.variables ?? {});
|
|
104
84
|
|
|
105
85
|
if (widget.target !== 'table' || !options.pagination) {
|
|
106
86
|
return data;
|
|
@@ -122,54 +102,19 @@ export async function getWidgetData(
|
|
|
122
102
|
};
|
|
123
103
|
}
|
|
124
104
|
|
|
125
|
-
async function getFunnelWidgetData(
|
|
126
|
-
adminforth: IAdminForth,
|
|
127
|
-
query: FunnelQueryConfig,
|
|
128
|
-
variables: DashboardVariables,
|
|
129
|
-
): Promise<DashboardWidgetData> {
|
|
130
|
-
const rows = await Promise.all(query.steps.map(async (step) => {
|
|
131
|
-
const valueField = step.metric.as;
|
|
132
|
-
const [values = {}] = await getAggregateRows(
|
|
133
|
-
adminforth,
|
|
134
|
-
step.resource,
|
|
135
|
-
step.filters,
|
|
136
|
-
[step.metric],
|
|
137
|
-
[],
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const row: Record<string, unknown> = {
|
|
141
|
-
name: step.name,
|
|
142
|
-
resource: step.resource,
|
|
143
|
-
[valueField]: values[valueField] ?? 0,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
for (const calc of query.calcs ?? []) {
|
|
147
|
-
row[calc.as] = evaluateCalc(calc.calc, row, variables);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return row;
|
|
151
|
-
}));
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
kind: 'aggregate',
|
|
155
|
-
columns: [
|
|
156
|
-
'name',
|
|
157
|
-
...Array.from(new Set(query.steps.map((step) => step.metric.as))),
|
|
158
|
-
...Array.from(new Set((query.calcs ?? []).map((calc) => calc.as))),
|
|
159
|
-
],
|
|
160
|
-
rows,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
105
|
async function getQueryWidgetData(
|
|
165
106
|
adminforth: IAdminForth,
|
|
166
107
|
query: QueryConfig,
|
|
167
108
|
variables: DashboardVariables,
|
|
168
109
|
): Promise<DashboardWidgetData> {
|
|
169
|
-
|
|
110
|
+
if (isStepsQuery(query)) {
|
|
111
|
+
return getStepsQueryData(adminforth, query, variables);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const singleAggregateSelect = getSingleAggregateSelectItem(query);
|
|
170
115
|
|
|
171
|
-
if (
|
|
172
|
-
return
|
|
116
|
+
if (singleAggregateSelect) {
|
|
117
|
+
return getSingleAggregateWidgetData(adminforth, query, singleAggregateSelect);
|
|
173
118
|
}
|
|
174
119
|
|
|
175
120
|
const selectedRows = isAggregateQuery(query)
|
|
@@ -203,27 +148,67 @@ async function getQueryWidgetData(
|
|
|
203
148
|
};
|
|
204
149
|
}
|
|
205
150
|
|
|
206
|
-
async function
|
|
151
|
+
async function getStepsQueryData(
|
|
207
152
|
adminforth: IAdminForth,
|
|
208
|
-
query: QueryConfig,
|
|
209
|
-
|
|
153
|
+
query: Extract<QueryConfig, { source: 'steps' }>,
|
|
154
|
+
variables: DashboardVariables,
|
|
155
|
+
): Promise<DashboardWidgetData> {
|
|
156
|
+
const rows = await Promise.all(query.steps.map(async (step) => {
|
|
157
|
+
const select = getStepSelect(step);
|
|
158
|
+
const [values = {}] = await getAggregateRows(
|
|
159
|
+
adminforth,
|
|
160
|
+
step.resource,
|
|
161
|
+
step.filters,
|
|
162
|
+
select,
|
|
163
|
+
[],
|
|
164
|
+
);
|
|
165
|
+
const row = buildCalculatedRow({
|
|
166
|
+
name: step.name,
|
|
167
|
+
resource: step.resource,
|
|
168
|
+
...values,
|
|
169
|
+
}, select, query.calcs, variables);
|
|
170
|
+
|
|
171
|
+
return row;
|
|
172
|
+
}));
|
|
173
|
+
const orderedRows = sortRows(rows, query.order_by);
|
|
174
|
+
const slicedRows = typeof query.limit === 'number'
|
|
175
|
+
? orderedRows.slice(query.offset ?? 0, (query.offset ?? 0) + query.limit)
|
|
176
|
+
: orderedRows.slice(query.offset ?? 0);
|
|
177
|
+
const columns = Array.from(new Set([
|
|
178
|
+
'name',
|
|
179
|
+
'resource',
|
|
180
|
+
...query.steps.flatMap((step) => getStepSelect(step).map((item) => item.as)),
|
|
181
|
+
...(query.calcs ?? []).map((item) => item.as),
|
|
182
|
+
]));
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
kind: 'aggregate',
|
|
186
|
+
columns,
|
|
187
|
+
rows: slicedRows,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function getSingleAggregateWidgetData(
|
|
192
|
+
adminforth: IAdminForth,
|
|
193
|
+
query: ResourceQueryConfig,
|
|
194
|
+
aggregate: QueryAggregateSelectItem,
|
|
210
195
|
): Promise<DashboardWidgetData> {
|
|
211
196
|
const [currentValues = {}] = await getAggregateRows(
|
|
212
197
|
adminforth,
|
|
213
198
|
query.resource,
|
|
214
199
|
query.filters,
|
|
215
|
-
[
|
|
200
|
+
[aggregate],
|
|
216
201
|
[],
|
|
217
202
|
);
|
|
218
203
|
const values: Record<string, unknown> = {
|
|
219
|
-
[
|
|
204
|
+
[aggregate.as]: currentValues[aggregate.as] ?? 0,
|
|
220
205
|
};
|
|
221
206
|
|
|
222
207
|
const rows = query.sparkline
|
|
223
|
-
? await
|
|
208
|
+
? await getSingleAggregateSparklineRows(adminforth, query, aggregate, getAdminForthFilters(query.filters))
|
|
224
209
|
: [values];
|
|
225
210
|
const columns = Array.from(new Set([
|
|
226
|
-
|
|
211
|
+
aggregate.as,
|
|
227
212
|
...(query.sparkline ? [query.sparkline.as] : []),
|
|
228
213
|
]));
|
|
229
214
|
|
|
@@ -235,11 +220,11 @@ async function getMetricWidgetData(
|
|
|
235
220
|
};
|
|
236
221
|
}
|
|
237
222
|
|
|
238
|
-
async function
|
|
223
|
+
async function getSingleAggregateSparklineRows(
|
|
239
224
|
adminforth: IAdminForth,
|
|
240
|
-
query:
|
|
241
|
-
|
|
242
|
-
filters:
|
|
225
|
+
query: ResourceQueryConfig,
|
|
226
|
+
aggregate: QueryAggregateSelectItem,
|
|
227
|
+
filters: DashboardQueryFilters,
|
|
243
228
|
) {
|
|
244
229
|
const sparkline = query.sparkline!;
|
|
245
230
|
const groupBy = [{
|
|
@@ -251,7 +236,7 @@ async function getMetricSparklineRows(
|
|
|
251
236
|
adminforth,
|
|
252
237
|
query.resource,
|
|
253
238
|
filters,
|
|
254
|
-
[
|
|
239
|
+
[aggregate],
|
|
255
240
|
groupBy,
|
|
256
241
|
);
|
|
257
242
|
|
|
@@ -275,14 +260,14 @@ async function getResourceRows(
|
|
|
275
260
|
);
|
|
276
261
|
}
|
|
277
262
|
|
|
278
|
-
function buildPlainQueryRows(rows: Record<string, unknown>[], query:
|
|
263
|
+
function buildPlainQueryRows(rows: Record<string, unknown>[], query: ResourceQueryConfig, variables: DashboardVariables) {
|
|
279
264
|
const select = query.select ?? getDefaultSelect(rows);
|
|
280
265
|
return rows.map((row) => buildPlainRow(row, select, query.calcs, variables));
|
|
281
266
|
}
|
|
282
267
|
|
|
283
268
|
async function buildAggregateQueryRows(
|
|
284
269
|
adminforth: IAdminForth,
|
|
285
|
-
query:
|
|
270
|
+
query: ResourceQueryConfig,
|
|
286
271
|
variables: DashboardVariables,
|
|
287
272
|
) {
|
|
288
273
|
const select = query.select ?? [];
|
|
@@ -302,7 +287,7 @@ async function buildAggregateQueryRows(
|
|
|
302
287
|
async function getAggregateRows(
|
|
303
288
|
adminforth: IAdminForth,
|
|
304
289
|
resourceId: string,
|
|
305
|
-
baseFilters: FilterExpression |
|
|
290
|
+
baseFilters: FilterExpression | DashboardQueryFilters | undefined,
|
|
306
291
|
select: QueryAggregateSelectItem[],
|
|
307
292
|
groupBy: EffectiveGroupByItem[],
|
|
308
293
|
) {
|
|
@@ -356,7 +341,7 @@ function buildCalculatedRow(
|
|
|
356
341
|
const values: Record<string, unknown> = { ...baseValues };
|
|
357
342
|
|
|
358
343
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
359
|
-
values[item.as] = evaluateCalc(item.calc, values
|
|
344
|
+
values[item.as] = evaluateCalc(item.calc, values);
|
|
360
345
|
}
|
|
361
346
|
|
|
362
347
|
return values;
|
|
@@ -379,39 +364,12 @@ function buildPlainRow(
|
|
|
379
364
|
}
|
|
380
365
|
|
|
381
366
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
382
|
-
values[item.as] = evaluateCalc(item.calc, values
|
|
367
|
+
values[item.as] = evaluateCalc(item.calc, values);
|
|
383
368
|
}
|
|
384
369
|
|
|
385
370
|
return values;
|
|
386
371
|
}
|
|
387
372
|
|
|
388
|
-
function evaluateCalc(calc: string, values: Record<string, unknown>, variables: DashboardVariables) {
|
|
389
|
-
const expression = calc
|
|
390
|
-
.replace(LOOKUP_CALL_RE, (_match, path: string, keyField: string, defaultValue: string) => {
|
|
391
|
-
const map = resolveVariablePath(variables, path);
|
|
392
|
-
const key = String(values[keyField] ?? '');
|
|
393
|
-
|
|
394
|
-
return String(toFiniteNumber(isRecord(map) && Object.prototype.hasOwnProperty.call(map, key)
|
|
395
|
-
? map[key]
|
|
396
|
-
: Number(defaultValue)));
|
|
397
|
-
})
|
|
398
|
-
.replace(CALC_IDENTIFIER_RE, (name) => String(toFiniteNumber(values[name])));
|
|
399
|
-
|
|
400
|
-
if (!SAFE_CALC_EXPRESSION_RE.test(expression)) {
|
|
401
|
-
throw new Error(`Unsupported calc expression: ${calc}`);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return Function(`"use strict"; return (${expression});`)();
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function resolveVariablePath(variables: DashboardVariables, path: string) {
|
|
408
|
-
return path
|
|
409
|
-
.replace(VARIABLE_PATH_PREFIX_RE, '')
|
|
410
|
-
.split('.')
|
|
411
|
-
.filter(Boolean)
|
|
412
|
-
.reduce<unknown>((current, segment) => isRecord(current) ? current[segment] : undefined, variables);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
373
|
function sortRows(rows: Record<string, unknown>[], orderBy: QueryOrderByItem[] = []) {
|
|
416
374
|
if (!orderBy.length) {
|
|
417
375
|
return rows;
|
|
@@ -449,7 +407,7 @@ function getBackendSort(orderBy: QueryOrderByItem[] | undefined) {
|
|
|
449
407
|
: Sorts.DESC(order.field));
|
|
450
408
|
}
|
|
451
409
|
|
|
452
|
-
function getColumns(rows: Record<string, unknown>[], query:
|
|
410
|
+
function getColumns(rows: Record<string, unknown>[], query: ResourceQueryConfig) {
|
|
453
411
|
const selectColumns = [
|
|
454
412
|
...(query.group_by ?? []).map(getGroupByAlias),
|
|
455
413
|
...(query.select ?? []).map(getSelectAlias),
|
|
@@ -463,14 +421,14 @@ function getDefaultSelect(rows: Record<string, unknown>[]): QuerySelectItem[] {
|
|
|
463
421
|
return Object.keys(rows[0] ?? {}).map((field) => ({ field }));
|
|
464
422
|
}
|
|
465
423
|
|
|
466
|
-
function isAggregateQuery(query:
|
|
424
|
+
function isAggregateQuery(query: ResourceQueryConfig) {
|
|
467
425
|
return Boolean(
|
|
468
426
|
query.group_by?.length
|
|
469
427
|
|| query.select?.some((item) => isAggregateSelectItem(item)),
|
|
470
428
|
);
|
|
471
429
|
}
|
|
472
430
|
|
|
473
|
-
function
|
|
431
|
+
function getSingleAggregateSelectItem(query: ResourceQueryConfig) {
|
|
474
432
|
if (query.group_by?.length) {
|
|
475
433
|
return undefined;
|
|
476
434
|
}
|
|
@@ -485,6 +443,14 @@ function getSingleAggregateMetricSelect(query: QueryConfig) {
|
|
|
485
443
|
return aggregateItems[0];
|
|
486
444
|
}
|
|
487
445
|
|
|
446
|
+
function isStepsQuery(query: QueryConfig): query is Extract<QueryConfig, { source: 'steps' }> {
|
|
447
|
+
return query.source === 'steps';
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function getStepSelect(step: StepsQueryStepConfig): QueryAggregateSelectItem[] {
|
|
451
|
+
return step.select;
|
|
452
|
+
}
|
|
453
|
+
|
|
488
454
|
function isFieldSelectItem(item: QuerySelectItem): item is QueryFieldSelectItem {
|
|
489
455
|
return 'field' in item && !('agg' in item);
|
|
490
456
|
}
|
|
@@ -605,7 +571,7 @@ function applyAggregateDefaults(values: Record<string, unknown>, select: QueryAg
|
|
|
605
571
|
|
|
606
572
|
function groupAggregateSelectItems(select: QueryAggregateSelectItem[]) {
|
|
607
573
|
const groups = new Map<string, {
|
|
608
|
-
filters:
|
|
574
|
+
filters: DashboardQueryFilters;
|
|
609
575
|
items: QueryAggregateSelectItem[];
|
|
610
576
|
}>();
|
|
611
577
|
|
|
@@ -621,7 +587,7 @@ function groupAggregateSelectItems(select: QueryAggregateSelectItem[]) {
|
|
|
621
587
|
return Array.from(groups.values());
|
|
622
588
|
}
|
|
623
589
|
|
|
624
|
-
function getFilterCacheKey(filters:
|
|
590
|
+
function getFilterCacheKey(filters: DashboardQueryFilters) {
|
|
625
591
|
if (Array.isArray(filters) && !filters.length) {
|
|
626
592
|
return '__base__';
|
|
627
593
|
}
|
|
@@ -629,29 +595,6 @@ function getFilterCacheKey(filters: DashboardWidgetFilters) {
|
|
|
629
595
|
return JSON.stringify(filters);
|
|
630
596
|
}
|
|
631
597
|
|
|
632
|
-
function mergeFilters(...filters: Array<FilterExpression | DashboardWidgetFilters | undefined>) {
|
|
633
|
-
const merged: Array<IAdminForthSingleFilter | IAdminForthAndOrFilter> = [];
|
|
634
|
-
|
|
635
|
-
for (const filter of filters) {
|
|
636
|
-
const normalized = getAdminForthFilters(filter);
|
|
637
|
-
|
|
638
|
-
if (Array.isArray(normalized)) {
|
|
639
|
-
merged.push(...normalized);
|
|
640
|
-
continue;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
if (normalized) {
|
|
644
|
-
merged.push(normalized);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (!merged.length) {
|
|
649
|
-
return [] as DashboardWidgetFilters;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
return merged.length === 1 ? merged[0] : merged;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
598
|
function getHiddenAggregateAlias(groupBy: EffectiveGroupByItem[], select: QueryAggregateSelectItem[]) {
|
|
656
599
|
const usedAliases = new Set([
|
|
657
600
|
...groupBy.map((item) => item.as),
|
|
@@ -702,124 +645,6 @@ function formatGroupValue(value: unknown, grain: TimeGrain | undefined) {
|
|
|
702
645
|
return value;
|
|
703
646
|
}
|
|
704
647
|
|
|
705
|
-
function getAdminForthFilters(filters: FilterExpression | DashboardWidgetFilters | undefined): DashboardWidgetFilters {
|
|
706
|
-
if (Array.isArray(filters)) {
|
|
707
|
-
return filters.map((filter) => isDashboardFilterExpression(filter)
|
|
708
|
-
? toAdminForthFilter(filter)
|
|
709
|
-
: filter);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (isDashboardFilterExpression(filters)) {
|
|
713
|
-
return toAdminForthFilter(filters);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (filters) {
|
|
717
|
-
return filters;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return [];
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function isDashboardFilterExpression(value: unknown): value is FilterExpression {
|
|
724
|
-
if (Array.isArray(value)) {
|
|
725
|
-
return true;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
if (!isRecord(value)) {
|
|
729
|
-
return false;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
return 'and' in value
|
|
733
|
-
|| 'or' in value
|
|
734
|
-
|| 'eq' in value
|
|
735
|
-
|| 'neq' in value
|
|
736
|
-
|| 'gt' in value
|
|
737
|
-
|| 'gte' in value
|
|
738
|
-
|| 'lt' in value
|
|
739
|
-
|| 'lte' in value
|
|
740
|
-
|| 'in' in value
|
|
741
|
-
|| 'not_in' in value
|
|
742
|
-
|| 'like' in value
|
|
743
|
-
|| 'ilike' in value;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function toAdminForthFilter(filter: FilterExpression): IAdminForthSingleFilter | IAdminForthAndOrFilter {
|
|
747
|
-
if (Array.isArray(filter)) {
|
|
748
|
-
return Filters.AND(filter.map((item) => toAdminForthFilter(item)));
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
if ('and' in filter) {
|
|
752
|
-
return Filters.AND(filter.and.map((item) => toAdminForthFilter(item)));
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
if ('or' in filter) {
|
|
756
|
-
return Filters.OR(filter.or.map((item) => toAdminForthFilter(item)));
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
for (const [operator, createFilter] of Object.entries(FILTER_OPERATORS)) {
|
|
760
|
-
if (Object.prototype.hasOwnProperty.call(filter, operator)) {
|
|
761
|
-
return createFilter(filter.field, resolveFilterValue(filter[operator as keyof typeof FILTER_OPERATORS]));
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
return Filters.AND([]);
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
function resolveFilterValue(value: unknown): unknown {
|
|
769
|
-
if (Array.isArray(value)) {
|
|
770
|
-
return value.map((item) => resolveFilterValue(item));
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
if (!isRecord(value)) {
|
|
774
|
-
return value;
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
if (value.now === true) {
|
|
778
|
-
return new Date().toISOString();
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (typeof value.now_minus === 'string') {
|
|
782
|
-
return subtractDuration(new Date(), value.now_minus).toISOString();
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
return value;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function subtractDuration(now: Date, duration: string) {
|
|
789
|
-
const match = duration.match(RELATIVE_DURATION_RE);
|
|
790
|
-
|
|
791
|
-
if (!match) {
|
|
792
|
-
throw new Error(`Unsupported relative date duration: ${duration}`);
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
const amount = Number(match[1]);
|
|
796
|
-
const unit = match[2];
|
|
797
|
-
const date = new Date(now);
|
|
798
|
-
|
|
799
|
-
if (unit === 'h') {
|
|
800
|
-
date.setUTCHours(date.getUTCHours() - amount);
|
|
801
|
-
} else if (unit === 'd') {
|
|
802
|
-
date.setUTCDate(date.getUTCDate() - amount);
|
|
803
|
-
} else if (unit === 'w') {
|
|
804
|
-
date.setUTCDate(date.getUTCDate() - amount * 7);
|
|
805
|
-
} else if (unit === 'mo') {
|
|
806
|
-
date.setUTCMonth(date.getUTCMonth() - amount);
|
|
807
|
-
} else if (unit === 'y') {
|
|
808
|
-
date.setUTCFullYear(date.getUTCFullYear() - amount);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
return date;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
function toFiniteNumber(value: unknown) {
|
|
815
|
-
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
816
|
-
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function isRecord(value: unknown): value is Record<string, any> {
|
|
820
|
-
return typeof value === 'object' && value !== null;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
648
|
export function createWidgetDataService(adminforth: IAdminForth): WidgetDataService {
|
|
824
649
|
return {
|
|
825
650
|
getWidgetData: (widget, options) => getWidgetData(adminforth, widget, options),
|