commit 5bfb673fe313ed99b0abff24fe5a3daba002a6bb
parent 41b06846b48bdf540896e636ff8f69859ec22109
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 13 May 2025 19:08:33 -0300
fix #9905
Diffstat:
2 files changed, 112 insertions(+), 16 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx b/packages/aml-backoffice-ui/src/pages/NewMeasure.tsx
@@ -10,19 +10,22 @@ import {
FormDesign,
FormUI,
InternationalizationAPI,
- undefinedIfEmpty,
useForm,
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
-import { useServerMeasures } from "../hooks/server-info.js";
import { useCurrentDecisionRequest } from "../hooks/decision-request.js";
+import { useServerMeasures } from "../hooks/server-info.js";
export type MeasureDefinition = {
name: string;
program: string;
check: string;
- context: { key: string; value: string }[];
+ context: {
+ key: string;
+ type: "string" | "number" | "boolean" | "json";
+ value: string;
+ }[];
};
/**
@@ -129,7 +132,7 @@ export function MeasureForm({
const context =
!form.status.result || !form.status.result.context
? []
- : (form.status.result.context as { key: string; value: string }[]);
+ : (form.status.result.context as MeasureDefinition["context"]);
return (
<div>
@@ -159,13 +162,13 @@ export function MeasureForm({
prog_name: newMeasure.program,
context: (newMeasure.context ?? []).reduce(
(prev, cur) => {
- prev[cur.key] = cur.value;
+ prev[cur.key] = getContextValueByType(cur.type, cur.value);
return prev;
},
- {} as Record<string, string>,
+ {} as Record<string, any>,
),
};
- updateRequest("add new measure",{
+ updateRequest("add new measure", {
custom_measures: currentMeasures,
});
onCancel();
@@ -180,20 +183,20 @@ export function MeasureForm({
disabled={form.status.status === "fail"}
onClick={() => {
const newMeasure = form.status.result as MeasureDefinition;
-
+
const CURRENT_MEASURES = { ...request.custom_measures };
CURRENT_MEASURES[name!] = {
check_name: newMeasure.check,
prog_name: newMeasure.program,
context: (newMeasure.context ?? []).reduce(
(prev, cur) => {
- prev[cur.key] = cur.value;
+ prev[cur.key] = getContextValueByType(cur.type, cur.value);
return prev;
},
- {} as Record<string, string>,
+ {} as Record<string, any>,
),
};
- updateRequest("update measure",{
+ updateRequest("update measure", {
custom_measures: CURRENT_MEASURES,
});
onCancel();
@@ -206,7 +209,7 @@ export function MeasureForm({
onClick={() => {
const currentMeasures = { ...request.custom_measures };
delete currentMeasures[name!];
- updateRequest("remove measure",{
+ updateRequest("remove measure", {
custom_measures: currentMeasures,
});
onCancel();
@@ -410,12 +413,41 @@ const formDesign = (
{
type: "text",
id: "key",
+ required: true,
label: i18n.str`Field name`,
},
{
- type: "text",
+ type: "choiceHorizontal",
+ id: "type",
+ label: i18n.str`Type`,
+ required: true,
+ choices: [
+ {
+ label: i18n.str`string`,
+ value: "string",
+ },
+ {
+ label: i18n.str`number`,
+ value: "number",
+ },
+ {
+ label: i18n.str`boolean`,
+ value: "boolean",
+ },
+ {
+ label: i18n.str`json`,
+ value: "json",
+ },
+ ],
+ },
+ {
+ type: "textArea",
id: "value",
+ required: true,
label: i18n.str`Value`,
+ validator(value, form) {
+ return validateContextValueByType(i18n, form["type"], value);
+ },
},
],
},
@@ -483,3 +515,65 @@ function programAndContextMatch(
}
return;
}
+
+function getJsonError(str: string) {
+ try {
+ JSON.parse(str);
+ return undefined;
+ } catch (e) {
+ if (e instanceof SyntaxError) {
+ return e.message;
+ }
+ return String(e);
+ }
+}
+
+// convert the string value of the form into the corresponding type
+// based on the user choice
+// check the function validateContextValueByType
+function getContextValueByType(type: string, value: string) {
+ if (type === "number") {
+ return Number.parseInt(value, 10);
+ }
+ if (type === "boolean") {
+ return value === "true" ? true : value === "false" ? false : undefined;
+ }
+ if (type === "json") {
+ return JSON.parse(value);
+ }
+ return value;
+}
+
+const REGEX_NUMER = /$[0-9]*^/;
+
+function validateContextValueByType(
+ i18n: InternationalizationAPI,
+ type: string,
+ value: string,
+) {
+ if (!value) return i18n.str`Can't be empty`;
+ if (type === "number") {
+ const num = Number.parseInt(value, 10);
+ return !REGEX_NUMER.test(value)
+ ? i18n.str`It should be a number`
+ : Number.isNaN(num)
+ ? i18n.str`Not a number`
+ : !Number.isFinite(num)
+ ? i18n.str`It should be finite`
+ : !Number.isSafeInteger(num)
+ ? i18n.str`It should be a safe integer`
+ : undefined;
+ }
+ if (type === "boolean") {
+ if (value === "true" || value === "false") return undefined;
+ return i18n.str`It should be either "true" or "false"`;
+ }
+ if (type === "json") {
+ const error = getJsonError(value);
+ if (error) {
+ return i18n.str`Couldn't parse as json string: ${error}`;
+ }
+ return undefined;
+ }
+ return undefined;
+}
diff --git a/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx b/packages/aml-backoffice-ui/src/pages/decision/Measures.tsx
@@ -148,7 +148,8 @@ function ShowAllMeasures({
? []
: Object.entries(m.context).map(([key, value]) => ({
key,
- value,
+ type: "json",
+ value: JSON.stringify(value),
})),
name: m.name,
program: m.type !== "info" ? m.programName : undefined,
@@ -174,8 +175,9 @@ function ShowAllMeasures({
? []
: Object.entries(m.context).map(([key, value]) => ({
key,
- value,
- })),
+ type: "json",
+ value: JSON.stringify(value),
+ })),
name: m.name,
program: m.type !== "info" ? m.programName : undefined,
});