Files
Checkmate/client/src/Utils/FormatUtils.ts
T
gorkem-bwl 68c1f2e281 feat(client): show uptime percentages with two decimals
Requested by a user who found xx.xx% more trustworthy than xx.x% on their
status page. The presentational argument holds, but the stronger reason is
that one decimal was actively misleading.

Intl rounds to the displayed precision, so every value above 99.95% rendered
as "100.0%" — a claim of zero downtime for a period that contained real
outages. On a public status page that is the opposite of reassuring: the
number reads as flawless during a month with a visible incident.

One decimal also collapsed the range operators care about most. 99.9%,
99.95% and 99.99% are three very different commitments (8.7 hours, 4.4 hours
and 52 minutes of annual downtime) and the first rendered as 99.9% while the
other two were indistinguishable at 100.0%.

Everything flows through the shared formatter in FormatUtils, so this covers
both surfaces at once: the monitor list and diagnostics gauges in the web
app, and the uptime figure, chart cells and hover tooltip on status pages.
Infrastructure gauges are untouched — they format separately at zero
decimals, where CPU at "43.00%" would only add noise.

Server-side precision was already sufficient; the aggregation divides
without rounding, so no API change is needed.

Adds tests pinning the regression: 7 of the 10 fail against the previous
one-decimal formatter.

Note: this restores the client Vitest setup. PR #3844 introduced it, but the
config, scripts and logger tests were dropped when that branch merged into
develop, leaving the client with no test runner again.
2026-08-20 09:38:07 -07:00

50 lines
1.6 KiB
TypeScript

/**
* Intl.NumberFormat instance for percentage formatting.
* Reused across all formatting calls for performance.
*
* Two decimals rather than one: at a single decimal every value above 99.95%
* renders as "100.0%", so a period containing real downtime is reported as
* flawless. It also collapses 99.9 / 99.95 / 99.99 — three materially
* different SLA commitments — into the same figure.
*/
const percentageFormatter = new Intl.NumberFormat("en-US", {
style: "percent",
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
/**
* Formats a decimal value as a percentage string.
* @param value - Decimal value (e.g., 0.75 for 75%)
* @returns Formatted percentage string (e.g., "75.00%")
* @example
* formatPercentage(0.75) // "75.00%"
* formatPercentage(1) // "100.00%"
* formatPercentage(0.5432) // "54.32%"
* formatPercentage(0.9999) // "99.99%"
*/
export const formatPercentage = (value: number): string => {
if (typeof value !== "number" || Number.isNaN(value)) {
return "0.00%";
}
return percentageFormatter.format(value);
};
/**
* Formats a whole number percentage value as a percentage string.
* @param value - Whole number percentage (e.g., 75 for 75%)
* @returns Formatted percentage string (e.g., "75.00%")
* @example
* formatPercentageFromWhole(75) // "75.00%"
* formatPercentageFromWhole(100) // "100.00%"
* formatPercentageFromWhole(54.32) // "54.32%"
*/
export const formatPercentageFromWhole = (value: number): string => {
return formatPercentage(value / 100);
};
export const getPercentage = (value: number, total: number) => {
if (!value || !total) return 0;
return (value / total) * 100;
};