From 5b35a6227b64dc7d9955e10eab57a3334f56a25f Mon Sep 17 00:00:00 2001 From: jnsdls Date: Fri, 18 Apr 2025 08:54:59 +0000 Subject: [PATCH] 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. --- apps/dashboard/src/@/api/usage/rpc.ts | 19 +++--- .../@/components/blocks/charts/area-chart.tsx | 63 ++++++++----------- apps/dashboard/src/@/components/ui/chart.tsx | 4 +- .../~/usage/rpc/components/count-graph.tsx | 16 ++--- .../~/usage/rpc/components/rate-graph.tsx | 59 +++++++++-------- .../[team_slug]/(team)/~/usage/rpc/page.tsx | 12 ++-- 6 files changed, 86 insertions(+), 87 deletions(-) diff --git a/apps/dashboard/src/@/api/usage/rpc.ts b/apps/dashboard/src/@/api/usage/rpc.ts index e999ceb8fd7..a3d29e54d67 100644 --- a/apps/dashboard/src/@/api/usage/rpc.ts +++ b/apps/dashboard/src/@/api/usage/rpc.ts @@ -56,19 +56,20 @@ export const fetchRPCUsage = unstable_cache( type Last24HoursRPCUsageApiResponse = { peakRate: { date: string; - peakRPS: number; + peakRPS: string; }; averageRate: { date: string; - averageRate: number; - includedCount: number; - rateLimitedCount: number; - overageCount: number; + averageRate: string; + peakRPS: string; + includedCount: string; + rateLimitedCount: string; + overageCount: string; }[]; totalCounts: { - includedCount: number; - rateLimitedCount: number; - overageCount: number; + includedCount: string; + rateLimitedCount: string; + overageCount: string; }; }; @@ -106,7 +107,7 @@ export const getLast24HoursRPCUsage = unstable_cache( data: resData.data as Last24HoursRPCUsageApiResponse, }; }, - ["rpc-usage-last-24-hours"], + ["rpc-usage-last-24-hours:v2"], { revalidate: 60, // 1 minute }, diff --git a/apps/dashboard/src/@/components/blocks/charts/area-chart.tsx b/apps/dashboard/src/@/components/blocks/charts/area-chart.tsx index a232b562042..a944c819886 100644 --- a/apps/dashboard/src/@/components/blocks/charts/area-chart.tsx +++ b/apps/dashboard/src/@/components/blocks/charts/area-chart.tsx @@ -4,7 +4,6 @@ import { Card, CardContent, CardDescription, - CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; @@ -31,18 +30,19 @@ type ThirdwebAreaChartProps = { description?: string; titleClassName?: string; }; - footer?: React.ReactNode; customHeader?: React.ReactNode; // chart config config: TConfig; data: Array & { time: number | string | Date }>; showLegend?: boolean; - maxLimit?: number; + yAxis?: boolean; xAxis?: { sameDay?: boolean; }; + variant?: "stacked" | "individual"; + // chart className chartClassName?: string; isPending: boolean; @@ -77,17 +77,7 @@ export function ThirdwebAreaChart( ) : props.data.length === 0 ? ( ) : ( - ({ - ...d, - maxLimit: props.maxLimit, - })) - : props.data - } - > + {props.yAxis && } ( ))} - {configKeys.map((key) => ( - - ))} - {props.maxLimit && ( - + {configKeys.map((key) => + key === "maxLine" ? ( + + ) : ( + + ), )} {props.showLegend && ( @@ -167,9 +159,6 @@ export function ThirdwebAreaChart( )} - {props.footer && ( - {props.footer} - )} ); } diff --git a/apps/dashboard/src/@/components/ui/chart.tsx b/apps/dashboard/src/@/components/ui/chart.tsx index 0a07dd7a0af..a452dbddc36 100644 --- a/apps/dashboard/src/@/components/ui/chart.tsx +++ b/apps/dashboard/src/@/components/ui/chart.tsx @@ -243,9 +243,7 @@ const ChartTooltipContent = React.forwardRef<
{nestLabel ? tooltipLabel : null} - {item.name === "maxLimit" - ? "Upper Limit" - : itemConfig?.label || item.name} + {itemConfig?.label || item.name}
{item.value !== undefined && ( diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/count-graph.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/count-graph.tsx index 896ff7b89c6..149edfc3cf8 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/count-graph.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/count-graph.tsx @@ -8,18 +8,20 @@ export function CountGraph(props: { currentRateLimit: number; data: { date: string; - includedCount: number; - overageCount: number; - rateLimitedCount: number; + includedCount: string; + overageCount: string; + rateLimitedCount: string; }[]; }) { - const hasAnyRateLimited = props.data.some((v) => v.rateLimitedCount > 0); + const hasAnyRateLimited = props.data.some( + (v) => Number(v.rateLimitedCount) > 0, + ); return ( { - return formatDate(new Date(label), "MMM dd, HH:mm"); + return formatDate(label, "MMM dd, HH:mm"); }} // @ts-expect-error - sending MORE data than expected is ok data={props.data .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) .map((v) => ({ - time: v.date, + time: `${v.date}Z`, includedCount: Number(v.includedCount) + Number(v.overageCount), rateLimitedCount: Number(v.rateLimitedCount), }))} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/rate-graph.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/rate-graph.tsx index 6fecf9f2714..3ae3fdfdaa6 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/rate-graph.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/components/rate-graph.tsx @@ -2,54 +2,63 @@ import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart"; import { formatDate } from "date-fns"; -import { InfoIcon } from "lucide-react"; export function RateGraph(props: { peakPercentage: number; currentRateLimit: number; - data: { date: string; averageRate: number }[]; + data: { date: string; averageRate: string; peakRPS: string }[]; }) { return ( 80 ? ( -
- -

- The red dashed line represents your current plan rate limit ( - {props.currentRateLimit} RPS) -

-
- ) : undefined + config={ + props.peakPercentage > 80 + ? { + averageRate: { + label: "Average RPS", + color: "hsl(var(--chart-1))", + }, + peakRPS: { + label: "Peak RPS", + color: "hsl(var(--chart-2))", + }, + maxLine: { + label: "Plan Rate Limit", + }, + } + : { + averageRate: { + label: "Average RPS", + color: "hsl(var(--chart-1))", + }, + peakRPS: { + label: "Peak RPS", + color: "hsl(var(--chart-2))", + }, + } } - config={{ - averageRate: { - label: "Average RPS", - color: "hsl(var(--chart-1))", - }, - }} + // @ts-expect-error - maxLine is always sent but not always rendered, this is OK data={props.data .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) .map((v) => ({ - time: v.date, - averageRate: Number(v.averageRate.toFixed(2)), + time: `${v.date}Z`, + averageRate: Number(Number(v.averageRate).toFixed(2)), + peakRPS: Number(v.peakRPS), + maxLine: props.currentRateLimit, }))} yAxis xAxis={{ sameDay: true, }} + showLegend hideLabel={false} toolTipLabelFormatter={(label) => { - return formatDate(new Date(label), "MMM dd, HH:mm"); + return formatDate(label, "MMM dd, HH:mm"); }} - // only show the upper limit if the peak usage is greater than 80% - maxLimit={props.peakPercentage > 80 ? props.currentRateLimit : undefined} isPending={false} /> ); diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/page.tsx index df89272f162..b914c47193a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/rpc/page.tsx @@ -48,7 +48,7 @@ export default async function RPCUsage(props: { const { peakRate, totalCounts, averageRate } = apiData.data; // Calculate percentage of limit for the peak - const peakPercentage = (peakRate.peakRPS / currentRateLimit) * 100; + const peakPercentage = (Number(peakRate.peakRPS) / currentRateLimit) * 100; // Determine status based on peak percentage const getStatusColor = (percentage: number) => { @@ -98,7 +98,7 @@ export default async function RPCUsage(props: {
- {peakRate.peakRPS.toFixed(1)} RPS + {Number(peakRate.peakRPS)} RPS
{peakRate.date - ? format(new Date(peakRate.date), "MMM d, HH:mm") + ? format(new Date(`${peakRate.date}Z`), "MMM d, HH:mm") : "No Requests in last 24 hours"}

@@ -177,9 +177,9 @@ export default async function RPCUsage(props: { Rate Limit Exceeded - Your peak usage of {peakRate.peakRPS.toFixed(1)} RPS has exceeded - your plan limit of {currentRateLimit} RPS. Consider upgrading your - plan to avoid rate limiting. + Your peak usage of {Number(peakRate.peakRPS)} RPS has exceeded your + plan limit of {currentRateLimit} RPS. Consider upgrading your plan + to avoid rate limiting. )}