@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
package/endpoint/widgets.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
ConfigurePivotTableWidgetRequestSchema,
|
|
22
22
|
ConfigureStackedBarChartWidgetRequestSchema,
|
|
23
23
|
ConfigureTableWidgetRequestSchema,
|
|
24
|
-
|
|
24
|
+
DashboardMutationResponseSchema,
|
|
25
25
|
DashboardWidgetDataResponseSchema,
|
|
26
26
|
GroupIdRequestSchema,
|
|
27
27
|
MoveWidgetRequestSchema,
|
|
@@ -117,11 +117,11 @@ function registerConfigureWidgetEndpoint(
|
|
|
117
117
|
path: options.path,
|
|
118
118
|
description: options.description,
|
|
119
119
|
request_schema: options.requestSchema,
|
|
120
|
-
response_schema:
|
|
120
|
+
response_schema: DashboardMutationResponseSchema,
|
|
121
121
|
handler: async ({ body, adminUser, response }) => {
|
|
122
122
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
123
123
|
response.setStatus(403);
|
|
124
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
124
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const request = body as ConfigureWidgetRequest;
|
|
@@ -134,15 +134,15 @@ function registerConfigureWidgetEndpoint(
|
|
|
134
134
|
|
|
135
135
|
if (!updatedDashboard) {
|
|
136
136
|
response.setStatus(404);
|
|
137
|
-
return { error: 'Dashboard not found' };
|
|
137
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
if (mutationError) {
|
|
141
141
|
response.setStatus(404);
|
|
142
|
-
return { error: mutationError };
|
|
142
|
+
return { ok: false, error: mutationError };
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
return
|
|
145
|
+
return { ok: true };
|
|
146
146
|
},
|
|
147
147
|
});
|
|
148
148
|
}
|
|
@@ -156,14 +156,15 @@ export function registerWidgetEndpoints(
|
|
|
156
156
|
path: '/dashboard/add_dashboard_widget',
|
|
157
157
|
description: 'Adds a new empty widget to a dashboard group. Superadmin only.',
|
|
158
158
|
request_schema: GroupIdRequestSchema,
|
|
159
|
-
response_schema:
|
|
159
|
+
response_schema: DashboardMutationResponseSchema,
|
|
160
160
|
handler: async ({ body, adminUser, response }) => {
|
|
161
161
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
162
162
|
response.setStatus(403);
|
|
163
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
163
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
let mutationError: string | null = null;
|
|
167
|
+
let widgetId: string | null = null;
|
|
167
168
|
const updatedDashboard = await ctx.updateDashboardConfig(body.slug, (config) => {
|
|
168
169
|
const group = config.groups.find((item) => item.id === body.groupId);
|
|
169
170
|
|
|
@@ -181,6 +182,7 @@ export function registerWidgetEndpoints(
|
|
|
181
182
|
order: nextOrder,
|
|
182
183
|
target: 'empty',
|
|
183
184
|
};
|
|
185
|
+
widgetId = widget.id;
|
|
184
186
|
|
|
185
187
|
return {
|
|
186
188
|
...config,
|
|
@@ -190,15 +192,15 @@ export function registerWidgetEndpoints(
|
|
|
190
192
|
|
|
191
193
|
if (!updatedDashboard) {
|
|
192
194
|
response.setStatus(404);
|
|
193
|
-
return { error: 'Dashboard not found' };
|
|
195
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
194
196
|
}
|
|
195
197
|
|
|
196
198
|
if (mutationError) {
|
|
197
199
|
response.setStatus(404);
|
|
198
|
-
return { error: mutationError };
|
|
200
|
+
return { ok: false, error: mutationError };
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
return
|
|
203
|
+
return { ok: true, widgetId };
|
|
202
204
|
},
|
|
203
205
|
});
|
|
204
206
|
|
|
@@ -207,11 +209,11 @@ export function registerWidgetEndpoints(
|
|
|
207
209
|
path: '/dashboard/move_dashboard_widget',
|
|
208
210
|
description: 'Moves a dashboard widget up or down inside its group. Superadmin only.',
|
|
209
211
|
request_schema: MoveWidgetRequestSchema,
|
|
210
|
-
response_schema:
|
|
212
|
+
response_schema: DashboardMutationResponseSchema,
|
|
211
213
|
handler: async ({ body, adminUser, response }) => {
|
|
212
214
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
213
215
|
response.setStatus(403);
|
|
214
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
216
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
let mutationError: string | null = null;
|
|
@@ -249,15 +251,15 @@ export function registerWidgetEndpoints(
|
|
|
249
251
|
|
|
250
252
|
if (!updatedDashboard) {
|
|
251
253
|
response.setStatus(404);
|
|
252
|
-
return { error: 'Dashboard not found' };
|
|
254
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
253
255
|
}
|
|
254
256
|
|
|
255
257
|
if (mutationError) {
|
|
256
258
|
response.setStatus(404);
|
|
257
|
-
return { error: mutationError };
|
|
259
|
+
return { ok: false, error: mutationError };
|
|
258
260
|
}
|
|
259
261
|
|
|
260
|
-
return
|
|
262
|
+
return { ok: true };
|
|
261
263
|
},
|
|
262
264
|
});
|
|
263
265
|
|
|
@@ -266,11 +268,11 @@ export function registerWidgetEndpoints(
|
|
|
266
268
|
path: '/dashboard/remove_dashboard_widget',
|
|
267
269
|
description: 'Removes one dashboard widget by id. Superadmin only.',
|
|
268
270
|
request_schema: WidgetIdRequestSchema,
|
|
269
|
-
response_schema:
|
|
271
|
+
response_schema: DashboardMutationResponseSchema,
|
|
270
272
|
handler: async ({ body, adminUser, response }) => {
|
|
271
273
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
272
274
|
response.setStatus(403);
|
|
273
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
275
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
274
276
|
}
|
|
275
277
|
|
|
276
278
|
let mutationError: string | null = null;
|
|
@@ -290,15 +292,15 @@ export function registerWidgetEndpoints(
|
|
|
290
292
|
|
|
291
293
|
if (!updatedDashboard) {
|
|
292
294
|
response.setStatus(404);
|
|
293
|
-
return { error: 'Dashboard not found' };
|
|
295
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
294
296
|
}
|
|
295
297
|
|
|
296
298
|
if (mutationError) {
|
|
297
299
|
response.setStatus(404);
|
|
298
|
-
return { error: mutationError };
|
|
300
|
+
return { ok: false, error: mutationError };
|
|
299
301
|
}
|
|
300
302
|
|
|
301
|
-
return
|
|
303
|
+
return { ok: true };
|
|
302
304
|
},
|
|
303
305
|
});
|
|
304
306
|
|
|
@@ -308,11 +310,11 @@ export function registerWidgetEndpoints(
|
|
|
308
310
|
path: '/dashboard/set_widget_config',
|
|
309
311
|
description: 'Replaces editable JSON configuration for a dashboard widget while preserving widget id, group id, and order. Superadmin only.',
|
|
310
312
|
request_schema: SetWidgetConfigRequestSchema,
|
|
311
|
-
response_schema:
|
|
313
|
+
response_schema: DashboardMutationResponseSchema,
|
|
312
314
|
handler: async ({ body, adminUser, response }) => {
|
|
313
315
|
if (!ctx.canEditDashboard(adminUser)) {
|
|
314
316
|
response.setStatus(403);
|
|
315
|
-
return { error: 'Dashboard edit is not allowed' };
|
|
317
|
+
return { ok: false, error: 'Dashboard edit is not allowed' };
|
|
316
318
|
}
|
|
317
319
|
|
|
318
320
|
const request = body as ConfigureWidgetRequest;
|
|
@@ -320,15 +322,15 @@ export function registerWidgetEndpoints(
|
|
|
320
322
|
|
|
321
323
|
if (!updatedDashboard) {
|
|
322
324
|
response.setStatus(404);
|
|
323
|
-
return { error: 'Dashboard not found' };
|
|
325
|
+
return { ok: false, error: 'Dashboard not found' };
|
|
324
326
|
}
|
|
325
327
|
|
|
326
328
|
if (mutationError) {
|
|
327
329
|
response.setStatus(404);
|
|
328
|
-
return { error: mutationError };
|
|
330
|
+
return { ok: false, error: mutationError };
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
return
|
|
333
|
+
return { ok: true };
|
|
332
334
|
},
|
|
333
335
|
});
|
|
334
336
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.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/api.ts
CHANGED
|
@@ -203,19 +203,14 @@ export const ConfigureHistogramChartWidgetRequestZodSchema = configureWidgetRequ
|
|
|
203
203
|
export const ConfigureFunnelChartWidgetRequestZodSchema = configureWidgetRequestSchema(ConfigurableFunnelChartWidgetConfigSchema)
|
|
204
204
|
export const ConfigurePivotTableWidgetRequestZodSchema = configureWidgetRequestSchema(ConfigurablePivotTableWidgetConfigSchema)
|
|
205
205
|
|
|
206
|
-
export const DashboardMutationResponseZodSchema = z.
|
|
207
|
-
z.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
target: z.string().optional(),
|
|
213
|
-
revision: z.number().optional(),
|
|
214
|
-
}).strict(),
|
|
215
|
-
DashboardErrorResponseZodSchema,
|
|
216
|
-
])
|
|
206
|
+
export const DashboardMutationResponseZodSchema = z.object({
|
|
207
|
+
ok: z.boolean(),
|
|
208
|
+
error: z.string().optional(),
|
|
209
|
+
groupId: z.string().optional(),
|
|
210
|
+
widgetId: z.string().optional(),
|
|
211
|
+
}).strict()
|
|
217
212
|
|
|
218
|
-
export const DashboardApiResponseSchema = toJSONSchema(
|
|
213
|
+
export const DashboardApiResponseSchema = toJSONSchema(z.unknown(), { target: 'draft-07' })
|
|
219
214
|
export const DashboardWidgetDataResponseSchema = toJSONSchema(DashboardWidgetDataResponseZodSchema, { target: 'draft-07' })
|
|
220
215
|
export const SlugRequestSchema = toJSONSchema(SlugRequestZodSchema, { target: 'draft-07' })
|
|
221
216
|
export const GroupIdRequestSchema = toJSONSchema(GroupIdRequestZodSchema, { target: 'draft-07' })
|
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,71 @@
|
|
|
1
|
+
import { Parser } from 'expr-eval-fork';
|
|
2
|
+
import type { DashboardVariables } from '../custom/model/dashboard.types.js';
|
|
3
|
+
|
|
4
|
+
const LOOKUP_VARIABLE_PATH_RE = /lookup\(\s*\$variables((?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\s*,/g;
|
|
5
|
+
const CALC_PARSER_OPTIONS = {
|
|
6
|
+
allowMemberAccess: false,
|
|
7
|
+
operators: {
|
|
8
|
+
assignment: false,
|
|
9
|
+
concatenate: false,
|
|
10
|
+
conditional: true,
|
|
11
|
+
comparison: true,
|
|
12
|
+
fndef: false,
|
|
13
|
+
in: false,
|
|
14
|
+
logical: true,
|
|
15
|
+
random: false,
|
|
16
|
+
},
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export function evaluateCalc(
|
|
20
|
+
calc: string,
|
|
21
|
+
values: Record<string, unknown>,
|
|
22
|
+
variables: DashboardVariables = {},
|
|
23
|
+
) {
|
|
24
|
+
const parser = createCalcParser(variables);
|
|
25
|
+
|
|
26
|
+
return parser.parse(normalizeLookupPaths(calc)).evaluate(normalizeCalcValues(values));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function createCalcParser(variables: DashboardVariables) {
|
|
30
|
+
const parser = new Parser(CALC_PARSER_OPTIONS);
|
|
31
|
+
|
|
32
|
+
parser.functions.lookup = (path: string | number, key: string | number, defaultValue = 0) => {
|
|
33
|
+
const map = resolveVariablePath(variables, String(path));
|
|
34
|
+
const value = isRecord(map) && Object.prototype.hasOwnProperty.call(map, String(key))
|
|
35
|
+
? map[String(key)]
|
|
36
|
+
: defaultValue;
|
|
37
|
+
|
|
38
|
+
return toFiniteNumber(value);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return parser;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeLookupPaths(calc: string) {
|
|
45
|
+
return calc.replace(LOOKUP_VARIABLE_PATH_RE, (_match, path: string) => {
|
|
46
|
+
return `lookup("${path.replace(/^\./, '')}",`;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeCalcValues(values: Record<string, unknown>) {
|
|
51
|
+
return Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
52
|
+
key,
|
|
53
|
+
typeof value === 'string' ? value : toFiniteNumber(value),
|
|
54
|
+
]));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toFiniteNumber(value: unknown) {
|
|
58
|
+
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
59
|
+
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveVariablePath(variables: DashboardVariables, path: string) {
|
|
63
|
+
return path
|
|
64
|
+
.split('.')
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.reduce<unknown>((current, segment) => isRecord(current) ? current[segment] : undefined, variables);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isRecord(value: unknown): value is Record<string, any> {
|
|
70
|
+
return typeof value === 'object' && value !== null;
|
|
71
|
+
}
|
|
@@ -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
|
+
}
|