Skip to content

Commit 0fdfb8a

Browse files
committed
[TOOL-2473] Dashboard: Update RPC method analytics chart to show % values (#5954)
<!-- start pr-codex --> ## PR-Codex overview This PR focuses on enhancing the `RpcMethodBarChartCardUI` component by improving data handling, updating the `valueFormatter` function, and refining the chart display logic. It also introduces new RPC methods and adjusts the layout of the chart. ### Detailed summary - Updated `valueFormatter` in `chart.tsx` to accept a second parameter. - Changed the gap between elements in the chart tooltip from `gap-2` to `gap-5`. - Adjusted condition to check for `item.value` to `item.value !== undefined`. - Added randomness to the data generation logic to simulate empty data. - Introduced new RPC methods in the `commonMethods` array. - Updated the `BadgeContainer` labels for clarity on RPC methods displayed. - Enhanced data processing in `RpcMethodBarChartCardUI` for better method display. - Implemented a total count for methods in the chart data. - Updated the tooltip label formatter to use `formatDate`. - Modified the Y-axis configuration to remove unused properties. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 46bf17c commit 0fdfb8a

File tree

3 files changed

+126
-78
lines changed

3 files changed

+126
-78
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: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,17 @@ const generateTimeSeriesData = (
3838
const date = new Date(today);
3939
date.setDate(date.getDate() - i);
4040

41+
// 10% chance of making the data completely empty
42+
const shouldMakeEmpty = Math.random() > 0.9;
43+
4144
for (const method of methods) {
4245
data.push({
4346
date: date.toISOString(),
4447
evmMethod: method,
45-
count: emptyData ? 0 : Math.floor(Math.random() * 1000) + 100,
48+
count:
49+
shouldMakeEmpty || emptyData
50+
? 0
51+
: Math.floor(Math.random() * 1000) + 100,
4652
});
4753
}
4854
}
@@ -55,17 +61,32 @@ const commonMethods = [
5561
"eth_getBalance",
5662
"eth_getTransactionReceipt",
5763
"eth_blockNumber",
64+
"eth_getLogs",
65+
"eth_getTransactionByHash",
66+
"eth_getCode",
67+
"eth_getTransactionCount",
68+
"eth_getStorageAt",
69+
"eth_gasPrice",
70+
"eth_getBlockByHash",
71+
"eth_getProof",
72+
"net_version",
5873
];
5974

6075
function Component() {
6176
return (
6277
<div className="container space-y-8 py-8">
63-
<BadgeContainer label="Normal Usage">
78+
<BadgeContainer label="Lot of RPC methods - show 10 at max, combines rest in 'Other'">
6479
<RpcMethodBarChartCardUI
6580
rawData={generateTimeSeriesData(30, commonMethods)}
6681
/>
6782
</BadgeContainer>
6883

84+
<BadgeContainer label="Max 5 RPC methods">
85+
<RpcMethodBarChartCardUI
86+
rawData={generateTimeSeriesData(30, commonMethods.slice(0, 5))}
87+
/>
88+
</BadgeContainer>
89+
6990
<BadgeContainer label="Empty Data">
7091
<RpcMethodBarChartCardUI rawData={[]} />
7192
</BadgeContainer>

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

Lines changed: 96 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,81 +6,102 @@ 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, isAllEmpty } = 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+
// sort methods by count (highest count first) - remove the ones with 0 count
46+
const sortedMethodsByCount = Array.from(methodNameToCountMap.entries())
47+
.sort((a, b) => b[1] - a[1])
48+
.filter((x) => x[1] > 0);
4949

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];
50+
const methodsToDisplayArray = sortedMethodsByCount
51+
.slice(0, maxMethodsToDisplay)
52+
.map(([method]) => method);
53+
const methodsToDisplay = new Set(methodsToDisplayArray);
54+
55+
// loop over each entry in dateToValueMap
56+
// replace the method that is not in methodsToDisplay with "Other"
57+
// add total key that is the sum of all methods
58+
for (const dateRecord of dateToValueMap.values()) {
59+
// calculate total
60+
let totalCountOfDay = 0;
61+
for (const count of Object.values(dateRecord)) {
62+
totalCountOfDay += count;
63+
}
64+
65+
for (const method of Object.keys(dateRecord)) {
66+
if (!methodsToDisplay.has(method)) {
67+
dateRecord.Other =
68+
(dateRecord.Other || 0) + (dateRecord[method] || 0);
69+
delete dateRecord[method];
6270
}
6371
}
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] = {
72+
73+
dateRecord.total = totalCountOfDay;
74+
}
75+
76+
const returnValue: Array<Record<string, string | number>> = [];
77+
for (const [date, value] of dateToValueMap.entries()) {
78+
returnValue.push({ date, ...value });
79+
}
80+
81+
const chartConfig: ChartConfig = {};
82+
for (const method of methodsToDisplayArray) {
83+
chartConfig[method] = {
7284
label: method,
7385
};
7486
}
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-
) {
87+
88+
// if we need to display "Other" methods
89+
if (sortedMethodsByCount.length > maxMethodsToDisplay) {
90+
chartConfig.Other = {
91+
label: "Other",
92+
};
93+
methodsToDisplayArray.push("Other");
94+
}
95+
96+
return {
97+
data: returnValue,
98+
methodsToDisplay: methodsToDisplayArray,
99+
chartConfig,
100+
isAllEmpty: returnValue.every((d) => d.total === 0),
101+
};
102+
}, [rawData]);
103+
104+
if (data.length === 0 || isAllEmpty) {
84105
return <EmptyStateCard metric="RPC" link="https://portal.thirdweb.com/" />;
85106
}
86107

