Skip to content

Commit 5b35a62

Browse files
committed
Update RPC usage charts to handle string values and improve visualization (#6766)
# Update RPC Usage Dashboard with Enhanced Visualization This PR updates the RPC usage dashboard to improve data visualization and fix type issues: - Changed numeric types in API response to strings for better precision - Added peak RPS data to the rate graph for more comprehensive monitoring - Implemented a "maxLine" feature to replace the previous maxLimit approach for clearer rate limit visualization - Added support for both stacked and individual chart variants - Fixed date formatting by properly handling UTC timezone with 'Z' suffix - Updated cache key to "rpc-usage-last-24-hours:v2" to ensure fresh data after changes - Removed unnecessary CardFooter component and simplified chart rendering logic - Enhanced tooltip display for better readability These changes provide users with more accurate and detailed information about their RPC usage patterns.
1 parent 03e2385 commit 5b35a62

File tree

6 files changed

+86
-87
lines changed

6 files changed

+86
-87
lines changed

apps/dashboard/src/@/api/usage/rpc.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,20 @@ export const fetchRPCUsage = unstable_cache(
5656
type Last24HoursRPCUsageApiResponse = {
5757
peakRate: {
5858
date: string;
59-
peakRPS: number;
59+
peakRPS: string;
6060
};
6161
averageRate: {
6262
date: string;
63-
averageRate: number;
64-
includedCount: number;
65-
rateLimitedCount: number;
66-
overageCount: number;
63+
averageRate: string;
64+
peakRPS: string;
65+
includedCount: string;
66+
rateLimitedCount: string;
67+
overageCount: string;
6768
}[];
6869
totalCounts: {
69-
includedCount: number;
70-
rateLimitedCount: number;
71-
overageCount: number;
70+
includedCount: string;
71+
rateLimitedCount: string;
72+
overageCount: string;
7273
};
7374
};
7475

@@ -106,7 +107,7 @@ export const getLast24HoursRPCUsage = unstable_cache(
106107
data: resData.data as Last24HoursRPCUsageApiResponse,
107108
};
108109
},
109-
["rpc-usage-last-24-hours"],
110+
["rpc-usage-last-24-hours:v2"],
110111
{
111112
revalidate: 60, // 1 minute
112113
},

apps/dashboard/src/@/components/blocks/charts/area-chart.tsx

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
Card,
55
CardContent,
66
CardDescription,
7-
CardFooter,
87
CardHeader,
98
CardTitle,
109
} from "@/components/ui/card";
@@ -31,18 +30,19 @@ type ThirdwebAreaChartProps<TConfig extends ChartConfig> = {
3130
description?: string;
3231
titleClassName?: string;
3332
};
34-
footer?: React.ReactNode;
3533
customHeader?: React.ReactNode;
3634
// chart config
3735
config: TConfig;
3836
data: Array<Record<keyof TConfig, number> & { time: number | string | Date }>;
3937
showLegend?: boolean;
40-
maxLimit?: number;
38+
4139
yAxis?: boolean;
4240
xAxis?: {
4341
sameDay?: boolean;
4442
};
4543

