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
Expand Up @@ -585,21 +585,28 @@ export function parseQuotaData(provider, data) {
case "qoder":
// Qoder ships a `user` quota and (optionally) an `organization`
// quota, both with same shape: {total, used, remaining, unit, resetAt}.
// Skip an organization bucket when its total is 0 — most personal
// Qoder accounts won't have one and rendering "0/0" is misleading.
/**
* Hide only all-zero organization placeholders; a zero reported total
* can still carry meaningful usage and is inferred from used + remaining.
*/
// Don't forward Qoder's `remaining` field: it's an absolute credit
// count, but getRemainingPercentage / QuotaTable interpret
// `remaining` as a 0-100 percentage and would render 348 credits
// as "348%". The percentage is computed from used/total instead.
if (data.quotas) {
Object.entries(data.quotas).forEach(([quotaType, quota]) => {
if (quotaType === "organization" && (!quota || (Number(quota.total) || 0) === 0)) {
if (
quotaType === "organization"
&& (!quota || [quota.total, quota.used, quota.remaining]
.every((value) => (Number(value) || 0) === 0))
) {
return;
}
normalizedQuotas.push({
name: quotaType === "user" ? "Personal" : quotaType === "organization" ? "Organization" : quotaType,
used: quota.used || 0,
total: quota.total || 0,
total: Math.max(0, Number(quota.total) || 0)
|| ((Number(quota.used) || 0) + (Number(quota.remaining) || 0)),
unit: quota.unit,
resetAt: quota.resetAt || null,
});
Expand Down
57 changes: 57 additions & 0 deletions tests/unit/qoder-quota.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";

import { parseQuotaData } from "../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js";

describe("Qoder quota normalization", () => {
it("shows zero-total organization usage with an inferred total", () => {
expect(parseQuotaData("qoder", {
quotas: {
organization: {
total: 0,
used: 3804,
remaining: 6196,
unit: "credits",
resetAt: "2026-07-31T16:00:00.000Z",
},
},
})).toEqual([{
name: "Organization",
used: 3804,
total: 10000,
unit: "credits",
resetAt: "2026-07-31T16:00:00.000Z",
}]);
});

it("keeps an all-zero personal quota", () => {
expect(parseQuotaData("qoder", {
quotas: {
user: {
total: 0,
used: 0,
remaining: 0,
unit: "credits",
},
},
})).toEqual([{
name: "Personal",
used: 0,
total: 0,
unit: "credits",
resetAt: null,
}]);
});

it("skips an all-zero organization placeholder", () => {
expect(parseQuotaData("qoder", {
quotas: {
organization: {
total: 0,
used: 0,
remaining: 0,
unit: "credits",
},
},
})).toEqual([]);
});
});
Loading