/* This file is part of GNU Taler (C) 2022-2024 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ import { AccessToken, AmountString, TalerCoreBankHttpClient, TalerCorebankApi, TalerError, } from "@gnu-taler/taler-util"; import { Attention, LocalNotificationBanner, useLocalNotification, useTranslationContext, } from "@gnu-taler/web-util/browser"; import { VNode, h } from "preact"; import { useState } from "preact/hooks"; import { useBankCoreApiContext } from "../../context/config.js"; import { useSessionState } from "../../hooks/session.js"; import { RouteDefinition } from "../../route.js"; import { getTimeframesForDate } from "./AdminHome.js"; interface Props { routeCancel: RouteDefinition; } type Options = { dayMetric: boolean; hourMetric: boolean; monthMetric: boolean; yearMetric: boolean; compareWithPrevious: boolean; endOnFirstFail: boolean; includeHeader: boolean; }; /** * Show histories of public accounts. */ export function DownloadStats({ routeCancel }: Props): VNode { const { i18n } = useTranslationContext(); const { state: credentials } = useSessionState(); const creds = credentials.status !== "loggedIn" || !credentials.isUserAdministrator ? undefined : credentials; const { bank: api } = useBankCoreApiContext(); const [options, setOptions] = useState({ compareWithPrevious: true, dayMetric: true, endOnFirstFail: false, hourMetric: true, includeHeader: true, monthMetric: true, yearMetric: true, }); const [lastStep, setLastStep] = useState<{ step: number; total: number }>(); const [downloaded, setDownloaded] = useState(); const referenceDates = [new Date()]; const [notification, , handleError] = useLocalNotification(); if (!creds) { return
only admin can download stats
; } return (

Download bank stats

{ e.preventDefault(); }} >
Include hour metric
Include day metric
Include month metric
Include year metric
Include table header
Add previous metric for compare
Fail on first error
Cancel
{!lastStep || lastStep.step === lastStep.total ? (
) : (
downloading...{" "} {Math.round((lastStep.step / lastStep.total) * 100)}
)} {!downloaded ? ( ); } async function fetchAllStatus( api: TalerCoreBankHttpClient, token: AccessToken, options: Options, references: Date[], progress: (current: number, total: number) => void, ): Promise { const allMetrics: TalerCorebankApi.MonitorTimeframeParam[] = []; if (options.hourMetric) { allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.hour); } if (options.dayMetric) { allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.day); } if (options.monthMetric) { allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.month); } if (options.yearMetric) { allMetrics.push(TalerCorebankApi.MonitorTimeframeParam.year); } /** * convert request into frames */ const allFrames = allMetrics.flatMap((timeframe) => references.map((reference) => ({ reference, timeframe, moment: getTimeframesForDate(reference, timeframe), })), ); const total = allFrames.length; /** * call API for info */ const allInfo = await allFrames.reduce( async (prev, frame, index) => { const accumulatedMap = await prev; progress(index, total); // await delay() const previous = options.compareWithPrevious ? await api.getMonitor(token, { timeframe: frame.timeframe, which: frame.moment.previous, }) : undefined; if (previous && previous.type === "fail" && options.endOnFirstFail) { throw TalerError.fromUncheckedDetail(previous.detail); } const current = await api.getMonitor(token, { timeframe: frame.timeframe, which: frame.moment.current, }); if (current.type === "fail" && options.endOnFirstFail) { throw TalerError.fromUncheckedDetail(current.detail); } const metricName = TalerCorebankApi.MonitorTimeframeParam[allMetrics[index]]; accumulatedMap[metricName] = { reference: frame.reference, current: current.type !== "ok" ? undefined : current.body, previous: !previous || previous.type !== "ok" ? undefined : previous.body, }; return accumulatedMap; }, Promise.resolve({} as Record), ); progress(total, total); /** * convert into table format * */ const table: Array = []; if (options.includeHeader) { table.push([ "date", "metric", "reference", "talerInCount", "talerInVolume", "talerOutCount", "talerOutVolume", "cashinCount", "cashinFiatVolume", "cashinRegionalVolume", "cashoutCount", "cashoutFiatVolume", "cashoutRegionalVolume", ]); } Object.entries(allInfo).forEach(([name, data]) => { if (data.current) { const row: TableRow = { date: data.reference.getTime(), metric: name, reference: "current", ...dataToRow(data.current), }; table.push(Object.values(row) as string[]); } if (data.previous) { const row: TableRow = { date: data.reference.getTime(), metric: name, reference: "previous", ...dataToRow(data.previous), }; table.push(Object.values(row) as string[]); } }); const csv = table.reduce((acc, row) => { return acc + row.join(",") + "\n"; }, ""); return csv; } type JustData = Omit, "date">, "reference">; function dataToRow(info: TalerCorebankApi.MonitorResponse): JustData { return { talerInCount: info.talerInCount, talerInVolume: info.talerInVolume, talerOutCount: info.talerOutCount, talerOutVolume: info.talerOutVolume, cashinCount: info.type === "no-conversions" ? undefined : info.cashinCount, cashinFiatVolume: info.type === "no-conversions" ? undefined : info.cashinFiatVolume, cashinRegionalVolume: info.type === "no-conversions" ? undefined : info.cashinRegionalVolume, cashoutCount: info.type === "no-conversions" ? undefined : info.cashoutCount, cashoutFiatVolume: info.type === "no-conversions" ? undefined : info.cashoutFiatVolume, cashoutRegionalVolume: info.type === "no-conversions" ? undefined : info.cashoutRegionalVolume, }; } type Data = { reference: Date; previous: TalerCorebankApi.MonitorResponse | undefined; current: TalerCorebankApi.MonitorResponse | undefined; }; type TableRow = { date: number; metric: string; reference: "current" | "previous"; cashinCount?: number; cashinRegionalVolume?: AmountString; cashinFiatVolume?: AmountString; cashoutCount?: number; cashoutRegionalVolume?: AmountString; cashoutFiatVolume?: AmountString; talerInCount: number; talerInVolume: AmountString; talerOutCount: number; talerOutVolume: AmountString; };