Skip to content

Commit de6b54a

Browse files
atrakhConvex, Inc.
authored andcommitted
dashboard: show better loading and error states for usage charts (#42960)
GitOrigin-RevId: 039e4430785f94fffdd6967882cd2942cbb6b352
1 parent 42682ad commit de6b54a

File tree

4 files changed

+742
-541
lines changed

4 files changed

+742
-541
lines changed

npm-packages/dashboard/src/components/billing/PlanSummary.tsx

Lines changed: 75 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import { Sheet } from "@ui/Sheet";
22
import { Tooltip } from "@ui/Tooltip";
33
import { Loading } from "@ui/Loading";
4+
import { Spinner } from "@ui/Spinner";
45
import { formatBytes, formatNumberCompact } from "@common/lib/format";
56
import { UsageSummary } from "hooks/usageMetrics";
67
import { ReactNode } from "react";
78
import { GetTokenInfoResponse, TeamEntitlementsResponse } from "generatedApi";
8-
import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
9+
import {
10+
QuestionMarkCircledIcon,
11+
CrossCircledIcon,
12+
} from "@radix-ui/react-icons";
913
import { cn } from "@ui/cn";
1014
import Link from "next/link";
1115
import { Donut } from "@ui/Donut";
@@ -17,13 +21,15 @@ export function PlanSummary({
1721
hasSubscription,
1822
showEntitlements,
1923
hasFilter,
24+
error,
2025
}: {
2126
chefTokenUsage?: GetTokenInfoResponse;
2227
teamSummary?: UsageSummary;
2328
entitlements?: TeamEntitlementsResponse;
2429
hasSubscription: boolean;
2530
showEntitlements: boolean;
2631
hasFilter: boolean;
32+
error?: any;
2733
}) {
2834
return (
2935
<PlanSummaryForTeam
@@ -33,6 +39,7 @@ export function PlanSummary({
3339
hasSubscription={hasSubscription}
3440
showEntitlements={showEntitlements}
3541
hasFilter={hasFilter}
42+
error={error}
3643
/>
3744
);
3845
}
@@ -138,6 +145,7 @@ export type PlanSummaryForTeamProps = {
138145
showEntitlements: boolean;
139146
hasSubscription: boolean;
140147
hasFilter: boolean;
148+
error?: any;
141149
};
142150

143151
export function PlanSummaryForTeam({
@@ -147,6 +155,7 @@ export function PlanSummaryForTeam({
147155
hasSubscription,
148156
showEntitlements,
149157
hasFilter,
158+
error,
150159
}: PlanSummaryForTeamProps) {
151160
return (
152161
<Sheet
@@ -192,42 +201,73 @@ export function PlanSummaryForTeam({
192201
</div>
193202
)}
194203
</div>
195-
{sections.map((section, index) => (
196-
<UsageSection
197-
key={index}
198-
metric={
199-
section.metric === "chefTokens"
200-
? chefTokenUsage
201-
? chefTokenUsage.centitokensUsed / 100
202-
: undefined
203-
: teamSummary
204-
? teamSummary[section.metric]
205-
: undefined
206-
}
207-
entitlement={
208-
section.metric === "chefTokens"
209-
? chefTokenUsage
210-
? chefTokenUsage.centitokensQuota / 100
211-
: undefined
212-
: entitlements
213-
? (entitlements[section.entitlement] ?? 0)
214-
: undefined
215-
}
216-
isNotSubjectToFilter={section.metric === "chefTokens" && hasFilter}
217-
hasSubscription={hasSubscription}
218-
metricName={section.metric}
219-
format={section.format}
220-
detail={section.detail}
221-
title={section.title}
222-
suffix={section.suffix}
223-
showEntitlements={showEntitlements}
224-
/>
225-
))}
204+
{error ? (
205+
<PlanSummaryError />
206+
) : !teamSummary ? (
207+
<PlanSummaryLoading />
208+
) : (
209+
sections.map((section, index) => (
210+
<UsageSection
211+
key={index}
212+
metric={
213+
section.metric === "chefTokens"
214+
? chefTokenUsage
215+
? chefTokenUsage.centitokensUsed / 100
216+
: undefined
217+
: teamSummary
218+
? teamSummary[section.metric]
219+
: undefined
220+
}
221+
entitlement={
222+
section.metric === "chefTokens"
223+
? chefTokenUsage
224+
? chefTokenUsage.centitokensQuota / 100
225+
: undefined
226+
: entitlements
227+
? (entitlements[section.entitlement] ?? 0)
228+
: undefined
229+
}
230+
isNotSubjectToFilter={
231+
section.metric === "chefTokens" && hasFilter
232+
}
233+
hasSubscription={hasSubscription}
234+
metricName={section.metric}
235+
format={section.format}
236+
detail={section.detail}
237+
title={section.title}
238+
suffix={section.suffix}
239+
showEntitlements={showEntitlements}
240+
/>
241+
))
242+
)}
226243
</div>
227244
</Sheet>
228245
);
229246
}
230247

248+
function PlanSummaryError() {
249+
return (
250+
<div className="flex h-56 flex-col items-center justify-center p-4 text-center">
251+
<CrossCircledIcon className="h-6 w-6 text-content-error" />
252+
<h5 className="mt-2">Error fetching Usage summary data</h5>
253+
<p className="mt-1 text-sm text-content-secondary">
254+
An error occurred while fetching usage summary data. Please try again
255+
later.
256+
</p>
257+
</div>
258+
);
259+
}
260+
261+
function PlanSummaryLoading() {
262+
return (
263+
<div className="flex h-56 items-center justify-center p-4">
264+
<div className="flex items-center justify-center">
265+
<Spinner className="size-12" />
266+
</div>
267+
</div>
268+
);
269+
}
270+
231271
export function UsageOverview(props: {
232272
metric?: number;
233273
entitlement?: number;
@@ -267,17 +307,16 @@ function UsageAmount({
267307
<>
268308
<div className="flex items-center gap-2">
269309
{showEntitlements &&
270-
(metric !== undefined && entitlement !== undefined ? (
310+
metric !== undefined &&
311+
entitlement !== undefined && (
271312
<Tooltip
272313
side="bottom"
273314
tip={`Your team has used ${Math.floor(100 * (metric / entitlement))}% of the included amount${title ? ` of ${title}` : ``}.`}
274315
className="flex animate-fadeInFromLoading items-center"
275316
>
276317
<Donut current={metric} max={entitlement} />
277318
</Tooltip>
278-
) : (
279-
<Loading className="h-6 w-6" />
280-
))}
319+
)}
281320
{title && <SectionLabel detail={detail}>{title}</SectionLabel>}
282321
</div>
283322
{metric === undefined || entitlement === undefined ? (

0 commit comments

Comments
 (0)