Skip to content

Commit f569209

Browse files
committed
feat(dashboard): adds rpc method bar chart card
1 parent ac42c45 commit f569209

File tree

3 files changed

+166
-2
lines changed

3 files changed

+166
-2
lines changed

apps/dashboard/src/@/api/analytics.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export interface InAppWalletStats {
2020
uniqueWalletsConnected: number;
2121
}
2222

23-
export interface EcosystemWalletStats extends InAppWalletStats {}
23+
export interface EcosystemWalletStats extends InAppWalletStats { }
2424

2525
export interface UserOpStats {
2626
date: string;
@@ -30,7 +30,13 @@ export interface UserOpStats {
3030
chainId?: string;
3131
}
3232

33-
interface AnalyticsQueryParams {
33+
export interface RpcMethodStats {
34+
date: string;
35+
evmMethod: string;
36+
count: number;
37+
}
38+
39+
export interface AnalyticsQueryParams {
3440
clientId?: string;
3541
accountId?: string;
3642
from?: Date;
@@ -115,6 +121,27 @@ export async function getUserOpUsage(
115121
return json.data as UserOpStats[];
116122
}
117123

124+
export async function getRpcMethodUsage(
125+
params: AnalyticsQueryParams,
126+
): Promise<RpcMethodStats[]> {
127+
const searchParams = buildSearchParams(params);
128+
const res = await fetchAnalytics(
129+
`v1/rpc/evm-methods?${searchParams.toString()}`,
130+
{
131+
method: "GET",
132+
headers: { "Content-Type": "application/json" },
133+
},
134+
);
135+
136+
if (res?.status !== 200) {
137+
console.error("Failed to fetch RPC method usage");
138+
return [];
139+
}
140+
141+
const json = await res.json();
142+
return json.data as RpcMethodStats[];
143+
}
144+
118145
export async function getWalletUsers(
119146
params: AnalyticsQueryParams,
120147
): Promise<WalletUserStats[]> {

apps/dashboard/src/app/team/components/Analytics/RpcMethodBarChartCard.stories.tsx

Whitespace-only changes.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import {
2+
Bar,
3+
CartesianGrid,
4+
BarChart as RechartsBarChart,
5+
XAxis,
6+
YAxis,
7+
} from "recharts";
8+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
9+
import {
10+
ChartConfig,
11+
ChartContainer,
12+
ChartTooltip,
13+
ChartTooltipContent,
14+
} from "@/components/ui/chart";
15+
import {
16+
AnalyticsQueryParams,
17+
getRpcMethodUsage,
18+
RpcMethodStats,
19+
} from "@/api/analytics";
20+
import { EmptyState } from "./EmptyState";
21+
import { formatTickerNumber } from "lib/format-utils";
22+
23+
const chartConfig = {
24+
evmMethod: { label: "EVM Method", color: "hsl(var(--chart-1))" },
25+
count: { label: "Count", color: "hsl(var(--chart-2))" },
26+
};
27+
28+
export async function RpcMethodBarChartCard(props: AnalyticsQueryParams) {
29+
const rawData = await getRpcMethodUsage(props);
30+
31+
return <RpcMethodBarChartCardUI rawData={rawData} />;
32+
}
33+
34+
// Split the UI out for storybook mocking
35+
export function RpcMethodBarChartCardUI({
36+
rawData,
37+
}: { rawData: RpcMethodStats[] }) {
38+
const uniqueMethods = Array.from(new Set(rawData.map((d) => d.evmMethod)));
39+
const uniqueDates = Array.from(new Set(rawData.map((d) => d.date)));
40+
41+
const data = uniqueDates.map((date) => {
42+
const dateData: { [key: string]: string | number } = { date };
43+
for (const method of uniqueMethods) {
44+
const methodData = rawData.find(
45+
(d) => d.date === date && d.evmMethod === method,
46+
);
47+
dateData[method] = methodData?.count ?? 0;
48+
}
49+
return dateData;
50+
});
51+
52+
const config: ChartConfig = {};
53+
for (const method of uniqueMethods) {
54+
config[method] = {
55+
label: method,
56+
};
57+
}
58+
59+
if (rawData.length === 0 || rawData.every((d) => d.count === 0)) {
60+
return <EmptyState />;
61+
}
62+
63+
return (
64+
<Card>
65+
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0">
66+
<div className="flex flex-1 flex-col justify-center gap-1 p-6">
67+
<CardTitle className="font-semibold text-lg">RPC Methods</CardTitle>
68+
</div>
69+
</CardHeader>
70+
<CardContent className="px-2 sm:p-6 sm:pl-0">
71+
<ChartContainer
72+
config={{
73+
...chartConfig,
74+
}}
75+
className="aspect-auto h-[250px] w-full pt-6"
76+
>
77+
<RechartsBarChart
78+
accessibilityLayer
79+
data={data}
80+
margin={{
81+
left: 12,
82+
right: 12,
83+
}}
84+
>
85+
<CartesianGrid vertical={false} />
86+
<XAxis
87+
dataKey="date"
88+
tickLine={false}
89+
axisLine={false}
90+
tickMargin={8}
91+
minTickGap={32}
92+
tickFormatter={(value: string) => {
93+
const date = new Date(value);
94+
return date.toLocaleDateString("en-US", {
95+
month: "short",
96+
day: "numeric",
97+
});
98+
}}
99+
/>
100+
<YAxis
101+
width={48}
102+
tickLine={false}
103+
axisLine={false}
104+
tickFormatter={(value: number) => formatTickerNumber(value)}
105+
/>
106+
<ChartTooltip
107+
content={
108+
<ChartTooltipContent
109+
className="w-[200px]"
110+
labelFormatter={(value) => {
111+
return new Date(value).toLocaleDateString("en-US", {
112+
month: "short",
113+
day: "numeric",
114+
year: "numeric",
115+
});
116+
}}
117+
valueFormatter={(v: unknown) =>
118+
formatTickerNumber(v as number)
119+
}
120+
/>
121+
}
122+
/>
123+
{uniqueMethods.map((method, idx) => (
124+
<Bar
125+
key={method}
126+
stackId="a"
127+
dataKey={method}
128+
radius={4}
129+
fill={`hsl(var(--chart-${idx}))`}
130+
/>
131+
))}
132+
</RechartsBarChart>
133+
</ChartContainer>
134+
</CardContent>
135+
</Card>
136+
);
137+
}

0 commit comments

Comments
 (0)