mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(ui): let key budget notes wrap, and stop encoding a per-scope operator
Regenerates schema.d.ts for the end-user docstring change, which the types sync gate needs. Notes now carry two clauses, e.g. "alert only, never blocks; compared against recorded spend rather than the live counter", and the scope cell truncated them to one clipped line behind a native tooltip. They are the most load-bearing text in the table, so they wrap now and the column is wider to suit. The threshold helper always read `comparison` per row, but its doc comment and the fixtures around it asserted that team enforces ">", which is no longer true: reservation tightens team, tag and end_user to ">=", and disabling reservation relaxes them back. Nothing about the rendering changes, but the tests now pin that one scope can render either operator instead of teaching a wrong default.
This commit is contained in:
parent
2c608b05a6
commit
7e6546e8cc
4 changed files with 30 additions and 22 deletions
|
|
@ -30,9 +30,9 @@ const TEAM_MEMBER_AT_LIMIT: KeyBudgetEntry = {
|
|||
status: "exceeded",
|
||||
};
|
||||
|
||||
const TEAM_UNDER_LIMIT: KeyBudgetEntry = {
|
||||
const PROJECT_UNDER_LIMIT: KeyBudgetEntry = {
|
||||
...UNCONFIGURED_BUDGET,
|
||||
scope: "team",
|
||||
scope: "project",
|
||||
comparison: ">",
|
||||
max_budget: 300,
|
||||
spend: 120,
|
||||
|
|
@ -40,8 +40,11 @@ const TEAM_UNDER_LIMIT: KeyBudgetEntry = {
|
|||
status: "ok",
|
||||
};
|
||||
|
||||
const INCLUSIVE_AT_300: KeyBudgetEntry = {
|
||||
// team is ">=" while budget reservation is on and ">" once an operator disables it, so the same
|
||||
// scope must render either operator off the response rather than a value baked in per scope.
|
||||
const RESERVED_TEAM_AT_300: KeyBudgetEntry = {
|
||||
...UNCONFIGURED_BUDGET,
|
||||
scope: "team",
|
||||
comparison: ">=",
|
||||
max_budget: 300,
|
||||
spend: 300,
|
||||
|
|
@ -49,7 +52,7 @@ const INCLUSIVE_AT_300: KeyBudgetEntry = {
|
|||
status: "exceeded",
|
||||
};
|
||||
|
||||
const EXCLUSIVE_AT_300: KeyBudgetEntry = { ...INCLUSIVE_AT_300, comparison: ">", status: "ok" };
|
||||
const UNRESERVED_TEAM_AT_300: KeyBudgetEntry = { ...RESERVED_TEAM_AT_300, comparison: ">", status: "ok" };
|
||||
|
||||
const SOFT_OVER: KeyBudgetEntry = {
|
||||
...UNCONFIGURED_BUDGET,
|
||||
|
|
@ -73,12 +76,12 @@ describe("budgetThresholdRule", () => {
|
|||
});
|
||||
|
||||
it("marks an exclusive scope as blocking only above the limit", () => {
|
||||
expect(budgetThresholdRule(TEAM_UNDER_LIMIT)).toBe("Blocks at > $300.00");
|
||||
expect(budgetThresholdRule(PROJECT_UNDER_LIMIT)).toBe("Blocks at > $300.00");
|
||||
});
|
||||
|
||||
it("distinguishes two scopes sitting on identical numbers by their operator alone", () => {
|
||||
expect(budgetThresholdRule(INCLUSIVE_AT_300)).toBe("Blocks at ≥ $300.00");
|
||||
expect(budgetThresholdRule(EXCLUSIVE_AT_300)).toBe("Blocks at > $300.00");
|
||||
it("reads the operator off each response, so one scope can render either threshold", () => {
|
||||
expect(budgetThresholdRule(RESERVED_TEAM_AT_300)).toBe("Blocks at ≥ $300.00");
|
||||
expect(budgetThresholdRule(UNRESERVED_TEAM_AT_300)).toBe("Blocks at > $300.00");
|
||||
});
|
||||
|
||||
it("never promises a soft budget will block", () => {
|
||||
|
|
|
|||
|
|
@ -41,9 +41,10 @@ export const severityRank = (entry: KeyBudgetEntry): number =>
|
|||
const COMPARISON_GLYPH: Record<string, string> = { ">=": "≥", ">": ">" };
|
||||
|
||||
/**
|
||||
* Scopes disagree on whether hitting the limit exactly is over it: team_member enforces `>=`
|
||||
* so 50 of 50 is already denied, while team enforces `>` so 300 of 300 still passes. Two rows
|
||||
* can therefore show identical numbers and opposite statuses, so state each row's real threshold.
|
||||
* Scopes disagree on whether hitting the limit exactly is already over it, and a scope's operator
|
||||
* is not fixed: budget reservation tightens some scopes to `>=`, and disabling it relaxes them back
|
||||
* to `>`. So this reads `comparison` off each row rather than assuming a constant per scope. Two
|
||||
* rows can show identical numbers and opposite statuses, so state the threshold each one enforces.
|
||||
*/
|
||||
export const budgetThresholdRule = (entry: KeyBudgetEntry): string | null => {
|
||||
if (entry.max_budget == null) return null;
|
||||
|
|
@ -71,12 +72,12 @@ function ScopeCell({ entry }: { entry: KeyBudgetEntry }) {
|
|||
</span>
|
||||
}
|
||||
/>
|
||||
{entity && <span className="truncate font-mono text-xs text-muted-foreground">{entity}</span>}
|
||||
{entry.note && (
|
||||
<span className="truncate text-xs text-muted-foreground italic" title={entry.note}>
|
||||
{entry.note}
|
||||
{entity && (
|
||||
<span className="truncate font-mono text-xs text-muted-foreground" title={entity}>
|
||||
{entity}
|
||||
</span>
|
||||
)}
|
||||
{entry.note && <span className="text-xs text-muted-foreground italic">{entry.note}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -110,7 +111,7 @@ export const getKeyBudgetsTableColumns = (): ColumnDef<KeyBudgetEntry>[] => [
|
|||
id: "scope",
|
||||
meta: { title: "Scope" },
|
||||
header: "Scope",
|
||||
size: 240,
|
||||
size: 300,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => <ScopeCell entry={row.original} />,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ const TEAM_SOFT_OVER: KeyBudgetEntry = {
|
|||
remaining: -400,
|
||||
source: "budget_table:b-soft",
|
||||
status: "exceeded",
|
||||
note: "alert only",
|
||||
note: "alert only, never blocks; compared against recorded spend rather than the live counter",
|
||||
};
|
||||
|
||||
const TEAM_MEMBER_BLOCKING: KeyBudgetEntry = {
|
||||
|
|
@ -268,7 +268,9 @@ describe("KeyInfoView Budgets tab", () => {
|
|||
expect(within(softRow).getByText("Exceeded (alert only)")).toBeInTheDocument();
|
||||
expect(within(softRow).queryByTestId("key-budget-blocking")).not.toBeInTheDocument();
|
||||
expect(within(softRow).queryByText("Blocks requests")).not.toBeInTheDocument();
|
||||
expect(softRow).toHaveTextContent("alert only");
|
||||
expect(softRow).toHaveTextContent(
|
||||
"alert only, never blocks; compared against recorded spend rather than the live counter",
|
||||
);
|
||||
});
|
||||
|
||||
it("explains why two rows on identical numbers get opposite statuses", async () => {
|
||||
|
|
@ -285,8 +287,8 @@ describe("KeyInfoView Budgets tab", () => {
|
|||
};
|
||||
const exclusive: KeyBudgetEntry = {
|
||||
...UNCONFIGURED_BUDGET,
|
||||
scope: "team",
|
||||
entity_type: "team",
|
||||
scope: "project",
|
||||
entity_type: "project",
|
||||
entity_label: "Platform",
|
||||
comparison: ">",
|
||||
max_budget: 300,
|
||||
|
|
|
|||
6
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
6
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -6822,7 +6822,8 @@ export interface paths {
|
|||
* hash. Defaults to the key in the Authorization header when omitted (`GET /key/budgets`).
|
||||
* - end_user_id: str | None (query parameter) - Also report the budgets that would apply to this
|
||||
* end user. Omitted end users produce no `end_user` rows, because nothing binds an end user to
|
||||
* a key outside a request.
|
||||
* a key outside a request. Proxy admins only, since end users are a proxy-global namespace with
|
||||
* no key, team or organization scoping to check a caller against.
|
||||
*
|
||||
* Returns:
|
||||
* - key: str - The key that was looked up, echoed back as it was passed in
|
||||
|
|
@ -7512,7 +7513,8 @@ export interface paths {
|
|||
* hash. Defaults to the key in the Authorization header when omitted (`GET /key/budgets`).
|
||||
* - end_user_id: str | None (query parameter) - Also report the budgets that would apply to this
|
||||
* end user. Omitted end users produce no `end_user` rows, because nothing binds an end user to
|
||||
* a key outside a request.
|
||||
* a key outside a request. Proxy admins only, since end users are a proxy-global namespace with
|
||||
* no key, team or organization scoping to check a caller against.
|
||||
*
|
||||
* Returns:
|
||||
* - key: str - The key that was looked up, echoed back as it was passed in
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue