Skip to content

Commit

Permalink
[TOOL-2473] Dashboard: Update RPC method analytics chart to show % va…
Browse files Browse the repository at this point in the history
…lues
  • Loading branch information
MananTank committed Jan 14, 2025
1 parent 46bf17c commit a89efab
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 77 deletions.
11 changes: 7 additions & 4 deletions apps/dashboard/src/@/components/ui/chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ const ChartTooltipContent = React.forwardRef<
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
valueFormatter?: (v: unknown) => string | undefined;
valueFormatter?: (
v: unknown,
payLoad: unknown,
) => React.ReactNode | undefined;
}
>(
(
Expand Down Expand Up @@ -233,7 +236,7 @@ const ChartTooltipContent = React.forwardRef<
)}
<div
className={cn(
"flex flex-1 justify-between gap-2 leading-none",
"flex flex-1 justify-between gap-5 leading-none",
nestLabel ? "items-end" : "items-center",
)}
>
Expand All @@ -243,10 +246,10 @@ const ChartTooltipContent = React.forwardRef<
{itemConfig?.label || item.name}
</span>
</div>
{item.value && (
{item.value !== undefined && (
<span className="font-medium font-mono text-foreground tabular-nums">
{valueFormatter
? valueFormatter(item.value) || item.value
? valueFormatter(item.value, item) || item.value
: item.value}
</span>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,32 @@ const commonMethods = [
"eth_getBalance",
"eth_getTransactionReceipt",
"eth_blockNumber",
"eth_getLogs",
"eth_getTransactionByHash",
"eth_getCode",
"eth_getTransactionCount",
"eth_getStorageAt",
"eth_gasPrice",
"eth_getBlockByHash",
"eth_getProof",
"net_version",
];

function Component() {
return (
<div className="container space-y-8 py-8">
<BadgeContainer label="Normal Usage">
<BadgeContainer label="Lot of RPC methods - show 10 at max, combines rest in 'Other'">
<RpcMethodBarChartCardUI
rawData={generateTimeSeriesData(30, commonMethods)}
/>
</BadgeContainer>

<BadgeContainer label="Max 5 RPC methods">
<RpcMethodBarChartCardUI
rawData={generateTimeSeriesData(30, commonMethods.slice(0, 5))}
/>
</BadgeContainer>

<BadgeContainer label="Empty Data">
<RpcMethodBarChartCardUI rawData={[]} />
</BadgeContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,81 +6,109 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { formatTickerNumber } from "lib/format-utils";
import { formatDate } from "date-fns";
import { useMemo } from "react";
import {
Bar,
CartesianGrid,
BarChart as RechartsBarChart,
XAxis,
YAxis,
} from "recharts";
import type { RpcMethodStats } from "types/analytics";
import { EmptyStateCard } from "../../../../components/Analytics/EmptyStateCard";

export function RpcMethodBarChartCardUI({
rawData,
}: { rawData: RpcMethodStats[] }) {
const uniqueMethods = useMemo(
() => Array.from(new Set(rawData.map((d) => d.evmMethod))),
[rawData],
);
const uniqueDates = useMemo(
() => Array.from(new Set(rawData.map((d) => d.date))),
[rawData],
);
const maxMethodsToDisplay = 10;

const { data, methodsToDisplay, chartConfig } = useMemo(() => {
const dateToValueMap: Map<string, Record<string, number>> = new Map();
const methodNameToCountMap: Map<string, number> = new Map();

for (const dataItem of rawData) {
const { date, evmMethod, count } = dataItem;
let dateRecord = dateToValueMap.get(date);

const data = useMemo(() => {
return uniqueDates.map((date) => {
const dateData: { [key: string]: string | number } = { date };
for (const method of uniqueMethods) {
const methodData = rawData.find(
(d) => d.date === date && d.evmMethod === method,
);
dateData[method] = methodData?.count ?? 0;
if (!dateRecord) {
dateRecord = {};
dateToValueMap.set(date, dateRecord);
}

// If we have too many methods to display well, add "other" and group the lowest keys for each time period
if (uniqueMethods.length > 5) {
// If we haven't added "other" as a key yet, add it
if (!uniqueMethods.includes("Other")) {
uniqueMethods.push("Other");
}
dateRecord[evmMethod] = (dateRecord[evmMethod] || 0) + count;
methodNameToCountMap.set(
evmMethod,
(methodNameToCountMap.get(evmMethod) || 0) + count,
);
}

// remove dates without with no data ( all methods have zero count )
for (const [date, value] of dateToValueMap.entries()) {
const isAllZero = Object.values(value).every((v) => v === 0);
if (isAllZero) {
dateToValueMap.delete(date);
}
}

// sort methods by count (highest count first) - remove the ones with 0 count
const sortedMethodsByCount = Array.from(methodNameToCountMap.entries())
.sort((a, b) => b[1] - a[1])
.filter((x) => x[1] > 0);

const methodsToDisplayArray = sortedMethodsByCount
.slice(0, maxMethodsToDisplay)
.map(([method]) => method);
const methodsToDisplay = new Set(methodsToDisplayArray);

// Sort the methods by their count for the time period
const sortedMethods = uniqueMethods
.filter((m) => m !== "Other")
.sort(
(a, b) =>
((dateData[b] as number) ?? 0) - ((dateData[a] as number) ?? 0),
);

dateData.Other = 0;
for (const method of sortedMethods.slice(5, sortedMethods.length)) {
dateData.Other += (dateData[method] as number) ?? 0;
delete dateData[method];
// loop over each entry in dateToValueMap
// replace the method that is not in methodsToDisplay with "Other"
// add total key that is the sum of all methods
for (const dateRecord of dateToValueMap.values()) {
// calculate total
let totalCountOfDay = 0;
for (const count of Object.values(dateRecord)) {
totalCountOfDay += count;
}

for (const method of Object.keys(dateRecord)) {
if (!methodsToDisplay.has(method)) {
dateRecord.Other =
(dateRecord.Other || 0) + (dateRecord[method] || 0);
delete dateRecord[method];
}
}
return dateData;
});
}, [uniqueDates, uniqueMethods, rawData]);

const config: ChartConfig = useMemo(() => {
const config: ChartConfig = {};
for (const method of uniqueMethods) {
config[method] = {

dateRecord.total = totalCountOfDay;
}

const returnValue: Array<Record<string, string | number>> = [];
for (const [date, value] of dateToValueMap.entries()) {
returnValue.push({ date, ...value });
}

const chartConfig: ChartConfig = {};
for (const method of methodsToDisplayArray) {
chartConfig[method] = {
label: method,
};
}
return config;
}, [uniqueMethods]);

if (
data.length === 0 ||
data.every((date) =>
Object.keys(date).every((k) => k === "date" || date[k] === 0),
)
) {

// if we need to display "Other" methods
if (sortedMethodsByCount.length > maxMethodsToDisplay) {
chartConfig.Other = {
label: "Other",
};
methodsToDisplayArray.push("Other");
}

return {
data: returnValue,
methodsToDisplay: methodsToDisplayArray,
chartConfig,
};
}, [rawData]);

if (data.length === 0) {
return <EmptyStateCard metric="RPC" link="https://portal.thirdweb.com/" />;
}

Expand All @@ -93,7 +121,7 @@ export function RpcMethodBarChartCardUI({
</CardHeader>
<CardContent className="px-2 sm:p-6 sm:pl-0">
<ChartContainer
config={config}
config={chartConfig}
className="aspect-auto h-[250px] w-full pt-6"
>
<RechartsBarChart
Expand All @@ -105,6 +133,7 @@ export function RpcMethodBarChartCardUI({
}}
>
<CartesianGrid vertical={false} />

<XAxis
dataKey="date"
tickLine={false}
Expand All @@ -119,36 +148,36 @@ export function RpcMethodBarChartCardUI({
});
}}
/>
<YAxis
width={48}
tickLine={false}
axisLine={false}
tickFormatter={(value: number) => formatTickerNumber(value)}
/>

<ChartTooltip
content={
<ChartTooltipContent
labelFormatter={(value) => {
return new Date(value).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
labelFormatter={(d) => formatDate(new Date(d), "MMM d")}
valueFormatter={(_value, _item) => {
const value = _value as number;
const payload = _item as {
payload: {
total: number;
};
};
return (
<span className="inline-flex gap-1.5">
{`${((value / payload.payload.total) * 100).toFixed(2)}`}
<span className="text-muted-foreground">%</span>
</span>
);
}}
valueFormatter={(v: unknown) =>
formatTickerNumber(v as number)
}
/>
}
/>
{uniqueMethods.map((method, idx) => (
{methodsToDisplay.map((method, idx) => (
<Bar
key={method}
stackId="a"
dataKey={method}
radius={[
idx === uniqueMethods.length - 1 ? 4 : 0,
idx === uniqueMethods.length - 1 ? 4 : 0,
idx === methodsToDisplay.length - 1 ? 4 : 0,
idx === methodsToDisplay.length - 1 ? 4 : 0,
idx === 0 ? 4 : 0,
idx === 0 ? 4 : 0,
]}
Expand Down

0 comments on commit a89efab

Please sign in to comment.