mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 08:24:46 +00:00
fix(usage): share SDK helpers and bound source cache
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from "vitest";
|
||||
import {
|
||||
balanceToneFromRemaining,
|
||||
toneFromUsedPct,
|
||||
usedPctOf,
|
||||
windowFromUsedPct,
|
||||
} from "./usage.js";
|
||||
|
||||
test("shared usage tones preserve the app threshold contract", () => {
|
||||
expect([null, 69.9, 70, 90, 90.1].map(toneFromUsedPct)).toEqual([
|
||||
"default",
|
||||
"ok",
|
||||
"warning",
|
||||
"warning",
|
||||
"danger",
|
||||
]);
|
||||
expect([null, 0, 0.01].map(balanceToneFromRemaining)).toEqual(["default", "danger", "ok"]);
|
||||
});
|
||||
|
||||
test("shared windows retain percentage, reset time, and optional headline", () => {
|
||||
expect(windowFromUsedPct({ id: "a", label: "A", utilizationPct: 30, headline: true })).toEqual({
|
||||
id: "a",
|
||||
label: "A",
|
||||
usedPct: 30,
|
||||
remainingPct: 70,
|
||||
resetsAt: null,
|
||||
headline: true,
|
||||
});
|
||||
expect(windowFromUsedPct({ id: "b", label: "B", utilizationPct: null })).toEqual({
|
||||
id: "b",
|
||||
label: "B",
|
||||
usedPct: null,
|
||||
remainingPct: null,
|
||||
resetsAt: null,
|
||||
});
|
||||
expect(usedPctOf(25, 100)).toBe(25);
|
||||
expect(usedPctOf(25, 0)).toBeNull();
|
||||
});
|
||||
@@ -49,3 +49,61 @@ export interface UsageSourceRegistration {
|
||||
fetch(input: unknown): Promise<UsageReport>;
|
||||
discover?(): Promise<JsonValue[]>;
|
||||
}
|
||||
|
||||
export function windowFromUsedPct(input: {
|
||||
id: string;
|
||||
label: string;
|
||||
utilizationPct: number | null | undefined;
|
||||
resetsAt?: string | null;
|
||||
tone?: UsageWindow["tone"];
|
||||
headline?: boolean;
|
||||
}): UsageWindow {
|
||||
const usedPct = typeof input.utilizationPct === "number" ? input.utilizationPct : null;
|
||||
const window: UsageWindow = {
|
||||
id: input.id,
|
||||
label: input.label,
|
||||
usedPct,
|
||||
remainingPct: usedPct === null ? null : Math.max(0, 100 - usedPct),
|
||||
resetsAt: input.resetsAt ?? null,
|
||||
};
|
||||
if (input.tone) window.tone = input.tone;
|
||||
if (input.headline) window.headline = true;
|
||||
return window;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tone scale for anything measured against a known limit, windows and balances alike.
|
||||
*
|
||||
* Thresholds match `deriveTone` in the app's provider-usage/tone.ts, which is what the
|
||||
* client falls back to when a window arrives without a tone. Healthy is "ok" rather than
|
||||
* "default" because that is what every provider setting a tone has always sent, and it is
|
||||
* what the bars render today below their thresholds.
|
||||
*/
|
||||
export function toneFromUsedPct(usedPct: number | null | undefined): UsageWindow["tone"] {
|
||||
if (typeof usedPct !== "number") return "default";
|
||||
if (usedPct > 90) return "danger";
|
||||
if (usedPct >= 70) return "warning";
|
||||
return "ok";
|
||||
}
|
||||
|
||||
/**
|
||||
* Tone for a balance with no known limit, where a percentage cannot be computed and the
|
||||
* only signal is whether anything is left. Prefer `toneFromUsedPct` when a limit exists:
|
||||
* this one stays "ok" until the balance is completely spent.
|
||||
*/
|
||||
export function balanceToneFromRemaining(
|
||||
remaining: number | null | undefined,
|
||||
): UsageBalance["tone"] {
|
||||
if (typeof remaining !== "number") return "default";
|
||||
if (remaining <= 0) return "danger";
|
||||
return "ok";
|
||||
}
|
||||
|
||||
/** Percentage of a limit consumed, or null when either side is unknown. */
|
||||
export function usedPctOf(
|
||||
used: number | null | undefined,
|
||||
limit: number | null | undefined,
|
||||
): number | null {
|
||||
if (typeof used !== "number" || typeof limit !== "number" || limit <= 0) return null;
|
||||
return (used / limit) * 100;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user