44+
variant?: "stacked" | "individual";
45+
4646
// chart className
4747
chartClassName?: string;
4848
isPending: boolean;
@@ -77,17 +77,7 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
7777
) : props.data.length === 0 ? (
7878
<EmptyChartState />
7979
) : (
80-
<AreaChart
81-
accessibilityLayer
82-
data={
83-
props.maxLimit
84-
? props.data.map((d) => ({
85-
...d,
86-
maxLimit: props.maxLimit,
87-
}))
88-
: props.data
89-
}
90-
>
80+
<AreaChart accessibilityLayer data={props.data}>
9181
<CartesianGrid vertical={false} />
9282
{props.yAxis && <YAxis tickLine={false} axisLine={false} />}
9383
<XAxis
@@ -136,26 +126,28 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
136126
</linearGradient>
137127
))}
138128
</defs>
139-
{configKeys.map((key) => (
140-
<Area
141-
key={key}
142-
dataKey={key}
143-
type="natural"
144-
fill={`url(#fill_${key})`}
145-
fillOpacity={0.4}
146-
stroke={`var(--color-${key})`}
147-
stackId="a"
148-
/>
149-
))}
150-
{props.maxLimit && (
151-
<Area
152-
type="monotone"
153-
dataKey="maxLimit"
154-
stroke="#ef4444"
155-
strokeWidth={2}
156-
strokeDasharray="5 5"
157-
fill="none"
158-
/>
129+
{configKeys.map((key) =>
130+
key === "maxLine" ? (
131+
<Area
132+
key={key}
133+
type="monotone"
134+
dataKey="maxLine"
135+
stroke="#ef4444"
136+
strokeWidth={2}
137+
strokeDasharray="5 5"
138+
fill="none"
139+
/>
140+
) : (
141+
<Area
142+
key={key}
143+
dataKey={key}
144+
type="natural"
145+
fill={`url(#fill_${key})`}
146+
fillOpacity={0.4}
147+
stroke={`var(--color-${key})`}
148+
stackId={props.variant !== "stacked" ? undefined : "a"}
149+
/>
150+
),
159151
)}
160152

161153
{props.showLegend && (
@@ -167,9 +159,6 @@ export function ThirdwebAreaChart<TConfig extends ChartConfig>(
167159
)}
168160
</ChartContainer>
169161
</CardContent>
170-
{props.footer && (
171-
<CardFooter className="w-full">{props.footer}</CardFooter>
172-
)}
173162
</Card>
174163
);
175164
}

apps/dashboard/src/@/components/ui/chart.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,7 @@ const ChartTooltipContent = React.forwardRef<
243243
<div className="grid gap-1.5">
244244
{nestLabel ? tooltipLabel : null}
245245
<span className="text-muted-foreground">
246-
{item.name === "maxLimit"
247-
? "Upper Limit"
248-
: itemConfig?.label || item.name}
246+
{itemConfig?.label || item.name}
249247
</span>
250248
</div>
251249
{item.value !== undefined && (

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/count-graph.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@ export function CountGraph(props: {
88
currentRateLimit: number;
99
data: {
1010
date: string;
11-
includedCount: number;
12-
overageCount: number;
13-
rateLimitedCount: number;
11+
includedCount: string;
12+
overageCount: string;
13+
rateLimitedCount: string;
1414
}[];
1515
}) {
16-
const hasAnyRateLimited = props.data.some((v) => v.rateLimitedCount > 0);
16+
const hasAnyRateLimited = props.data.some(
17+
(v) => Number(v.rateLimitedCount) > 0,
18+
);
1719
return (
1820
<ThirdwebAreaChart
1921
chartClassName="aspect-[1.5] lg:aspect-[4]"
2022
header={{
2123
title: "Requests Over Time",
22-
description: "Requests over the last 24 hours. All times in UTC.",
24+
description: "Requests over the last 24 hours.",
2325
}}
2426
config={
2527
hasAnyRateLimited
@@ -47,13 +49,13 @@ export function CountGraph(props: {
4749
}}
4850
hideLabel={false}
4951
toolTipLabelFormatter={(label) => {
50-
return formatDate(new Date(label), "MMM dd, HH:mm");
52+
return formatDate(label, "MMM dd, HH:mm");
5153
}}
5254
// @ts-expect-error - sending MORE data than expected is ok
5355
data={props.data
5456
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
5557
.map((v) => ({
56-
time: v.date,
58+
time: `${v.date}Z`,
5759
includedCount: Number(v.includedCount) + Number(v.overageCount),
5860
rateLimitedCount: Number(v.rateLimitedCount),
5961
}))}

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/rate-graph.tsx

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,54 +2,63 @@
22

33
import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart";
44
import { formatDate } from "date-fns";
5-
import { InfoIcon } from "lucide-react";
65

76
export function RateGraph(props: {
87
peakPercentage: number;
98
currentRateLimit: number;
10-
data: { date: string; averageRate: number }[];
9+
data: { date: string; averageRate: string; peakRPS: string }[];
1110
}) {
1211
return (
1312
<ThirdwebAreaChart
1413
chartClassName="aspect-[1.5] lg:aspect-[4]"
1514
header={{
1615
title: "Request Rate Over Time",
17-
description: "Request rate over the last 24 hours. All times in UTC.",
16+
description: "Request rate over the last 24 hours.",
1817
}}
19-
// only show the footer if the peak usage is greater than 80%
20-
footer={
21-
props.peakPercentage > 80 ? (
22-
<div className="flex items-center justify-center gap-2">
23-
<InfoIcon className="h-4 w-4 text-muted-foreground" />
24-
<p className="text-muted-foreground text-xs">
25-
The red dashed line represents your current plan rate limit (
26-
{props.currentRateLimit} RPS)
27-
</p>
28-
</div>
29-
) : undefined
18+
config={
19+
props.peakPercentage > 80
20+
? {
21+
averageRate: {
22+
label: "Average RPS",
23+
color: "hsl(var(--chart-1))",
24+
},
25+
peakRPS: {
26+
label: "Peak RPS",
27+
color: "hsl(var(--chart-2))",
28+
},
29+
maxLine: {
30+
label: "Plan Rate Limit",
31+
},
32+
}
33+
: {
34+
averageRate: {
35+
label: "Average RPS",
36+
color: "hsl(var(--chart-1))",
37+
},
38+
peakRPS: {
39+
label: "Peak RPS",
40+
color: "hsl(var(--chart-2))",
41+
},
42+
}
3043
}
31-
config={{
32-
averageRate: {
33-
label: "Average RPS",
34-
color: "hsl(var(--chart-1))",
35-
},
36-
}}
44+
// @ts-expect-error - maxLine is always sent but not always rendered, this is OK
3745
data={props.data
3846
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
3947
.map((v) => ({
40-
time: v.date,
41-
averageRate: Number(v.averageRate.toFixed(2)),
48+
time: `${v.date}Z`,
49+
averageRate: Number(Number(v.averageRate).toFixed(2)),
50+
peakRPS: Number(v.peakRPS),
51+
maxLine: props.currentRateLimit,
4252
}))}
4353
yAxis
4454
xAxis={{
4555
sameDay: true,
4656
}}
57+
showLegend
4758
hideLabel={false}
4859
toolTipLabelFormatter={(label) => {
49-
return formatDate(new Date(label), "MMM dd, HH:mm");
60+
return formatDate(label, "MMM dd, HH:mm");
5061
}}
51-
// only show the upper limit if the peak usage is greater than 80%
52-
maxLimit={props.peakPercentage > 80 ? props.currentRateLimit : undefined}
5362
isPending={false}
5463
/>
5564
);

apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/page.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default async function RPCUsage(props: {
4848
const { peakRate, totalCounts, averageRate } = apiData.data;
4949

5050
// Calculate percentage of limit for the peak
51-
const peakPercentage = (peakRate.peakRPS / currentRateLimit) * 100;
51+
const peakPercentage = (Number(peakRate.peakRPS) / currentRateLimit) * 100;
5252

5353
// Determine status based on peak percentage
5454
const getStatusColor = (percentage: number) => {
@@ -98,7 +98,7 @@ export default async function RPCUsage(props: {
9898
<CardContent>
9999
<div className="flex items-center justify-between">
100100
<div className="font-bold text-2xl">
101-
{peakRate.peakRPS.toFixed(1)} RPS
101+
{Number(peakRate.peakRPS)} RPS
102102
</div>
103103
<div className="flex items-center gap-2">
104104
<div
@@ -114,7 +114,7 @@ export default async function RPCUsage(props: {
114114
<p className="mt-1 text-muted-foreground text-xs">
115115
<ClockIcon className="mr-1 inline h-3 w-3" />
116116
{peakRate.date
117-
? format(new Date(peakRate.date), "MMM d, HH:mm")
117+
? format(new Date(`${peakRate.date}Z`), "MMM d, HH:mm")
118118
: "No Requests in last 24 hours"}
119119
</p>
120120
</CardContent>
@@ -177,9 +177,9 @@ export default async function RPCUsage(props: {
177177
<AlertTriangleIcon className="h-4 w-4" />
178178
<AlertTitle>Rate Limit Exceeded</AlertTitle>
179179
<AlertDescription>
180-
Your peak usage of {peakRate.peakRPS.toFixed(1)} RPS has exceeded
181-
your plan limit of {currentRateLimit} RPS. Consider upgrading your
182-
plan to avoid rate limiting.
180+
Your peak usage of {Number(peakRate.peakRPS)} RPS has exceeded your
181+
plan limit of {currentRateLimit} RPS. Consider upgrading your plan
182+
to avoid rate limiting.
183183
</AlertDescription>
184184
</Alert>
185185
)}

0 commit comments

Comments
 (0)