fix(FN-7786): show partial Command Center costs
Fusion-Task-Id: FN-7786
This commit is contained in:
7
.changeset/bright-cost-subtotals.md
Normal file
7
.changeset/bright-cost-subtotals.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Show partial estimated costs across all Command Center cost views when some model pricing is unavailable.
|
||||||
|
category: fix
|
||||||
|
dev: Priced subtotals use a trailing plus sign; entirely unpriced usage remains unavailable.
|
||||||
@@ -715,6 +715,18 @@ describe("CommandCenter shell", () => {
|
|||||||
expect(liveMetricValue("command-center-live-tokens")).toBe("1,234,567,890");
|
expect(liveMetricValue("command-center-live-tokens")).toBe("1,234,567,890");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows the priced subtotal in Overview when some usage has unknown pricing", async () => {
|
||||||
|
mockOverviewApi({
|
||||||
|
tokens: {
|
||||||
|
...tokenFixture(1_500),
|
||||||
|
cost: { usd: 911.39004125, unavailable: true, stale: false },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
render(<CommandCenter />);
|
||||||
|
|
||||||
|
expect(await screen.findByTestId("command-center-stat-tokens")).toHaveTextContent("$911.39+");
|
||||||
|
});
|
||||||
|
|
||||||
it("live-polls token totals for the Overview card, live strip, and model charts", async () => {
|
it("live-polls token totals for the Overview card, live strip, and model charts", async () => {
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
let resolvePoll: ((value: ReturnType<typeof tokenFixture>) => void) | null = null;
|
let resolvePoll: ((value: ReturnType<typeof tokenFixture>) => void) | null = null;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ type OrgChartDragState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function costSortValue(cost: CostResult): number {
|
function costSortValue(cost: CostResult): number {
|
||||||
return cost.unavailable || cost.usd === null ? -1 : cost.usd;
|
return cost.usd ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function agentLabel(agent: TeamAgentSummary, unknownLabel: string): string {
|
function agentLabel(agent: TeamAgentSummary, unknownLabel: string): string {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ The Tokens detail area is the full-fidelity by-model source of truth: every grou
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function costSortValue(cost: CostResult): number {
|
function costSortValue(cost: CostResult): number {
|
||||||
return cost.unavailable || cost.usd === null ? -1 : cost.usd;
|
return cost.usd ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function modelGroupIdentity(group: TokenGroupSummary): string {
|
function modelGroupIdentity(group: TokenGroupSummary): string {
|
||||||
@@ -287,16 +287,12 @@ export function TokensArea({ range, projectId }: { range: DateRange; projectId?:
|
|||||||
<td>{formatCount(g.cachedTokens)}</td>
|
<td>{formatCount(g.cachedTokens)}</td>
|
||||||
<td>{formatCount(g.totalTokens)}</td>
|
<td>{formatCount(g.totalTokens)}</td>
|
||||||
<td>
|
<td>
|
||||||
{g.cost.unavailable || g.cost.usd === null ? (
|
<span
|
||||||
<span
|
className={g.cost.usd === null ? "cc-unavailable" : undefined}
|
||||||
className="cc-unavailable"
|
title={g.cost.unavailable ? t("commandCenter.tokens.costUnavailable", "No pricing for some or all usage") : undefined}
|
||||||
title={t("commandCenter.tokens.costUnavailable", "No pricing for this model")}
|
>
|
||||||
>
|
{formatCost(g.cost.usd, g.cost.unavailable)}
|
||||||
—
|
</span>
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
formatCost(g.cost.usd, g.cost.unavailable)
|
|
||||||
)}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { WorkflowIcon } from "../../WorkflowIcon";
|
|||||||
type SortKey = "workflow" | "tokens" | "cost" | "filesChanged" | "tasksCompleted" | "tasksInProgress" | "tasksInReview";
|
type SortKey = "workflow" | "tokens" | "cost" | "filesChanged" | "tasksCompleted" | "tasksInProgress" | "tasksInReview";
|
||||||
|
|
||||||
function costSortValue(cost: CostResult): number {
|
function costSortValue(cost: CostResult): number {
|
||||||
return cost.unavailable || cost.usd === null ? -1 : cost.usd;
|
return cost.usd ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function workflowLabel(workflow: WorkflowSummary, unknownLabel: string): string {
|
function workflowLabel(workflow: WorkflowSummary, unknownLabel: string): string {
|
||||||
|
|||||||
@@ -180,6 +180,24 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("TokensArea provider model icons", () => {
|
describe("TokensArea provider model icons", () => {
|
||||||
|
it("renders a priced subtotal when the aggregate also contains unpriced usage", async () => {
|
||||||
|
apiMock.mockResolvedValue({
|
||||||
|
...tokenFixture(),
|
||||||
|
cost: { usd: 11.25, unavailable: true, stale: false },
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
...tokenFixture().groups[0],
|
||||||
|
cost: { usd: 3.5, unavailable: true, stale: false },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<TokensArea range={range7d} />);
|
||||||
|
|
||||||
|
expect(await screen.findByTestId("cc-tokens-cost")).toHaveTextContent("$11.25+");
|
||||||
|
expect(screen.getByTestId("cc-tokens-row-claude-sonnet-4-5")).toHaveTextContent("$3.50+");
|
||||||
|
});
|
||||||
|
|
||||||
it("renders current OpenAI Codex priced costs as dollars instead of the unavailable sentinel", async () => {
|
it("renders current OpenAI Codex priced costs as dollars instead of the unavailable sentinel", async () => {
|
||||||
const usage = { inputTokens: 1_000_000, outputTokens: 200_000, cachedTokens: 500_000, cacheWriteTokens: 100_000 };
|
const usage = { inputTokens: 1_000_000, outputTokens: 200_000, cachedTokens: 500_000, cacheWriteTokens: 100_000 };
|
||||||
const cost = costFor(usage, { provider: "openai-codex", model: "gpt-5.5" });
|
const cost = costFor(usage, { provider: "openai-codex", model: "gpt-5.5" });
|
||||||
|
|||||||
@@ -112,8 +112,7 @@ describe("WorkflowArea", () => {
|
|||||||
await screen.findByTestId("cc-area-workflows");
|
await screen.findByTestId("cc-area-workflows");
|
||||||
expect(mocks.api).toHaveBeenCalledWith("/command-center/workflows?from=2026-06-08", undefined);
|
expect(mocks.api).toHaveBeenCalledWith("/command-center/workflows?from=2026-06-08", undefined);
|
||||||
expect(screen.getByTestId("cc-workflows-total-tokens").textContent).toContain("1,650");
|
expect(screen.getByTestId("cc-workflows-total-tokens").textContent).toContain("1,650");
|
||||||
expect(screen.getByTestId("cc-workflows-total-cost").textContent).toContain("—");
|
expect(screen.getByTestId("cc-workflows-total-cost").textContent).toContain("$4.25+");
|
||||||
expect(screen.getByTestId("cc-workflows-total-cost").textContent).not.toContain("$0");
|
|
||||||
expect(screen.getByTestId("cc-workflows-tokens-chart").textContent).toContain("Coding");
|
expect(screen.getByTestId("cc-workflows-tokens-chart").textContent).toContain("Coding");
|
||||||
|
|
||||||
const table = screen.getByTestId("cc-workflows-table");
|
const table = screen.getByTestId("cc-workflows-table");
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ import { ActivityArea } from "../ActivityArea";
|
|||||||
import { EcosystemArea } from "../EcosystemArea";
|
import { EcosystemArea } from "../EcosystemArea";
|
||||||
import { useAnalyticsArea } from "../useAnalyticsArea";
|
import { useAnalyticsArea } from "../useAnalyticsArea";
|
||||||
import { ConfirmDialogProvider } from "../../../../hooks/useConfirm";
|
import { ConfirmDialogProvider } from "../../../../hooks/useConfirm";
|
||||||
import { rangeQuery } from "../areaShared";
|
import { formatCost, rangeQuery } from "../areaShared";
|
||||||
import { defaultPresets, rangeFromPreset } from "../../DateRangePicker";
|
import { defaultPresets, rangeFromPreset } from "../../DateRangePicker";
|
||||||
import {
|
import {
|
||||||
activityFixture,
|
activityFixture,
|
||||||
@@ -80,6 +80,15 @@ import {
|
|||||||
tokenFixture,
|
tokenFixture,
|
||||||
} from "./areas.test-harness";
|
} from "./areas.test-harness";
|
||||||
|
|
||||||
|
describe("Command Center cost formatting", () => {
|
||||||
|
it("shows priced subtotals when only part of the usage has known pricing", () => {
|
||||||
|
expect(formatCost(911.39004125, true)).toBe("$911.39+");
|
||||||
|
expect(formatCost(12.5, false)).toBe("$12.50");
|
||||||
|
expect(formatCost(0, false)).toBe("$0.00");
|
||||||
|
expect(formatCost(null, true)).toBe("—");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
apiMock.mockReset();
|
apiMock.mockReset();
|
||||||
backfillGithubSourceIssueClosedAtMock.mockReset();
|
backfillGithubSourceIssueClosedAtMock.mockReset();
|
||||||
@@ -1394,11 +1403,16 @@ describe("TeamArea", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("renders a large comma-grouped total unchanged in the team total tokens stat", async () => {
|
it("renders a large comma-grouped total unchanged in the team total tokens stat", async () => {
|
||||||
apiMock.mockResolvedValue(populatedTeamFixture(1_234_567_890));
|
const fixture = populatedTeamFixture(1_234_567_890);
|
||||||
|
apiMock.mockResolvedValue({
|
||||||
|
...fixture,
|
||||||
|
totals: { ...fixture.totals, cost: { usd: 4.25, unavailable: true, stale: false } },
|
||||||
|
});
|
||||||
render(<TeamArea range={range7d} />);
|
render(<TeamArea range={range7d} />);
|
||||||
|
|
||||||
await screen.findByTestId("cc-area-team");
|
await screen.findByTestId("cc-area-team");
|
||||||
expect(screen.getByTestId("cc-team-total-tokens").textContent).toContain("1,234,567,890");
|
expect(screen.getByTestId("cc-team-total-tokens").textContent).toContain("1,234,567,890");
|
||||||
|
expect(screen.getByTestId("cc-team-total-cost")).toHaveTextContent("$4.25+");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the team pie safe for single-item and non-finite data", async () => {
|
it("keeps the team pie safe for single-item and non-finite data", async () => {
|
||||||
|
|||||||
@@ -45,12 +45,18 @@ export function formatDurationMs(ms: number | null): string {
|
|||||||
return `${seconds}s`;
|
return `${seconds}s`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Format a USD cost result, returning the unavailable sentinel "—" when unknown. */
|
/**
|
||||||
|
* Format a USD cost result for every Command Center cost surface.
|
||||||
|
*
|
||||||
|
* FNXC:CommandCenterCost 2026-07-10-23:20:
|
||||||
|
* Cost aggregation deliberately preserves the priced subtotal when some usage has unknown pricing. Overview, Tokens, Team, and Workflows must display that subtotal with a trailing `+`; show `—` only when no usage can be priced, and keep an exact priced zero distinct from unavailable data.
|
||||||
|
*/
|
||||||
export function formatCost(usd: number | null, unavailable: boolean): string {
|
export function formatCost(usd: number | null, unavailable: boolean): string {
|
||||||
if (unavailable || usd === null || !Number.isFinite(usd)) {
|
if (usd === null || !Number.isFinite(usd)) {
|
||||||
return "—";
|
return "—";
|
||||||
}
|
}
|
||||||
return `$${usd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
const formatted = `$${usd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
||||||
|
return unavailable ? `${formatted}+` : formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** True when the picker's custom range is invalid (from after to). */
|
/** True when the picker's custom range is invalid (from after to). */
|
||||||
|
|||||||
Reference in New Issue
Block a user