Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CopyableId } from "@/components/copyableId";
import { Label } from "@/components/ui/label";
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { resetDurationLabels } from "@/lib/constants/governance";
import { fiscalQuarterNote, resetDurationLabels } from "@/lib/constants/governance";
import { Customer } from "@/lib/types/governance";
import { cn } from "@/lib/utils";
import { formatCompactNumber } from "@/lib/utils/numbers";
Expand Down Expand Up @@ -32,15 +32,28 @@ function DetailCard({ title, children, contentClassName }: { title: string; chil
);
}

function BudgetLineBar({ current, max, resetDuration }: { current: number; max: number; resetDuration?: string }) {
function BudgetLineBar({
current,
max,
resetDuration,
resetConfig,
}: {
current: number;
max: number;
resetDuration?: string;
resetConfig?: { quarter_start_month?: number };
}) {
const pct = max > 0 ? Math.min((current / max) * 100, 100) : 0;
const isOver80 = pct >= 80;
const isOver100 = pct >= 100;
return (
<div className="space-y-1.5">
<div className="flex items-center justify-between text-xs">
<span className="font-medium" />
<span className="text-muted-foreground">{formatResetDuration(resetDuration)}</span>
<span className="text-muted-foreground">
{formatResetDuration(resetDuration)}
{fiscalQuarterNote(resetDuration, resetConfig)}
</span>
</div>
<div className="bg-muted h-1.5 w-full overflow-hidden rounded-full">
<div
Expand Down Expand Up @@ -124,7 +137,13 @@ export function CustomerDetailSheet({ customer, open, onOpenChange }: Props) {
{[...budgets]
.sort((a, b) => (b.max_limit || 0) - (a.max_limit || 0))
.map((b) => (
<BudgetLineBar key={b.id} current={b.current_usage} max={b.max_limit} resetDuration={b.reset_duration} />
<BudgetLineBar
key={b.id}
current={b.current_usage}
max={b.max_limit}
resetDuration={b.reset_duration}
resetConfig={b.reset_config}
/>
))}
</div>
) : (
Expand Down
7 changes: 4 additions & 3 deletions ui/app/workspace/governance/views/customerSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const createInitialState = (customer?: Customer | null): Omit<CustomerFormData,
id: b.id,
max_limit: b.max_limit,
reset_duration: b.reset_duration,
reset_config: b.reset_config,
})),
tokenMaxLimit: customer?.rate_limit?.token_max_limit ?? undefined,
tokenResetDuration: customer?.rate_limit?.token_reset_duration || "1h",
Expand Down Expand Up @@ -196,9 +197,9 @@ export default function CustomerSheet({ open, onOpenChange, customer, onSuccess
// spend becomes a meaningful choice; creating one has no usage to reset.
const budgetsChanged = () => {
if (!isEditing || !customer) return false;
const signature = (rows: { max_limit?: number | null; reset_duration?: string }[]) =>
const signature = (rows: { max_limit?: number | null; reset_duration?: string; reset_config?: { quarter_start_month?: number } }[]) =>
[...rows]
.map((r) => `${r.max_limit ?? ""}:${r.reset_duration ?? ""}`)
.map((r) => `${r.max_limit ?? ""}:${r.reset_duration ?? ""}:${r.reset_config?.quarter_start_month ?? ""}`)
.sort()
.join("|");
const next = formData.budgets.filter((b) => b.max_limit !== undefined && b.max_limit !== null);
Expand All @@ -223,7 +224,7 @@ export default function CustomerSheet({ open, onOpenChange, customer, onSuccess
const saveCustomer = async (resetBudgetUsage: boolean) => {
const budgetRequests: CreateBudgetRequest[] = formData.budgets
.filter((b) => b.max_limit !== undefined && b.max_limit !== null)
.map((b) => ({ id: b.id, max_limit: b.max_limit!, reset_duration: b.reset_duration }));
.map((b) => ({ id: b.id, max_limit: b.max_limit!, reset_duration: b.reset_duration, reset_config: b.reset_config }));

try {
if (isEditing && customer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DottedSeparator } from "@/components/ui/separator";
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useSheetNavigation } from "@/hooks/useSheetNavigation";
import { supportsCalendarAlignment } from "@/lib/constants/governance";
import { fiscalQuarterNote, supportsCalendarAlignment } from "@/lib/constants/governance";
import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons";
import { ProviderLabels, ProviderName } from "@/lib/constants/logs";
import { useRemoveVirtualKeyBudgetOverrideMutation, useSetVirtualKeyBudgetOverrideMutation } from "@/lib/store/apis/governanceApi";
Expand Down Expand Up @@ -351,6 +351,7 @@ export default function VirtualKeyDetailSheet({
<span>
Resets {parseResetPeriod(b.reset_duration)}
{virtualKey.calendar_aligned && supportsCalendarAlignment(b.reset_duration) && " (calendar)"}
{fiscalQuarterNote(b.reset_duration, b.reset_config)}
</span>
{b.last_reset ? (
<span>
Expand Down Expand Up @@ -456,6 +457,7 @@ export default function VirtualKeyDetailSheet({
<span>
Resets {parseResetPeriod(b.reset_duration)}
{virtualKey.calendar_aligned && supportsCalendarAlignment(b.reset_duration) && " (calendar)"}
{fiscalQuarterNote(b.reset_duration, b.reset_config)}
</span>
{b.last_reset ? (
<span>Last reset {formatDistanceToNow(new Date(b.last_reset), { addSuffix: true })}</span>
Expand Down Expand Up @@ -601,6 +603,7 @@ export default function VirtualKeyDetailSheet({
<span>
Resets {parseResetPeriod(b.reset_duration)}
{virtualKey.calendar_aligned && supportsCalendarAlignment(b.reset_duration) && " (calendar)"}
{fiscalQuarterNote(b.reset_duration, b.reset_config)}
</span>
{b.last_reset ? (
<span>
Expand Down
12 changes: 9 additions & 3 deletions ui/components/budgetDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Progress } from "@/components/ui/progress";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { resetDurationLabels, supportsCalendarAlignment } from "@/lib/constants/governance";
import { fiscalQuarterNote, resetDurationLabels, supportsCalendarAlignment } from "@/lib/constants/governance";
import { Budget } from "@/lib/types/governance";
import { cn } from "@/lib/utils";
import { formatCurrency, getEffectiveBudgetLimit, hasActiveBudgetOverride } from "@/lib/utils/governance";
Expand Down Expand Up @@ -45,7 +45,10 @@ export function BudgetDisplay({ budgets, calendarAligned }: BudgetDisplayProps)
{formatCurrency(effectiveMaxLimit)}
{hasOverride ? <span className="text-muted-foreground ml-1 text-[10px]">override</span> : null}
</span>
<span className="text-muted-foreground text-xs">{formatResetDuration(b.reset_duration, calendarAligned)}</span>
<span className="text-muted-foreground text-xs">
{formatResetDuration(b.reset_duration, calendarAligned)}
{fiscalQuarterNote(b.reset_duration, b.reset_config)}
</span>
</div>
<Progress value={pct} className={cn("bg-muted/70 dark:bg-muted/30 h-1.5", barClass)} />
</div>
Expand All @@ -60,7 +63,10 @@ export function BudgetDisplay({ budgets, calendarAligned }: BudgetDisplayProps)
</p>
) : null}
{b.reset_duration ? (
<p className="text-primary-foreground/80 text-xs">Resets {formatResetDuration(b.reset_duration, calendarAligned)}</p>
<p className="text-primary-foreground/80 text-xs">
Resets {formatResetDuration(b.reset_duration, calendarAligned)}
{fiscalQuarterNote(b.reset_duration, b.reset_config)}
</p>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
) : null}
</TooltipContent>
</Tooltip>
Expand Down
10 changes: 9 additions & 1 deletion ui/lib/budgetOutline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
// compact mono budget labels, allocation-bar math, and the neutral swatch ramp
// consistent across every screen that renders a provider config.

import { fiscalQuarterNote } from "./constants/governance";

export interface BudgetLineLike {
max_limit?: number | null;
reset_duration?: string;
// Fiscal quarter definition; only meaningful when reset_duration is "1Q".
reset_config?: { quarter_start_month?: number };
}

// Short, mono-friendly period suffixes (e.g. "$20/wk"). Falls back to the raw
Expand Down Expand Up @@ -49,10 +53,14 @@ export function money(value: number | null | undefined): string {
// Returns `fallback` when empty.
export function budgetLinesLabel(budgets: BudgetLineLike[] | undefined, fallback = "No budget"): string {
if (!budgets || budgets.length === 0) return fallback;
return [...budgets]
const label = [...budgets]
.sort((a, b) => periodRank(a.reset_duration) - periodRank(b.reset_duration))
.map((b) => `${money(b.max_limit)}/${shortPeriod(b.reset_duration)}`)
.join(" · ");
// A group has at most one quarterly line (duplicate periods are blocked), so a
// single trailing fiscal-start note is unambiguous. Empty for a January start.
const quarterly = budgets.find((b) => b.reset_duration?.endsWith("Q"));
return label + fiscalQuarterNote(quarterly?.reset_duration, quarterly?.reset_config);
}

// True when two or more budget lines share the same reset period. A budget
Expand Down
15 changes: 15 additions & 0 deletions ui/lib/constants/governance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ export function formatQuarterPreview(startMonth?: number): string {
.join(" · ");
}

/**
* Compact read-only note naming a quarterly budget's fiscal-year start, e.g.
* " · FY starts Apr". Returns "" for non-quarterly budgets and for a January /
* unset start (the default), so it only ever appears when it changes behaviour.
* Callers append it after the reset-period label (which already reads "Quarterly").
*/
export function fiscalQuarterNote(resetDuration?: string, resetConfig?: { quarter_start_month?: number } | null): string {
if (!resetDuration || !resetDuration.endsWith("Q")) return "";
const start = resetConfig?.quarter_start_month;
// Number.isInteger also rejects undefined/NaN; a fractional month like 2.5 would
// otherwise pass the range check and index MONTH_ABBREVIATIONS between slots.
if (start === undefined || !Number.isInteger(start) || start === 1 || start < 1 || start > 12) return "";
return ` · FY starts ${MONTH_ABBREVIATIONS[start - 1]}`;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Month choices for the fiscal quarter start select.
export const quarterStartMonthOptions = MONTH_ABBREVIATIONS.map((_, index) => ({
label: new Date(Date.UTC(2026, index, 1)).toLocaleString("en-US", { month: "long", timeZone: "UTC" }),
Expand Down
26 changes: 26 additions & 0 deletions ui/lib/utils/governance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { periodRank, shortPeriod } from "@/lib/budgetOutline";
import {
budgetResetDurationOptions,
fiscalQuarterNote,
formatQuarterPreview,
resetDurationLabels,
resetDurationOptions,
Expand Down Expand Up @@ -117,6 +118,31 @@ describe("fiscal quarter preview", () => {
expect(formatQuarterPreview(1.5)).toBe(formatQuarterPreview(1));
});
});
describe("fiscalQuarterNote", () => {
it("names the fiscal start only for a non-January quarterly budget", () => {
expect(fiscalQuarterNote("1Q", { quarter_start_month: 4 })).toBe(" · FY starts Apr");
expect(fiscalQuarterNote("1Q", { quarter_start_month: 10 })).toBe(" · FY starts Oct");
});

it("is empty for a January or unset start (the default)", () => {
expect(fiscalQuarterNote("1Q", { quarter_start_month: 1 })).toBe("");
expect(fiscalQuarterNote("1Q", {})).toBe("");
expect(fiscalQuarterNote("1Q", undefined)).toBe("");
});

it("is empty for a non-quarterly duration regardless of config", () => {
expect(fiscalQuarterNote("1M", { quarter_start_month: 4 })).toBe("");
expect(fiscalQuarterNote(undefined, { quarter_start_month: 4 })).toBe("");
});

it("is empty for an out-of-range or non-integer month", () => {
expect(fiscalQuarterNote("1Q", { quarter_start_month: 0 })).toBe("");
expect(fiscalQuarterNote("1Q", { quarter_start_month: 13 })).toBe("");
// A fractional month sits in range but indexes MONTH_ABBREVIATIONS between
// slots, which would render "FY starts undefined" without the integer guard.
expect(fiscalQuarterNote("1Q", { quarter_start_month: 2.5 })).toBe("");
});
});
describe("budgetSignature", () => {
const quarterly = (quarterStartMonth?: number) => [
{
Expand Down
Loading