@adminforth/dashboard 1.9.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/custom/api/dashboardApi.ts +73 -36
- package/custom/runtime/DashboardRuntime.vue +26 -22
- 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/runtime/DashboardRuntime.vue +26 -22
- package/dist/endpoint/groups.js +22 -20
- package/dist/endpoint/widgets.js +28 -26
- package/dist/schema/api.d.ts +12 -1738
- package/dist/schema/api.js +7 -12
- package/dist/services/calc-evaluator.d.ts +2 -1
- package/dist/services/calc-evaluator.js +29 -3
- package/dist/services/widgetDataService.js +2 -2
- package/endpoint/groups.ts +22 -20
- package/endpoint/widgets.ts +28 -26
- package/package.json +1 -1
- package/schema/api.ts +7 -12
- package/services/calc-evaluator.ts +41 -3
- package/services/widgetDataService.ts +2 -2
package/dist/schema/api.js
CHANGED
|
@@ -159,18 +159,13 @@ export const ConfigurePieChartWidgetRequestZodSchema = configureWidgetRequestSch
|
|
|
159
159
|
export const ConfigureHistogramChartWidgetRequestZodSchema = configureWidgetRequestSchema(ConfigurableHistogramChartWidgetConfigSchema);
|
|
160
160
|
export const ConfigureFunnelChartWidgetRequestZodSchema = configureWidgetRequestSchema(ConfigurableFunnelChartWidgetConfigSchema);
|
|
161
161
|
export const ConfigurePivotTableWidgetRequestZodSchema = configureWidgetRequestSchema(ConfigurablePivotTableWidgetConfigSchema);
|
|
162
|
-
export const DashboardMutationResponseZodSchema = z.
|
|
163
|
-
z.
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
revision: z.number().optional(),
|
|
170
|
-
}).strict(),
|
|
171
|
-
DashboardErrorResponseZodSchema,
|
|
172
|
-
]);
|
|
173
|
-
export const DashboardApiResponseSchema = toJSONSchema(DashboardApiResponseZodSchema, { target: 'draft-07' });
|
|
162
|
+
export const DashboardMutationResponseZodSchema = z.object({
|
|
163
|
+
ok: z.boolean(),
|
|
164
|
+
error: z.string().optional(),
|
|
165
|
+
groupId: z.string().optional(),
|
|
166
|
+
widgetId: z.string().optional(),
|
|
167
|
+
}).strict();
|
|
168
|
+
export const DashboardApiResponseSchema = toJSONSchema(z.unknown(), { target: 'draft-07' });
|
|
174
169
|
export const DashboardWidgetDataResponseSchema = toJSONSchema(DashboardWidgetDataResponseZodSchema, { target: 'draft-07' });
|
|
175
170
|
export const SlugRequestSchema = toJSONSchema(SlugRequestZodSchema, { target: 'draft-07' });
|
|
176
171
|
export const GroupIdRequestSchema = toJSONSchema(GroupIdRequestZodSchema, { target: 'draft-07' });
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { DashboardVariables } from '../custom/model/dashboard.types.js';
|
|
2
|
+
export declare function evaluateCalc(calc: string, values: Record<string, unknown>, variables?: DashboardVariables): any;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
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;
|
|
2
3
|
const CALC_PARSER_OPTIONS = {
|
|
3
4
|
allowMemberAccess: false,
|
|
4
5
|
operators: {
|
|
@@ -12,9 +13,25 @@ const CALC_PARSER_OPTIONS = {
|
|
|
12
13
|
random: false,
|
|
13
14
|
},
|
|
14
15
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return
|
|
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
|
+
});
|
|
18
35
|
}
|
|
19
36
|
function normalizeCalcValues(values) {
|
|
20
37
|
return Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
@@ -26,3 +43,12 @@ function toFiniteNumber(value) {
|
|
|
26
43
|
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
27
44
|
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
28
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
|
+
}
|
|
@@ -175,7 +175,7 @@ function getAggregateRows(adminforth, resourceId, baseFilters, select, groupBy)
|
|
|
175
175
|
function buildCalculatedRow(baseValues, select, calcs = [], variables) {
|
|
176
176
|
const values = Object.assign({}, baseValues);
|
|
177
177
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
178
|
-
values[item.as] = evaluateCalc(item.calc, values);
|
|
178
|
+
values[item.as] = evaluateCalc(item.calc, values, variables);
|
|
179
179
|
}
|
|
180
180
|
return values;
|
|
181
181
|
}
|
|
@@ -190,7 +190,7 @@ function buildPlainRow(row, select, calcs = [], variables) {
|
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
192
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
193
|
-
values[item.as] = evaluateCalc(item.calc, values);
|
|
193
|
+
values[item.as] = evaluateCalc(item.calc, values, variables);
|
|
194
194
|
}
|
|
195
195
|
return values;
|
|
196
196
|
}
|
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
|
|
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
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' })
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Parser } from 'expr-eval-fork';
|
|
2
|
+
import type { DashboardVariables } from '../custom/model/dashboard.types.js';
|
|
2
3
|
|
|
4
|
+
const LOOKUP_VARIABLE_PATH_RE = /lookup\(\s*\$variables((?:\.[a-zA-Z_][a-zA-Z0-9_]*)+)\s*,/g;
|
|
3
5
|
const CALC_PARSER_OPTIONS = {
|
|
4
6
|
allowMemberAccess: false,
|
|
5
7
|
operators: {
|
|
@@ -14,10 +16,35 @@ const CALC_PARSER_OPTIONS = {
|
|
|
14
16
|
},
|
|
15
17
|
} as const;
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
export function evaluateCalc(
|
|
20
|
+
calc: string,
|
|
21
|
+
values: Record<string, unknown>,
|
|
22
|
+
variables: DashboardVariables = {},
|
|
23
|
+
) {
|
|
24
|
+
const parser = createCalcParser(variables);
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
});
|
|
21
48
|
}
|
|
22
49
|
|
|
23
50
|
function normalizeCalcValues(values: Record<string, unknown>) {
|
|
@@ -31,3 +58,14 @@ function toFiniteNumber(value: unknown) {
|
|
|
31
58
|
const numberValue = typeof value === 'number' ? value : Number(value);
|
|
32
59
|
return Number.isFinite(numberValue) ? numberValue : 0;
|
|
33
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
|
+
}
|
|
@@ -341,7 +341,7 @@ function buildCalculatedRow(
|
|
|
341
341
|
const values: Record<string, unknown> = { ...baseValues };
|
|
342
342
|
|
|
343
343
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
344
|
-
values[item.as] = evaluateCalc(item.calc, values);
|
|
344
|
+
values[item.as] = evaluateCalc(item.calc, values, variables);
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
return values;
|
|
@@ -364,7 +364,7 @@ function buildPlainRow(
|
|
|
364
364
|
}
|
|
365
365
|
|
|
366
366
|
for (const item of [...select.filter(isCalcSelectItem), ...calcs]) {
|
|
367
|
-
values[item.as] = evaluateCalc(item.calc, values);
|
|
367
|
+
values[item.as] = evaluateCalc(item.calc, values, variables);
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
return values;
|