commit 88dd48fb841c5303e5eb9c83ae61fe3eb16957e1
parent 2262e53bf60270736dc7583923898074065ed76b
Author: Sebastian <sebasjm@gmail.com>
Date: Mon, 17 Feb 2025 15:16:57 -0300
fix #9539
Diffstat:
7 files changed, 251 insertions(+), 80 deletions(-)
diff --git a/packages/aml-backoffice-ui/src/hooks/server-info.ts b/packages/aml-backoffice-ui/src/hooks/server-info.ts
@@ -23,6 +23,7 @@ import {
opFixedSuccess,
PaytoString,
TalerCorebankApi,
+ TalerExchangeErrorsByMethod,
TalerExchangeResultByMethod,
TalerHttpError,
} from "@gnu-taler/taler-util";
@@ -95,12 +96,27 @@ export function useServerStatistics(
.map(([name]) => name as AmlEventsName);
}, [dialect]);
+ type Success = TalerExchangeResultByMethod<"getAmlKycStatistics">;
+ type Error = TalerExchangeErrorsByMethod<"getAmlKycStatistics">;
+ type Response = {
+ key: AmlEventsName;
+ current: Success | Error;
+ previous: Success | Error | undefined;
+ };
+
async function fetcher([officer, keys, current, previous]: [
OfficerAccount,
AmlEventsName[],
Timeframe,
Timeframe | undefined,
]) {
+ const firstQuery = await api.getAmlKycStatistics(officer, "test", {
+ since: current.start,
+ until: current.end,
+ });
+ if (firstQuery.type === "fail") {
+ return firstQuery;
+ }
const queries = keys.map((key) => {
return Promise.all([
api.getAmlKycStatistics(officer, key, {
@@ -114,7 +130,8 @@ export function useServerStatistics(
until: previous.end,
}),
]).then(([c, p]) => {
- return { key, current: c.body, previous: p?.body };
+ const ret: Response = { key, current: c, previous: p };
+ return ret;
});
});
@@ -124,9 +141,7 @@ export function useServerStatistics(
}
const { data, error } = useSWR<
- OperationOk<
- { key: AmlEventsName; current: EventCounter; previous?: EventCounter }[]
- >,
+ OperationOk<Response[]> | Error,
TalerHttpError
>(!session ? undefined : [session, keys, current, previous], fetcher, {
refreshInterval: 0,
diff --git a/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx b/packages/aml-backoffice-ui/src/pages/CaseDetails.tsx
@@ -532,21 +532,44 @@ function ShowMesaureInfo({ nextMeasures }: { nextMeasures: string[] }): VNode {
}
if (measures.type === "fail") {
switch (measures.case) {
- // case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account signature is wrong, contact administrator or create
- a new one.
+ This account signature is invalid, contact administrator or
+ create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.NotFound:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not known, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.Conflict:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
</i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
default:
- assertUnreachable(measures.case);
+ assertUnreachable(measures);
}
}
const filteredMeasures = nextMeasures.filter((n) => !!n && !!n.trim());
@@ -1233,21 +1256,44 @@ export function ShowMeasuresToSelect({
}
if (measures.type === "fail") {
switch (measures.case) {
- // case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account signature is wrong, contact administrator or create
- a new one.
+ This account signature is invalid, contact administrator or
+ create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.NotFound:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not known, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.Conflict:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
</i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
default:
- assertUnreachable(measures.case);
+ assertUnreachable(measures);
}
}
diff --git a/packages/aml-backoffice-ui/src/pages/Cases.tsx b/packages/aml-backoffice-ui/src/pages/Cases.tsx
@@ -207,44 +207,42 @@ export function Cases({
if (list.type === "fail") {
switch (list.case) {
- case HttpStatusCode.Forbidden: {
+ case HttpStatusCode.Forbidden:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account signature is wrong, contact administrator or create
- a new one.
+ This account signature is invalid, contact administrator or
+ create a new one.
</i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
- }
- case HttpStatusCode.NotFound: {
+ case HttpStatusCode.NotFound:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
- <i18n.Translate>This account is not known.</i18n.Translate>
+ <i18n.Translate>
+ The designated AML account is not known, contact administrator
+ or create a new one.
+ </i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
- }
case HttpStatusCode.Conflict:
- {
- return (
- <Fragment>
- <Attention type="danger" title={i18n.str`Operation denied`}>
- <i18n.Translate>
- This account doesn't have access. Request account activation
- sending your public key.
- </i18n.Translate>
- </Attention>
- <Officer />
- </Fragment>
- );
- }
- return <Officer />;
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
default:
assertUnreachable(list);
}
@@ -281,44 +279,42 @@ export function CasesUnderInvestigation({
if (list.type === "fail") {
switch (list.case) {
- case HttpStatusCode.Forbidden: {
+ case HttpStatusCode.Forbidden:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account signature is wrong, contact administrator or create
- a new one.
+ This account signature is invalid, contact administrator or
+ create a new one.
</i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
- }
- case HttpStatusCode.NotFound: {
+ case HttpStatusCode.NotFound:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
- <i18n.Translate>This account is not known.</i18n.Translate>
+ <i18n.Translate>
+ The designated AML account is not known, contact administrator
+ or create a new one.
+ </i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
- }
case HttpStatusCode.Conflict:
- {
- return (
- <Fragment>
- <Attention type="danger" title={i18n.str`Operation denied`}>
- <i18n.Translate>
- This account doesn't have access. Request account activation
- sending your public key.
- </i18n.Translate>
- </Attention>
- <Officer />
- </Fragment>
- );
- }
- return <Officer />;
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
default:
assertUnreachable(list);
}
diff --git a/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx b/packages/aml-backoffice-ui/src/pages/CreateAccount.tsx
@@ -70,7 +70,7 @@ const createAccountForm = (
label: i18n.str`Repeat password`,
required: true,
validator(value) {
- return value
+ return !value
? i18n.str`required`
: // : state.password !== value
// ? i18n.str`doesn't match`
diff --git a/packages/aml-backoffice-ui/src/pages/Dashboard.tsx b/packages/aml-backoffice-ui/src/pages/Dashboard.tsx
@@ -17,12 +17,19 @@ import {
AbsoluteTime,
AmlSpaDialect,
assertUnreachable,
+ HttpStatusCode,
+ OperationFail,
+ OperationOk,
TalerCorebankApi,
TalerError,
+ TalerExchangeErrorsByMethod,
+ TalerExchangeResultByMethod,
TranslatedString,
} from "@gnu-taler/taler-util";
import {
+ Attention,
InternationalizationAPI,
+ Loading,
RouteDefinition,
useExchangeApiContext,
useTranslationContext,
@@ -36,6 +43,7 @@ import { usePreferences } from "../hooks/preferences.js";
import { useServerStatistics } from "../hooks/server-info.js";
import { AmlEventsName } from "./decision/aml-events.js";
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
+import { Officer } from "./Officer.js";
export function Dashboard({
routeToDownloadStats,
@@ -85,11 +93,86 @@ function EventMetrics({
const resp = useServerStatistics(dialect, params.current, params.previous);
- if (!resp) return <Fragment />;
+ if (!resp) {
+ return <Loading />;
+ }
if (resp instanceof TalerError) {
return <ErrorLoadingWithDebug error={resp} />;
}
+ // the request test failed?
+ const failReponse = resp.type === "fail" ? resp : undefined;
+
+ // request test good, but did one of the event metric request failed?
+ const oneOftheRequestFailed =
+ resp.type === "ok"
+ ? resp.body.find(
+ (r) =>
+ r.current.type === "fail" ||
+ (r.previous && r.previous.type === "fail"),
+ )
+ : undefined;
+
+ // collect error on a single variable
+ const error = failReponse
+ ? failReponse
+ : oneOftheRequestFailed
+ ? oneOftheRequestFailed.current.type === "fail"
+ ? oneOftheRequestFailed.current
+ : (oneOftheRequestFailed.previous as TalerExchangeErrorsByMethod<"getAmlKycStatistics">)
+ : undefined;
+
+ //check
+ if (error) {
+ switch (error.case) {
+ case HttpStatusCode.Forbidden:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ This account signature is invalid, contact administrator or
+ create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.NotFound:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not known, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.Conflict:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ default:
+ assertUnreachable(error);
+ }
+ }
+ const result = resp as OperationOk<
+ {
+ key: AmlEventsName;
+ current: TalerExchangeResultByMethod<"getAmlKycStatistics">;
+ previous: TalerExchangeResultByMethod<"getAmlKycStatistics"> | undefined;
+ }[]
+ >;
+
return (
<div class="px-4 mt-4">
<div class="sm:flex sm:items-center mb-4">
@@ -117,9 +200,11 @@ function EventMetrics({
</div>
<dl class="mt-5 grid grid-cols-1 md:grid-cols-4 divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow-lg md:divide-x md:divide-y-0">
- {resp.body.map((ev) => {
+ {result.body.map((ev) => {
const label = labelForEvent(ev.key, i18n);
const desc = descriptionForEvent(ev.key, i18n);
+ if (ev.current.type === "fail") return undefined;
+ if (ev.previous?.type === "fail") return undefined;
return (
<div class="px-4 py-5 sm:p-6">
<dt class="text-base font-normal text-gray-900">
@@ -129,8 +214,8 @@ function EventMetrics({
)}
</dt>
<MetricValueNumber
- current={ev.current.counter}
- previous={ev.previous?.counter}
+ current={ev.current.body.counter}
+ previous={ev.previous?.body.counter}
/>
</div>
);
diff --git a/packages/aml-backoffice-ui/src/pages/MeasureList.tsx b/packages/aml-backoffice-ui/src/pages/MeasureList.tsx
@@ -47,21 +47,44 @@ export function MeasureList({ routeToNew }: { routeToNew: RouteDefinition }) {
if (measures.type === "fail") {
switch (measures.case) {
- // case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account signature is wrong, contact administrator or create
- a new one.
+ This account signature is invalid, contact administrator or
+ create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.NotFound:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not known, contact administrator
+ or create a new one.
+ </i18n.Translate>
+ </Attention>
+ <Officer />
+ </Fragment>
+ );
+ case HttpStatusCode.Conflict:
+ return (
+ <Fragment>
+ <Attention type="danger" title={i18n.str`Operation denied`}>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
</i18n.Translate>
</Attention>
<Officer />
</Fragment>
);
default:
- assertUnreachable(measures.case);
+ assertUnreachable(measures);
}
}
diff --git a/packages/aml-backoffice-ui/src/pages/Search.tsx b/packages/aml-backoffice-ui/src/pages/Search.tsx
@@ -53,6 +53,7 @@ import { useOfficer } from "../hooks/officer.js";
import { privatePages } from "../Routing.js";
import { Pagination, ToInvestigateIcon } from "./Cases.js";
import { HandleAccountNotReady } from "./HandleAccountNotReady.js";
+import { Officer } from "./Officer.js";
export function Search() {
const officer = useOfficer();
@@ -60,6 +61,10 @@ export function Search() {
const [paytoUri, setPayto] = useState<PaytoUri | undefined>(undefined);
+ if (officer.state !== "ready") {
+ return <HandleAccountNotReady officer={officer} />;
+ }
+
const design: FormDesign = {
type: "single-column",
fields: paytoTypeField(i18n),
@@ -70,10 +75,6 @@ export function Search() {
// createFormValidator(i18n),
);
- if (officer.state !== "ready") {
- return <HandleAccountNotReady officer={officer} />;
- }
-
return (
<div>
<h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
@@ -130,42 +131,44 @@ function ShowResult({ payto }: { payto: PaytoUri }): VNode {
}
if (history.type === "fail") {
switch (history.case) {
- case HttpStatusCode.Forbidden: {
+ case HttpStatusCode.Forbidden:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account signature is wrong, contact administrator or create
- a new one.
+ This account signature is invalid, contact administrator or
+ create a new one.
</i18n.Translate>
</Attention>
+ <Officer />
</Fragment>
);
- }
- case HttpStatusCode.Conflict: {
+ case HttpStatusCode.NotFound:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
<i18n.Translate>
- This account doesn't have access. Request account activation
- sending your public key.
+ The designated AML account is not known, contact administrator
+ or create a new one.
</i18n.Translate>
</Attention>
+ <Officer />
</Fragment>
);
- }
- case HttpStatusCode.NotFound: {
+ case HttpStatusCode.Conflict:
return (
<Fragment>
<Attention type="danger" title={i18n.str`Operation denied`}>
- <i18n.Translate>This account is not known.</i18n.Translate>
+ <i18n.Translate>
+ The designated AML account is not enabled, contact administrator
+ or create a new one.
+ </i18n.Translate>
</Attention>
+ <Officer />
</Fragment>
);
- }
- default: {
+ default:
assertUnreachable(history);
- }
}
}
@@ -610,6 +613,9 @@ function validateIBAN(
iban: string,
i18n: ReturnType<typeof useTranslationContext>["i18n"],
): TranslatedString | undefined {
+ if (!iban) {
+ return i18n.str`required`;
+ }
// Check total length
if (iban.length < 4)
return i18n.str`IBAN numbers usually have more that 4 digits`;