diff --git a/apps/dashboard/src/@/api/usage/rpc.ts b/apps/dashboard/src/@/api/usage/rpc.ts new file mode 100644 index 00000000000..97112960966 --- /dev/null +++ b/apps/dashboard/src/@/api/usage/rpc.ts @@ -0,0 +1,54 @@ +import "server-only"; +import { unstable_cache } from "next/cache"; + +export type RPCUsageDataItem = { + date: string; + usageType: "included" | "overage" | "rate-limit"; + count: string; +}; + +export const fetchRPCUsage = unstable_cache( + async (params: { + teamId: string; + projectId?: string; + authToken: string; + from: string; + to: string; + period: "day" | "week" | "month" | "year" | "all"; + }) => { + const analyticsEndpoint = process.env.ANALYTICS_SERVICE_URL as string; + const url = new URL(`${analyticsEndpoint}/v2/rpc/usage-types`); + url.searchParams.set("teamId", params.teamId); + if (params.projectId) { + url.searchParams.set("projectId", params.projectId); + } + url.searchParams.set("from", params.from); + url.searchParams.set("to", params.to); + url.searchParams.set("period", params.period); + + const res = await fetch(url, { + headers: { + Authorization: `Bearer ${params.authToken}`, + }, + }); + + if (!res.ok) { + const error = await res.text(); + return { + ok: false as const, + error: error, + }; + } + + const resData = await res.json(); + + return { + ok: true as const, + data: resData.data as RPCUsageDataItem[], + }; + }, + ["nebula-analytics"], + { + revalidate: 60 * 60, // 1 hour + }, +); diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/overview/components/Usage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/overview/components/Usage.tsx index f237f0c0604..c31345b042b 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/overview/components/Usage.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/usage/overview/components/Usage.tsx @@ -1,6 +1,8 @@ import { getInAppWalletUsage, getUserOpUsage } from "@/api/analytics"; import type { Team } from "@/api/team"; import type { TeamSubscription } from "@/api/team-subscription"; +import type { RPCUsageDataItem } from "@/api/usage/rpc"; +import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart"; import { Button } from "@/components/ui/button"; import type { Account, @@ -28,6 +30,7 @@ type UsageProps = { image: string | null; slug: string; }[]; + rpcUsage: RPCUsageDataItem[]; }; const compactNumberFormatter = new Intl.NumberFormat("en-US", { @@ -42,24 +45,8 @@ export const Usage: React.FC = ({ team, client, projects, + rpcUsage, }) => { - // TODO - get this from team instead of account - const rpcMetrics = useMemo(() => { - if (!usageData) { - return {}; - } - - return { - title: "Unlimited Requests", - total: ( - - {compactNumberFormatter.format(usageData.rateLimits.rpc)} Requests Per - Second - - ), - }; - }, [usageData]); - const gatewayMetrics = useMemo(() => { if (!usageData) { return {}; @@ -91,6 +78,37 @@ export const Usage: React.FC = ({ const storageUsage = team.capabilities.storage.upload; + const rpcUsageData = useMemo(() => { + const mappedRPCUsage = rpcUsage.reduce( + (acc, curr) => { + switch (curr.usageType) { + case "rate-limit": + acc[curr.date] = { + ...(acc[curr.date] || {}), + "rate-limit": + (acc[curr.date]?.["rate-limit"] || 0) + Number(curr.count), + included: acc[curr.date]?.included || 0, + }; + break; + default: + acc[curr.date] = { + ...(acc[curr.date] || {}), + "rate-limit": acc[curr.date]?.["rate-limit"] || 0, + included: (acc[curr.date]?.included || 0) + Number(curr.count), + }; + break; + } + return acc; + }, + {} as Record, + ); + + return Object.entries(mappedRPCUsage).map(([date, usage]) => ({ + time: new Date(date).getTime(), + ...usage, + })); + }, [rpcUsage]); + return (
= ({ variant="team" /> - Something went wrong. Please try again later. @@ -48,6 +66,7 @@ export default async function Page(props: { account={account} team={team} client={client} + rpcUsage={rpcUsage.ok ? rpcUsage.data : []} projects={projects.map((project) => ({ id: project.id, name: project.name,