Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion apps/dashboard/src/@/api/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type {
EcosystemWalletStats,
EngineCloudStats,
InAppWalletStats,
RpcMethodStats,
TransactionStats,
UniversalBridgeStats,
UniversalBridgeWalletStats,
Expand Down Expand Up @@ -40,6 +39,18 @@ interface InsightUsageStats {
totalRequests: number;
}

export interface RpcMethodStats {
date: string;
evmMethod: string;
count: number;
}

export interface RpcUsageTypeStats {
date: string;
usageType: string;
count: number;
}

async function fetchAnalytics(
input: string | URL,
init?: RequestInit,
Expand Down Expand Up @@ -251,6 +262,26 @@ export async function getRpcMethodUsage(
return json.data as RpcMethodStats[];
}

export async function getRpcUsageByType(
params: AnalyticsQueryParams,
): Promise<RpcUsageTypeStats[]> {
const searchParams = buildSearchParams(params);
const res = await fetchAnalytics(
`v2/rpc/usage-types?${searchParams.toString()}`,
{
method: "GET",
},
);

if (res?.status !== 200) {
console.error("Failed to fetch RPC usage");
return [];
}

const json = await res.json();
return json.data as RpcUsageTypeStats[];
}

export async function getWalletUsers(
params: AnalyticsQueryParams,
): Promise<WalletUserStats[]> {
Expand Down
6 changes: 0 additions & 6 deletions apps/dashboard/src/@/types/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ export interface TransactionStats {
count: number;
}

export interface RpcMethodStats {
date: string;
evmMethod: string;
count: number;
}

export interface EngineCloudStats {
date: string;
chainId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
CoinsIcon,
HomeIcon,
LockIcon,
RssIcon,
SettingsIcon,
WalletIcon,
} from "lucide-react";
Expand Down Expand Up @@ -103,6 +104,11 @@ export function ProjectSidebarLayout(props: {
icon: SmartAccountIcon,
label: "Account Abstraction",
},
{
href: `${layoutPath}/rpc`,
icon: RssIcon,
label: "RPC",
},
{
href: `${layoutPath}/vault`,
icon: LockIcon,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/nextjs";
import type { RpcMethodStats } from "@/api/analytics";
import { BadgeContainer } from "@/storybook/utils";
import type { RpcMethodStats } from "@/types/analytics";
import { RpcMethodBarChartCardUI } from "./RpcMethodBarChartCardUI";

const meta = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {
BarChart as RechartsBarChart,
XAxis,
} from "recharts";
import type { RpcMethodStats } from "@/api/analytics";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import type { RpcMethodStats } from "@/types/analytics";
import { EmptyStateCard } from "../../../../../components/Analytics/EmptyStateCard";

export function RpcMethodBarChartCardUI({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export async function InsightAnalytics(props: {

if (!hasVolume) {
return (
<div className="container flex max-w-7xl grow flex-col">
<div className="flex grow flex-col">
<InsightFTUX clientId={props.projectClientId} />
</div>
);
Expand All @@ -134,22 +134,18 @@ export async function InsightAnalytics(props: {
</div>
<ResponsiveSuspense
fallback={
<div className="flex flex-col gap-10 lg:gap-6">
<div className="flex flex-col gap-6">
<div className="grid grid-cols-2 gap-4 lg:gap-6">
<Skeleton className="h-[120px] border rounded-xl" />
<Skeleton className="h-[120px] border rounded-xl" />
</div>
<Skeleton className="h-[350px] border rounded-xl" />
<div className="relative grid grid-cols-1 gap-6 rounded-xl border border-border bg-card p-4 lg:gap-12 xl:grid-cols-2 xl:p-6">
<Skeleton className="h-[350px] border rounded-xl" />
<Skeleton className="h-[350px] border rounded-xl" />
<div className="absolute top-6 bottom-6 left-[50%] hidden w-[1px] bg-border xl:block" />
<Skeleton className="h-[88px] border rounded-xl" />
<Skeleton className="h-[88px] border rounded-xl" />
</div>
<Skeleton className="h-[480px] border rounded-xl" />
<Skeleton className="h-[376px] border rounded-xl" />
</div>
}
searchParamsUsed={["from", "to", "interval"]}
>
<div className="flex flex-col gap-10 lg:gap-6">
<div className="flex flex-col gap-6">
<div className="grid grid-cols-2 gap-4 lg:gap-6">
<StatCard
icon={ActivityIcon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default async function Layout(props: {

return (
<div className="flex grow flex-col">
<div className="pt-4 lg:pt-6">
<div className="py-8 border-b">
<div className="container max-w-7xl">
<h1 className="mb-1 font-semibold text-2xl tracking-tight lg:text-3xl">
Insight
Expand All @@ -36,8 +36,6 @@ export default async function Layout(props: {
</UnderlineLink>
</p>
</div>

<div className="h-4" />
</div>

<div className="h-6" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"use client";
import { useMemo, useState } from "react";
import type { ThirdwebClient } from "thirdweb";
import { shortenLargeNumber } from "thirdweb/utils";
import type { RpcMethodStats } from "@/api/analytics";
import { PaginationButtons } from "@/components/blocks/pagination-buttons";
import { Card } from "@/components/ui/card";
import { SkeletonContainer } from "@/components/ui/skeleton";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { CardHeading } from "../../universal-bridge/components/common";

export function TopRPCMethodsTable(props: {
data: RpcMethodStats[];
client: ThirdwebClient;
}) {
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 30;

const sortedData = useMemo(() => {
return props.data?.sort((a, b) => b.count - a.count) || [];
}, [props.data]);

const totalPages = useMemo(() => {
return Math.ceil(sortedData.length / itemsPerPage);
}, [sortedData.length]);

const tableData = useMemo(() => {
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
return sortedData.slice(startIndex, endIndex);
}, [sortedData, currentPage]);

const isEmpty = useMemo(() => sortedData.length === 0, [sortedData]);

return (
<Card className="relative flex flex-col rounded-xl border border-border bg-card p-4">
{/* header */}
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<CardHeading>Top EVM Methods Called </CardHeading>
</div>

<div className="h-5" />
<TableContainer scrollableContainerClassName="h-[280px]">
<Table>
<TableHeader className="sticky top-0 z-10 bg-background">
<TableRow>
<TableHead>Method</TableHead>
<TableHead>Requests</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{tableData.map((method, i) => {
return (
<MethodTableRow
client={props.client}
key={method.evmMethod}
method={method}
rowIndex={i}
/>
);
})}
</TableBody>
</Table>
{isEmpty && (
<div className="flex min-h-[240px] w-full items-center justify-center text-muted-foreground text-sm">
No data available
</div>
)}
</TableContainer>

{!isEmpty && totalPages > 1 && (
<div className="mt-4 flex justify-center">
<PaginationButtons
activePage={currentPage}
onPageClick={setCurrentPage}
totalPages={totalPages}
/>
</div>
)}
</Card>
);
}

function MethodTableRow(props: {
method?: {
evmMethod: string;
count: number;
};
client: ThirdwebClient;
rowIndex: number;
}) {
const delayAnim = {
animationDelay: `${props.rowIndex * 100}ms`,
};

return (
<TableRow>
<TableCell>
<SkeletonContainer
className="inline-flex"
loadedData={props.method?.evmMethod}
render={(v) => (
<p className={"truncate max-w-[280px]"} title={v}>
{v}
</p>
)}
skeletonData="..."
style={delayAnim}
/>
</TableCell>
<TableCell>
<SkeletonContainer
className="inline-flex"
loadedData={props.method?.count}
render={(v) => {
return <p>{shortenLargeNumber(v)}</p>;
}}
skeletonData={0}
style={delayAnim}
/>
</TableCell>
</TableRow>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { format } from "date-fns";
import { shortenLargeNumber } from "thirdweb/utils";
import type { RpcUsageTypeStats } from "@/api/analytics";
import { ThirdwebAreaChart } from "@/components/blocks/charts/area-chart";

export function RequestsGraph(props: { data: RpcUsageTypeStats[] }) {
return (
<ThirdwebAreaChart
chartClassName="aspect-[1.5] lg:aspect-[4]"
config={{
requests: {
color: "hsl(var(--chart-1))",
label: "Count",
},
}}
data={props.data
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
.reduce(
(acc, curr) => {
const existingEntry = acc.find((e) => e.time === curr.date);
if (existingEntry) {
existingEntry.requests += curr.count;
} else {
acc.push({
requests: curr.count,
time: curr.date,
});
}
return acc;
},
[] as { requests: number; time: string }[],
)}
header={{
description: "Requests over time.",
title: "RPC Requests",
}}
hideLabel={false}
isPending={false}
showLegend
toolTipLabelFormatter={(label) => {
return format(label, "MMM dd, HH:mm");
}}
toolTipValueFormatter={(value) => {
return shortenLargeNumber(value as number);
}}
xAxis={{
sameDay: true,
}}
yAxis
/>
);
}
Loading
Loading