@@ -93,7 +114,7 @@ export function RpcMethodBarChartCardUI({
93114
</CardHeader>
94115
<CardContent className="px-2 sm:p-6 sm:pl-0">
95116
<ChartContainer
96-
config={config}
117+
config={chartConfig}
97118
className="aspect-auto h-[250px] w-full pt-6"
98119
>
99120
<RechartsBarChart
@@ -105,6 +126,7 @@ export function RpcMethodBarChartCardUI({
105126
}}
106127
>
107128
<CartesianGrid vertical={false} />
129+
108130
<XAxis
109131
dataKey="date"
110132
tickLine={false}
@@ -119,36 +141,38 @@ export function RpcMethodBarChartCardUI({
119141
});
120142
}}
121143
/>
122-
<YAxis
123-
width={48}
124-
tickLine={false}
125-
axisLine={false}
126-
tickFormatter={(value: number) => formatTickerNumber(value)}
127-
/>
144+
128145
<ChartTooltip
129146
content={
130147
<ChartTooltipContent
131-
labelFormatter={(value) => {
132-
return new Date(value).toLocaleDateString("en-US", {
133-
month: "short",
134-
day: "numeric",
135-
year: "numeric",
136-
});
148+
labelFormatter={(d) => formatDate(new Date(d), "MMM d")}
149+
valueFormatter={(_value, _item) => {
150+
const value = typeof _value === "number" ? _value : 0;
151+
const payload = _item as {
152+
payload: {
153+
total: number;
154+
};
155+
};
156+
const total =
157+
payload.payload.total === 0 ? 1 : payload.payload.total;
158+
return (
159+
<span className="inline-flex gap-1.5">
160+
{`${((value / total) * 100).toFixed(2)}`}
161+
<span className="text-muted-foreground">%</span>
162+
</span>
163+
);
137164
}}
138-
valueFormatter={(v: unknown) =>
139-
formatTickerNumber(v as number)
140-
}
141165
/>
142166
}
143167
/>
144-
{uniqueMethods.map((method, idx) => (
168+
{methodsToDisplay.map((method, idx) => (
145169
<Bar
146170
key={method}
147171
stackId="a"
148172
dataKey={method}
149173
radius={[
150-
idx === uniqueMethods.length - 1 ? 4 : 0,
151-
idx === uniqueMethods.length - 1 ? 4 : 0,
174+
idx === methodsToDisplay.length - 1 ? 4 : 0,
175+
idx === methodsToDisplay.length - 1 ? 4 : 0,
152176
idx === 0 ? 4 : 0,
153177
idx === 0 ? 4 : 0,
154178
]}

0 commit comments

Comments
 (0)