Skip to content

Commit fa99acf

Browse files
authored
migrate user op analytics to server components (#6328)
1 parent 15adec4 commit fa99acf

File tree

17 files changed

+374
-298
lines changed

17 files changed

+374
-298
lines changed

apps/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"next-plausible": "^3.12.4",
7373
"next-themes": "^0.4.4",
7474
"nextjs-toploader": "^1.6.12",
75+
"nuqs": "^2.4.0",
7576
"p-limit": "^6.2.0",
7677
"papaparse": "^5.5.2",
7778
"pluralize": "^8.0.0",

apps/dashboard/src/@/actions/proxies.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,6 @@ async function proxy<T extends object>(
7171
};
7272
}
7373

74-
export async function analyticsServerProxy<T extends object = object>(
75-
params: ProxyActionParams,
76-
) {
77-
return proxy<T>(
78-
process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com",
79-
params,
80-
);
81-
}
82-
8374
export async function apiServerProxy<T extends object = object>(
8475
params: ProxyActionParams,
8576
) {

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

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
WalletStats,
1010
WalletUserStats,
1111
} from "types/analytics";
12+
import { getChains } from "./chain";
1213

1314
function buildSearchParams(
1415
params: AnalyticsQueryParams | AnalyticsQueryParamsV2,
@@ -93,13 +94,16 @@ export async function getInAppWalletUsage(
9394
}
9495

9596
export async function getUserOpUsage(
96-
params: AnalyticsQueryParams,
97+
params: AnalyticsQueryParamsV2,
9798
): Promise<UserOpStats[]> {
9899
const searchParams = buildSearchParams(params);
99-
const res = await fetchAnalytics(`v1/user-ops?${searchParams.toString()}`, {
100-
method: "GET",
101-
headers: { "Content-Type": "application/json" },
102-
});
100+
const res = await fetchAnalytics(
101+
`v2/bundler/usage?${searchParams.toString()}`,
102+
{
103+
method: "GET",
104+
headers: { "Content-Type": "application/json" },
105+
},
106+
);
103107

104108
if (res?.status !== 200) {
105109
const reason = await res?.text();
@@ -113,6 +117,40 @@ export async function getUserOpUsage(
113117
return json.data as UserOpStats[];
114118
}
115119

120+
export async function getAggregateUserOpUsage(
121+
params: Omit<AnalyticsQueryParamsV2, "period">,
122+
): Promise<UserOpStats> {
123+
const [userOpStats, chains] = await Promise.all([
124+
getUserOpUsage({ ...params, period: "all" }),
125+
getChains(),
126+
]);
127+
// Aggregate stats across wallet types
128+
return userOpStats.reduce(
129+
(acc, curr) => {
130+
// Skip testnets from the aggregated stats
131+
if (curr.chainId) {
132+
const chain = chains.data.find(
133+
(c) => c.chainId.toString() === curr.chainId,
134+
);
135+
if (chain?.testnet) {
136+
return acc;
137+
}
138+
}
139+
140+
acc.successful += curr.successful;
141+
acc.failed += curr.failed;
142+
acc.sponsoredUsd += curr.sponsoredUsd;
143+
return acc;
144+
},
145+
{
146+
date: (params.from || new Date()).toISOString(),
147+
successful: 0,
148+
failed: 0,
149+
sponsoredUsd: 0,
150+
},
151+
);
152+
}
153+
116154
export async function getClientTransactions(
117155
params: AnalyticsQueryParamsV2,
118156
): Promise<TransactionStats[]> {

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import "server-only";
2+
import type { ChainMetadata } from "thirdweb/chains";
3+
import type { ChainService } from "../../app/(dashboard)/(chain)/types/chain";
24
import { API_SERVER_URL, THIRDWEB_API_SECRET } from "../constants/env";
35

46
export async function getGasSponsoredChains() {
@@ -32,3 +34,21 @@ export async function getGasSponsoredChains() {
3234
return [];
3335
}
3436
}
37+
38+
export function getChains() {
39+
return fetch(
40+
`${API_SERVER_URL}/v1/chains`,
41+
// revalidate every 60 minutes
42+
{ next: { revalidate: 60 * 60 } },
43+
).then((res) => res.json()) as Promise<{ data: ChainMetadata[] }>;
44+
}
45+
46+
export function getChainServices() {
47+
return fetch(
48+
`${API_SERVER_URL}/v1/chains/services`,
49+
// revalidate every 60 minutes
50+
{ next: { revalidate: 60 * 60 } },
51+
).then((res) => res.json()) as Promise<{
52+
data: Record<number, Array<ChainService>>;
53+
}>;
54+
}

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

Lines changed: 1 addition & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { analyticsServerProxy, apiServerProxy } from "@/actions/proxies";
1+
import { apiServerProxy } from "@/actions/proxies";
22
import type { Project } from "@/api/projects";
33
import type { Team } from "@/api/team";
44
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
5-
import { useAllChainsData } from "hooks/chains/allChains";
65
import { useActiveAccount } from "thirdweb/react";
7-
import type { UserOpStats } from "types/analytics";
86
import { accountKeys, authorizedWallets } from "../cache-keys";
97

108
// FIXME: We keep repeating types, API server should provide them
@@ -142,132 +140,6 @@ export function useAccountCredits() {
142140
});
143141
}
144142

145-
type UserOpUsageQueryResult = (UserOpStats & { chainId?: string })[];
146-
147-
async function getUserOpUsage(args: {
148-
clientId: string;
149-
from?: Date;
150-
to?: Date;
151-
period?: "day" | "week" | "month" | "year" | "all";
152-
}) {
153-
const { clientId, from, to, period } = args;
154-
155-
const searchParams: Record<string, string> = {
156-
clientId,
157-
};
158-
159-
if (from) {
160-
searchParams.from = from.toISOString();
161-
}
162-
if (to) {
163-
searchParams.to = to.toISOString();
164-
}
165-
if (period) {
166-
searchParams.period = period;
167-
}
168-
169-
const res = await analyticsServerProxy<{ data: UserOpUsageQueryResult }>({
170-
pathname: "/v1/user-ops",
171-
method: "GET",
172-
searchParams: searchParams,
173-
headers: {
174-
"Content-Type": "application/json",
175-
},
176-
});
177-
178-
if (!res.ok) {
179-
throw new Error(res.error);
180-
}
181-
182-
const json = res.data;
183-
184-
return json.data;
185-
}
186-
187-
// TODO - remove this hook, fetch this on server
188-
export function useUserOpUsageAggregate(args: {
189-
clientId: string;
190-
from?: Date;
191-
to?: Date;
192-
}) {
193-
const { clientId, from, to } = args;
194-
const address = useActiveAccount()?.address;
195-
const chainStore = useAllChainsData();
196-
197-
return useQuery<UserOpStats>({
198-
queryKey: accountKeys.userOpStats(
199-
address || "",
200-
clientId,
201-
from?.toISOString() || "",
202-
to?.toISOString() || "",
203-
"all",
204-
),
205-
queryFn: async () => {
206-
const userOpStats = await getUserOpUsage({
207-
clientId,
208-
from,
209-
to,
210-
period: "all",
211-
});
212-
213-
// Aggregate stats across wallet types
214-
return userOpStats.reduce(
215-
(acc, curr) => {
216-
// Skip testnets from the aggregated stats
217-
if (curr.chainId) {
218-
const chain = chainStore.idToChain.get(Number(curr.chainId));
219-
if (chain?.testnet) {
220-
return acc;
221-
}
222-
}
223-
224-
acc.successful += curr.successful;
225-
acc.failed += curr.failed;
226-
acc.sponsoredUsd += curr.sponsoredUsd;
227-
return acc;
228-
},
229-
{
230-
date: (from || new Date()).toISOString(),
231-
successful: 0,
232-
failed: 0,
233-
sponsoredUsd: 0,
234-
},
235-
);
236-
},
237-
enabled: !!clientId && !!address,
238-
});
239-
}
240-
241-
// TODO - remove this hook, fetch this on server
242-
export function useUserOpUsagePeriod(args: {
243-
clientId: string;
244-
from?: Date;
245-
to?: Date;
246-
period: "day" | "week" | "month" | "year";
247-
}) {
248-
const { clientId, from, to, period } = args;
249-
const address = useActiveAccount()?.address;
250-
251-
return useQuery({
252-
queryKey: accountKeys.userOpStats(
253-
address || "",
254-
clientId as string,
255-
from?.toISOString() || "",
256-
to?.toISOString() || "",
257-
period,
258-
),
259-
queryFn: async () => {
260-
return getUserOpUsage({
261-
clientId,
262-
from,
263-
to,
264-
period,
265-
});
266-
},
267-
enabled: !!clientId && !!address,
268-
});
269-
}
270-
271143
export function useUpdateAccount() {
272144
const queryClient = useQueryClient();
273145
const address = useActiveAccount()?.address;

apps/dashboard/src/app/(dashboard)/(chain)/chainlist/components/server/chain-table.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type {
1212
ChainMetadataWithServices,
1313
ChainSupportedService,
1414
} from "../../../types/chain";
15-
import { getChains } from "../../../utils";
15+
import { getChainsWithServices } from "../../../utils";
1616
import { ChainlistPagination } from "../client/pagination";
1717
import { ChainListCard } from "../server/chainlist-card";
1818
import { ChainListRow } from "../server/chainlist-row";
@@ -35,7 +35,7 @@ const DEFAULT_PAGE_SIZE = 24;
3535
const DEFAULT_PAGE = 1;
3636

3737
async function getChainsToRender(params: SearchParams) {
38-
const chains = await getChains();
38+
const chains = await getChainsWithServices();
3939

4040
// sort the chains
4141
const sortedChains = chains.sort((a, b) => {

apps/dashboard/src/app/(dashboard)/(chain)/utils.ts

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -72,29 +72,19 @@ import zytronBanner from "./temp-assets/zytronBanner.png";
7272
import zytronCTA from "./temp-assets/zytronCTA.jpg";
7373
// END TEMPORARY
7474

75+
import {
76+
getChainServices,
77+
getChains,
78+
getGasSponsoredChains,
79+
} from "@/api/chain";
7580
import { API_SERVER_URL } from "@/constants/env";
7681
import type { ChainMetadata } from "thirdweb/chains";
77-
import { getGasSponsoredChains } from "../../../@/api/chain";
78-
import type {
79-
ChainMetadataWithServices,
80-
ChainService,
81-
ChainServices,
82-
} from "./types/chain";
82+
import type { ChainMetadataWithServices, ChainServices } from "./types/chain";
8383

84-
export async function getChains() {
84+
export async function getChainsWithServices() {
8585
const [chains, chainServices] = await Promise.all([
86-
fetch(
87-
`${API_SERVER_URL}/v1/chains`,
88-
// revalidate every 60 minutes
89-
{ next: { revalidate: 60 * 60 } },
90-
).then((res) => res.json()) as Promise<{ data: ChainMetadata[] }>,
91-
fetch(
92-
`${API_SERVER_URL}/v1/chains/services`,
93-
// revalidate every 60 minutes
94-
{ next: { revalidate: 60 * 60 } },
95-
).then((res) => res.json()) as Promise<{
96-
data: Record<number, Array<ChainService>>;
97-
}>,
86+
getChains(),
87+
getChainServices(),
9888
]);
9989

10090
if (!chains.data.length) {

apps/dashboard/src/app/providers.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
44
import { ThemeProvider, useTheme } from "next-themes";
5+
import { NuqsAdapter } from "nuqs/adapters/next/app";
56
import { useEffect, useMemo } from "react";
67
import { Toaster } from "sonner";
78
import {
@@ -20,25 +21,27 @@ const queryClient = new QueryClient();
2021

2122
export function AppRouterProviders(props: { children: React.ReactNode }) {
2223
return (
23-
<QueryClientProvider client={queryClient}>
24-
<SyncChainStores />
25-
<ThirdwebProvider>
26-
<SyncChainDefinitionsToConnectionManager />
27-
<TWAutoConnect />
28-
<PosthogIdentifier />
29-
<ThemeProvider
30-
attribute="class"
31-
disableTransitionOnChange
32-
enableSystem={false}
33-
defaultTheme="dark"
34-
>
35-
<ToasterSetup />
36-
<SanctionedAddressesChecker>
37-
{props.children}
38-
</SanctionedAddressesChecker>
39-
</ThemeProvider>
40-
</ThirdwebProvider>
41-
</QueryClientProvider>
24+
<NuqsAdapter>
25+
<QueryClientProvider client={queryClient}>
26+
<SyncChainStores />
27+
<ThirdwebProvider>
28+
<SyncChainDefinitionsToConnectionManager />
29+
<TWAutoConnect />
30+
<PosthogIdentifier />
31+
<ThemeProvider
32+
attribute="class"
33+
disableTransitionOnChange
34+
enableSystem={false}
35+
defaultTheme="dark"
36+
>
37+
<ToasterSetup />
38+
<SanctionedAddressesChecker>
39+
{props.children}
40+
</SanctionedAddressesChecker>
41+
</ThemeProvider>
42+
</ThirdwebProvider>
43+
</QueryClientProvider>
44+
</NuqsAdapter>
4245
);
4346
}
4447

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,13 @@ async function OverviewPageContent(props: {
127127
}),
128128
// User operations usage
129129
getUserOpUsage({
130-
accountId: account.id,
130+
teamId,
131131
from: range.from,
132132
to: range.to,
133133
period: interval,
134134
}),
135135
getUserOpUsage({
136-
accountId: account.id,
136+
teamId,
137137
from: range.from,
138138
to: range.to,
139139
period: "all",

0 commit comments

Comments
 (0)