Skip to content

Commit dfc824d

Browse files
committed
[Dashboard] Feature: RPC Chart (#5504)
CNCT-2395 <img width="1161" alt="Screenshot 2024-11-22 at 9 52 09 PM" src="https://github.com/user-attachments/assets/e46f1645-11bb-42d5-abcf-147852a3e8ce"> <!-- start pr-codex --> --- ## PR-Codex overview This PR primarily focuses on refactoring the analytics data handling by shifting the source of types from the `@/api/analytics` module to a new `types/analytics` module. This change enhances type definitions and improves code organization. ### Detailed summary - Changed import paths for various analytics types to `types/analytics`. - Added new interfaces in `apps/dashboard/src/types/analytics.ts` for `WalletStats`, `WalletUserStats`, `InAppWalletStats`, `EcosystemWalletStats`, `UserOpStats`, `RpcMethodStats`, and `AnalyticsQueryParams`. - Updated multiple components and utilities to utilize the new type definitions. - Implemented a new RPC method usage function in `apps/dashboard/src/@/api/analytics.ts`. - Added `RpcMethodBarChartCard` component to visualize RPC method usage in `apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 7dd4298 commit dfc824d

File tree

26 files changed

+406
-75
lines changed

26 files changed

+406
-75
lines changed

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

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,12 @@
11
import { fetchAnalytics } from "data/analytics/fetch-analytics";
2-
3-
export interface WalletStats {
4-
date: string;
5-
uniqueWalletsConnected: number;
6-
totalConnections: number;
7-
walletType: string;
8-
}
9-
10-
export interface WalletUserStats {
11-
date: string;
12-
newUsers: number;
13-
returningUsers: number;
14-
totalUsers: number;
15-
}
16-
17-
export interface InAppWalletStats {
18-
date: string;
19-
authenticationMethod: string;
20-
uniqueWalletsConnected: number;
21-
}
22-
23-
export interface EcosystemWalletStats extends InAppWalletStats {}
24-
25-
export interface UserOpStats {
26-
date: string;
27-
successful: number;
28-
failed: number;
29-
sponsoredUsd: number;
30-
chainId?: string;
31-
}
32-
33-
interface AnalyticsQueryParams {
34-
clientId?: string;
35-
accountId?: string;
36-
from?: Date;
37-
to?: Date;
38-
period?: "day" | "week" | "month" | "year" | "all";
39-
}
2+
import type {
3+
AnalyticsQueryParams,
4+
InAppWalletStats,
5+
RpcMethodStats,
6+
UserOpStats,
7+
WalletStats,
8+
WalletUserStats,
9+
} from "types/analytics";
4010

4111
function buildSearchParams(params: AnalyticsQueryParams): URLSearchParams {
4212
const searchParams = new URLSearchParams();
@@ -115,6 +85,27 @@ export async function getUserOpUsage(
11585
return json.data as UserOpStats[];
11686
}
11787

88+
export async function getRpcMethodUsage(
89+
params: AnalyticsQueryParams,
90+
): Promise<RpcMethodStats[]> {
91+
const searchParams = buildSearchParams(params);
92+
const res = await fetchAnalytics(
93+
`v1/rpc/evm-methods?${searchParams.toString()}`,
94+
{
95+
method: "GET",
96+
headers: { "Content-Type": "application/json" },
97+
},
98+
);
99+
100+
if (res?.status !== 200) {
101+
console.error("Failed to fetch RPC method usage");
102+
return [];
103+
}
104+
105+
const json = await res.json();
106+
return json.data as RpcMethodStats[];
107+
}
108+
118109
export async function getWalletUsers(
119110
params: AnalyticsQueryParams,
120111
): Promise<WalletUserStats[]> {
@@ -135,3 +126,21 @@ export async function getWalletUsers(
135126
const json = await res.json();
136127
return json.data as WalletUserStats[];
137128
}
129+
130+
export async function isProjectActive(
131+
params: AnalyticsQueryParams,
132+
): Promise<boolean> {
133+
const searchParams = buildSearchParams(params);
134+
const res = await fetchAnalytics(`v1/active?${searchParams.toString()}`, {
135+
method: "GET",
136+
headers: { "Content-Type": "application/json" },
137+
});
138+
139+
if (res?.status !== 200) {
140+
console.error("Failed to fetch project active status");
141+
return false;
142+
}
143+
144+
const json = await res.json();
145+
return json.data.isActive as boolean;
146+
}

apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { UserOpStats } from "@/api/analytics";
21
import type { Team } from "@/api/team";
32
import {
43
type Query,
@@ -9,6 +8,7 @@ import {
98
import { THIRDWEB_ANALYTICS_API_HOST, THIRDWEB_API_HOST } from "constants/urls";
109
import { useAllChainsData } from "hooks/chains/allChains";
1110
import invariant from "tiny-invariant";
11+
import type { UserOpStats } from "types/analytics";
1212
import { accountKeys, apiKeys, authorizedWallets } from "../cache-keys";
1313
import { useLoggedInUser } from "./useLoggedInUser";
1414

apps/dashboard/src/app/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { UserOpStats } from "@/api/analytics";
21
import { cn } from "@/lib/utils";
32
import { defineChain } from "thirdweb";
43
import { type ChainMetadata, getChainMetadata } from "thirdweb/chains";
4+
import type { UserOpStats } from "types/analytics";
55
import { EmptyAccountAbstractionChartContent } from "../../../../../components/smart-wallets/AccountAbstractionAnalytics/SponsoredTransactionsChartCard";
66
import { BarChart } from "../../../components/Analytics/BarChart";
77
import { CombinedBarChartCard } from "../../../components/Analytics/CombinedBarChartCard";

apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type {
1010
InAppWalletStats,
1111
WalletStats,
1212
WalletUserStats,
13-
} from "@/api/analytics";
13+
} from "types/analytics";
1414

1515
import {
1616
type DurationId,

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/analytics/components/EcosystemWalletUsersChartCard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"use client";
2-
import type { EcosystemWalletStats } from "@/api/analytics";
32
import { ExportToCSVButton } from "@/components/blocks/ExportToCSVButton";
43
import {
54
type ChartConfig,
@@ -21,6 +20,7 @@ import { format } from "date-fns";
2120
import { formatTickerNumber } from "lib/format-utils";
2221
import { useMemo } from "react";
2322
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
23+
import type { EcosystemWalletStats } from "types/analytics";
2424

2525
type ChartData = Record<string, number> & {
2626
time: string; // human readable date

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/analytics/components/Summary.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { EcosystemWalletStats } from "@/api/analytics";
21
import { Stat } from "components/analytics/stat";
32
import { ActivityIcon, UserIcon } from "lucide-react";
3+
import type { EcosystemWalletStats } from "types/analytics";
44

55
export function EcosystemWalletsSummary(props: {
66
allTimeStats: EcosystemWalletStats[] | undefined;

apps/dashboard/src/app/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/components/ecosystem-header.client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"use client";
2-
import type { EcosystemWalletStats } from "@/api/analytics";
32
import { CopyButton } from "@/components/ui/CopyButton";
43
import { Spinner } from "@/components/ui/Spinner/Spinner";
54
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
@@ -24,6 +23,7 @@ import {
2423
} from "lucide-react";
2524
import Image from "next/image";
2625
import Link from "next/link";
26+
import type { EcosystemWalletStats } from "types/analytics";
2727
import { useEcosystemList } from "../../../hooks/use-ecosystem-list";
2828
import type { Ecosystem } from "../../../types";
2929
import { EcosystemWalletsSummary } from "../analytics/components/Summary";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type { Meta, StoryObj } from "@storybook/react";
2+
import { BadgeContainer, mobileViewport } from "stories/utils";
3+
import type { RpcMethodStats } from "types/analytics";
4+
import { RpcMethodBarChartCardUI } from "./RpcMethodBarChartCardUI";
5+
6+
const meta = {
7+
title: "Analytics/RpcMethodBarChartCard",
8+
component: Component,
9+
parameters: {
10+
layout: "centered",
11+
},
12+
} satisfies Meta<typeof Component>;
13+
14+
export default meta;
15+
type Story = StoryObj<typeof meta>;
16+
17+
export const Desktop: Story = {
18+
parameters: {
19+
viewport: { defaultViewport: "desktop" },
20+
},
21+
};
22+
23+
export const Mobile: Story = {
24+
parameters: {
25+
viewport: mobileViewport("iphone14"),
26+
},
27+
};
28+
29+
const generateTimeSeriesData = (
30+
days: number,
31+
methods: string[],
32+
emptyData = false,
33+
) => {
34+
const data: RpcMethodStats[] = [];
35+
const today = new Date();
36+
37+
for (let i = days - 1; i >= 0; i--) {
38+
const date = new Date(today);
39+
date.setDate(date.getDate() - i);
40+
41+
for (const method of methods) {
42+
data.push({
43+
date: date.toISOString(),
44+
evmMethod: method,
45+
count: emptyData ? 0 : Math.floor(Math.random() * 1000) + 100,
46+
});
47+
}
48+
}
49+
50+
return data;
51+
};
52+
53+
const commonMethods = [
54+
"eth_call",
55+
"eth_getBalance",
56+
"eth_getTransactionReceipt",
57+
"eth_blockNumber",
58+
];
59+
60+
function Component() {
61+
return (
62+
<div className="container space-y-8 py-8">
63+
<BadgeContainer label="Normal Usage">
64+
<RpcMethodBarChartCardUI
65+
rawData={generateTimeSeriesData(30, commonMethods)}
66+
/>
67+
</BadgeContainer>
68+
69+
<BadgeContainer label="Empty Data">
70+
<RpcMethodBarChartCardUI rawData={[]} />
71+
</BadgeContainer>
72+
73+
<BadgeContainer label="Zero Values">
74+
<RpcMethodBarChartCardUI
75+
rawData={generateTimeSeriesData(30, commonMethods, true)}
76+
/>
77+
</BadgeContainer>
78+
79+
<BadgeContainer label="Single Method">
80+
<RpcMethodBarChartCardUI
81+
rawData={generateTimeSeriesData(30, ["eth_call"])}
82+
/>
83+
</BadgeContainer>
84+
</div>
85+
);
86+
}

0 commit comments

Comments
 (0)