Skip to content

Commit a89efab

Browse files
committed
[TOOL-2473] Dashboard: Update RPC method analytics chart to show % values
1 parent 46bf17c commit a89efab

File tree

3 files changed

+124
-77
lines changed

3 files changed

+124
-77
lines changed

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,10 @@ const ChartTooltipContent = React.forwardRef<
113113
indicator?: "line" | "dot" | "dashed";
114114
nameKey?: string;
115115
labelKey?: string;
116-
valueFormatter?: (v: unknown) => string | undefined;
116+
valueFormatter?: (
117+
v: unknown,
118+
payLoad: unknown,
119+
) => React.ReactNode | undefined;
117120
}
118121
>(
119122
(
@@ -233,7 +236,7 @@ const ChartTooltipContent = React.forwardRef<
233236
)}
234237
<div
235238
className={cn(
236-
"flex flex-1 justify-between gap-2 leading-none",
239+
"flex flex-1 justify-between gap-5 leading-none",
237240
nestLabel ? "items-end" : "items-center",
238241
)}
239242
>
@@ -243,10 +246,10 @@ const ChartTooltipContent = React.forwardRef<
243246
{itemConfig?.label || item.name}
244247
</span>
245248
</div>
246-
{item.value && (
249+
{item.value !== undefined && (
247250
<span className="font-medium font-mono text-foreground tabular-nums">
248251
{valueFormatter
249-
? valueFormatter(item.value) || item.value
252+
? valueFormatter(item.value, item) || item.value
250253
: item.value}
251254
</span>
252255
)}

apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/RpcMethodBarChartCard/RpcMethodBarChartCard.stories.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,32 @@ const commonMethods = [
5555
"eth_getBalance",
5656
"eth_getTransactionReceipt",
5757
"eth_blockNumber",
58+
"eth_getLogs",
59+
"eth_getTransactionByHash",
60+
"eth_getCode",
61+
"eth_getTransactionCount",
62+
"eth_getStorageAt",
63+
"eth_gasPrice",
64+
"eth_getBlockByHash",
65+
"eth_getProof",
66+
"net_version",
5867
];
5968

6069
function Component() {
6170
return (
6271
<div className="container space-y-8 py-8">
63-
<BadgeContainer label="Normal Usage">
72+
<BadgeContainer label="Lot of RPC methods - show 10 at max, combines rest in 'Other'">
6473
<RpcMethodBarChartCardUI
6574
rawData={generateTimeSeriesData(30, commonMethods)}
6675
/>
6776
</BadgeContainer>
6877

78+
<BadgeContainer label="Max 5 RPC methods">
79+
<RpcMethodBarChartCardUI
80+
rawData={generateTimeSeriesData(30, commonMethods.slice(0, 5))}
81+
/>
82+
</BadgeContainer>
83+
6984
<BadgeContainer label="Empty Data">
7085
<RpcMethodBarChartCardUI rawData={[]} />
7186
</BadgeContainer>

apps/dashboard/src/app/team/[team_slug]/[project_slug]/components/RpcMethodBarChartCard/RpcMethodBarChartCardUI.tsx

Lines changed: 101 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,81 +6,109 @@ import {
66
ChartTooltip,
77
ChartTooltipContent,
88
} from "@/components/ui/chart";
9-
import { formatTickerNumber } from "lib/format-utils";
9+
import { formatDate } from "date-fns";
1010
import { useMemo } from "react";
1111
import {
1212
Bar,
1313
CartesianGrid,
1414
BarChart as RechartsBarChart,
1515
XAxis,
16-
YAxis,
1716
} from "recharts";
1817
import type { RpcMethodStats } from "types/analytics";
1918
import { EmptyStateCard } from "../../../../components/Analytics/EmptyStateCard";
2019

2120
export function RpcMethodBarChartCardUI({
2221
rawData,
2322
}: { rawData: RpcMethodStats[] }) {
24-
const uniqueMethods = useMemo(
25-
() => Array.from(new Set(rawData.map((d) => d.evmMethod))),
26-
[rawData],
27-
);
28-
const uniqueDates = useMemo(
29-
() => Array.from(new Set(rawData.map((d) => d.date))),
30-
[rawData],
31-
);
23+
const maxMethodsToDisplay = 10;
24+
25+
const { data, methodsToDisplay, chartConfig } = useMemo(() => {
26+
const dateToValueMap: Map<string, Record<string, number>> = new Map();
27+
const methodNameToCountMap: Map<string, number> = new Map();
28+
29+
for (const dataItem of rawData) {
30+
const { date, evmMethod, count } = dataItem;
31+
let dateRecord = dateToValueMap.get(date);
3232

33-
const data = useMemo(() => {
34-
return uniqueDates.map((date) => {
35-
const dateData: { [key: string]: string | number } = { date };
36-
for (const method of uniqueMethods) {
37-
const methodData = rawData.find(
38-
(d) => d.date === date && d.evmMethod === method,
39-
);
40-
dateData[method] = methodData?.count ?? 0;
33+
if (!dateRecord) {
34+
dateRecord = {};
35+
dateToValueMap.set(date, dateRecord);
4136
}
4237

43-
// If we have too many methods to display well, add "other" and group the lowest keys for each time period
44-
if (uniqueMethods.length > 5) {
45-
// If we haven't added "other" as a key yet, add it
46-
if (!uniqueMethods.includes("Other")) {
47-
uniqueMethods.push("Other");
48-
}
38+
dateRecord[evmMethod] = (dateRecord[evmMethod] || 0) + count;
39+
methodNameToCountMap.set(
40+
evmMethod,
41+
(methodNameToCountMap.get(evmMethod) || 0) + count,
42+
);
43+
}
44+
45+
// remove dates without with no data ( all methods have zero count )
46+
for (const [date, value] of dateToValueMap.entries()) {
47+
const isAllZero = Object.values(value).every((v) => v === 0);
48+
if (isAllZero) {
49+
dateToValueMap.delete(date);
50+
}
51+
}
52+
53+
// sort methods by count (highest count first) - remove the ones with 0 count
54+
const sortedMethodsByCount = Array.from(methodNameToCountMap.entries())
55+
.sort((a, b) => b[1] - a[1])
56+
.filter((x) => x[1] > 0);
57+
58+
const methodsToDisplayArray = sortedMethodsByCount
59+
.slice(0, maxMethodsToDisplay)
60+
.map(([method]) => method);
61+
const methodsToDisplay = new Set(methodsToDisplayArray);
4962

50-
// Sort the methods by their count for the time period
51-
const sortedMethods = uniqueMethods
52-
.filter((m) => m !== "Other")
53-
.sort(
54-
(a, b) =>
55-
((dateData[b] as number) ?? 0) - ((dateData[a] as number) ?? 0),
56-
);
57-
58-
dateData.Other = 0;
59-
for (const method of sortedMethods.slice(5, sortedMethods.length)) {
60-
dateData.Other += (dateData[method] as number) ?? 0;
61-
delete dateData[method];
63+
// loop over each entry in dateToValueMap
64+
// replace the method that is not in methodsToDisplay with "Other"
65+
// add total key that is the sum of all methods
66+
for (const dateRecord of dateToValueMap.values()) {
67+
// calculate total
68+
let totalCountOfDay = 0;
69+
for (const count of Object.values(dateRecord)) {
70+
totalCountOfDay += count;
71+
}
72+
73+
for (const method of Object.keys(dateRecord)) {
74+
if (!methodsToDisplay.has(method)) {
75+
dateRecord.Other =
76+
(dateRecord.Other || 0) + (dateRecord[method] || 0);
77+
delete dateRecord[method];
6278
}
6379
}
64-
return dateData;
65-
});
66-
}, [uniqueDates, uniqueMethods, rawData]);
67-
68-
const config: ChartConfig = useMemo(() => {
69-
const config: ChartConfig = {};
70-
for (const method of uniqueMethods) {
71-
config[method] = {
80+
81+
dateRecord.total = totalCountOfDay;
82+
}
83+
84+
const returnValue: Array<Record<string, string | number>> = [];
85+
for (const [date, value] of dateToValueMap.entries()) {
86+
returnValue.push({ date, ...value });
87+
}
88+
89+
const chartConfig: ChartConfig = {};
90+
for (const method of methodsToDisplayArray) {
91+
chartConfig[method] = {
7292
label: method,
7393
};
7494
}
75-
return config;
76-
}, [uniqueMethods]);
77-
78-
if (
79-
data.length === 0 ||
80-
data.every((date) =>
81-
Object.keys(date).every((k) => k === "date" || date[k] === 0),
82-
)
83-
) {
95+
96+
// if we need to display "Other" methods
97+
if (sortedMethodsByCount.length > maxMethodsToDisplay) {
98+
chartConfig.Other = {
99+
label: "Other",
100+
};
101+
methodsToDisplayArray.push("Other");
102+
}
103+
104+
return {
105+
data: returnValue,
106+
methodsToDisplay: methodsToDisplayArray,
107+
chartConfig,
108+
};
109+
}, [rawData]);
110+
111+
if (data.length === 0) {
84112
return <EmptyStateCard metric="RPC" link="https://portal.thirdweb.com/" />;
85113
}
86114

@@ -93,7 +121,7 @@ export function RpcMethodBarChartCardUI({
93121
</CardHeader>
94122
<CardContent className="px-2 sm:p-6 sm:pl-0">
95123
<ChartContainer
96-
config={config}
124+
config={chartConfig}
97125
className="aspect-auto h-[250px] w-full pt-6"
98126
>
99127
<RechartsBarChart
@@ -105,6 +133,7 @@ export function RpcMethodBarChartCardUI({
105133
}}
106134
>
107135
<CartesianGrid vertical={false} />
136+
108137
<XAxis
109138
dataKey="date"
110139
tickLine={false}
@@ -119,36 +148,36 @@ export function RpcMethodBarChartCardUI({
119148
});
120149
}}
121150
/>
122-
<YAxis
123-
width={48}
124-
tickLine={false}
125-
axisLine={false}
126-
tickFormatter={(value: number) => formatTickerNumber(value)}
127-
/>
151+
128152
<ChartTooltip
129153
content={
130154
<ChartTooltipContent
131-
labelFormatter={(value) => {
132-
return new Date(value).toLocaleDateString("en-US", {
133-
month: "short",
134-
day: "numeric",
135-
year: "numeric",
136-
});
155+
labelFormatter={(d) => formatDate(new Date(d), "MMM d")}
156+
valueFormatter={(_value, _item) => {
157+
const value = _value as number;
158+
const payload = _item as {
159+
payload: {
160+
total: number;
161+
};
162+
};
163+
return (
164+
<span className="inline-flex gap-1.5">
165+
{`${((value / payload.payload.total) * 100).toFixed(2)}`}
166+
<span className="text-muted-foreground">%</span>
167+
</span>
168+
);
137169
}}
138-
valueFormatter={(v: unknown) =>
139-
formatTickerNumber(v as number)
140-
}
141170
/>
142171
}
143172
/>
144-
{uniqueMethods.map((method, idx) => (
173+
{methodsToDisplay.map((method, idx) => (
145174
<Bar
146175
key={method}
147176
stackId="a"
148177
dataKey={method}
149178
radius={[
150-
idx === uniqueMethods.length - 1 ? 4 : 0,
151-
idx === uniqueMethods.length - 1 ? 4 : 0,
179+
idx === methodsToDisplay.length - 1 ? 4 : 0,
180+
idx === methodsToDisplay.length - 1 ? 4 : 0,
152181
idx === 0 ? 4 : 0,
153182
idx === 0 ? 4 : 0,
154183
]}

0 commit comments

Comments
 (0)