mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 17:33:51 +02:00
merge: preserve independent cost refresh warnings
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
693a33c588
3 files changed
+32
-5
No files matched your search
@@ -160,7 +160,7 @@ Company quota requests resolve the caller's accessible subscription AI connectio
|
||||
|
||||
Responses carry an opaque account identity and successful capture timestamp. The 30-second cache is scoped to company, user, connection, grant and credential revision; authorization and revision metadata are checked before reading cached results. Transient failures retain the last successful UI observation with its original timestamp. Rotation, revocation and explicit authentication rejection clear obsolete windows. Public messages never contain provider subprocess commands or raw diagnostics.
|
||||
|
||||
Both Costs page variants retain loaded reports and expanded rows during background refresh failures, with a small stale-data notice. Initial failures remain visible. An initial budget-load failure shows one safe notice above the tabs, including when Budgets is hidden; successful polling restores incident controls automatically. Company/date changes isolate cached reports. The run-count label is `runs: 0 api · 11 sub`: distinct recorded runs, not tasks or individual model requests. Input totals include cache reads, with the cached portion shown explicitly.
|
||||
Both Costs page variants retain loaded reports and expanded rows during background refresh failures, with a small stale-data notice. Initial failures remain visible. An initial budget-load failure shows one safe notice above the tabs, including when Budgets is hidden; successful polling restores incident controls automatically. Budget-load and stale-report notices remain independent when requests fail together. Company/date changes isolate cached reports. The run-count label is `runs: 0 api · 11 sub`: distinct recorded runs, not tasks or individual model requests. Input totals include cache reads, with the cached portion shown explicitly.
|
||||
|
||||
Receipt identity and supplemental charges: `heartbeat:` idempotency keys are reserved for internally generated run receipts. Additional API-reported charges may link to the same run with their own keys. They contribute to monthly and acknowledged-run lifetime totals, while receipt-integrity checks compare only the original provider receipt. Recovery isolates each budget scope so a deleted target or failed scope does not prevent recovery elsewhere.
|
||||
|
||||
|
||||
@@ -238,6 +238,30 @@ describe.each([
|
||||
expect(container.textContent).toContain("Bender");
|
||||
});
|
||||
|
||||
it.each(["summary", "financeEvents", "byProvider", "byBiller", "windowSpend"])("shows independent budget and stale-report notices when %s refresh fails", async (method) => {
|
||||
overview.mockRejectedValue(new Error("internal budget failure"));
|
||||
await render();
|
||||
if (method === "byProvider" || method === "windowSpend") await click("Providers");
|
||||
if (method === "byBiller") await click("Billers");
|
||||
const successfulReport = api[method].getMockImplementation()!;
|
||||
api[method].mockRejectedValue(new Error("internal report failure"));
|
||||
await act(async () => { await client.invalidateQueries(); });
|
||||
await settle();
|
||||
expect(container.textContent).toContain("Budget data could not be loaded");
|
||||
expect(container.textContent).toContain("Showing the last loaded data");
|
||||
expect(container.textContent).toContain("$402.88");
|
||||
expect(container.textContent).not.toContain("internal");
|
||||
overview.mockResolvedValue({ policies: [], activeIncidents: [], pendingApprovalCount: 0, pausedAgentCount: 0, pausedProjectCount: 0 });
|
||||
await act(async () => { await client.invalidateQueries(); });
|
||||
await settle();
|
||||
expect(container.textContent).not.toContain("Budget data could not be loaded");
|
||||
expect(container.textContent).toContain("Showing the last loaded data");
|
||||
api[method].mockImplementation(successfulReport);
|
||||
await act(async () => { await client.invalidateQueries(); });
|
||||
await settle();
|
||||
expect(container.textContent).not.toContain("Showing the last loaded data");
|
||||
});
|
||||
|
||||
it("retains loaded budget controls and finance totals on background errors", async () => {
|
||||
overview.mockResolvedValue({
|
||||
policies: [], activeIncidents: [{ id: "incident", scopeType: "agent", scopeName: "Codie", status: "open", thresholdType: "hard", amountObserved: 200, amountLimit: 1000 }],
|
||||
|
||||
@@ -670,11 +670,14 @@ export function Costs({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{((spendData && spendError) || (financeData && financeError) || budgetError || providerError || billerError || weekError || weekBillerError || windowError) ? (
|
||||
{budgetError && !budgetData ? (
|
||||
<p role="status" className="text-sm text-muted-foreground">
|
||||
{budgetError && !budgetData
|
||||
? "Budget data could not be loaded. Please try again shortly."
|
||||
: "Showing the last loaded data. Updates will resume automatically."}
|
||||
Budget data could not be loaded. Please try again shortly.
|
||||
</p>
|
||||
) : null}
|
||||
{((spendData && spendError) || (financeData && financeError) || (budgetData && budgetError) || providerError || billerError || weekError || weekBillerError || windowError) ? (
|
||||
<p role="status" className="text-sm text-muted-foreground">
|
||||
Showing the last loaded data. Updates will resume automatically.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
|
||||
Reference in new issue
Block a user