From 35b44f593847a65d09ce0bc40c57452dd0a5cf44 Mon Sep 17 00:00:00 2001 From: Jonas Daniels Date: Mon, 24 Feb 2025 15:53:07 -0800 Subject: [PATCH] migrate analytics params from clientId/accountId to teamId/projectId --- apps/dashboard/src/@/api/analytics.ts | 181 +++++++++++++++--- .../[team_slug]/(team)/~/analytics/page.tsx | 24 +-- .../components/EcosystemAnalyticsPage.tsx | 2 +- .../components/Transactions/index.tsx | 6 +- .../in-app-wallets/_components/header.tsx | 13 +- .../connect/in-app-wallets/layout.tsx | 2 +- .../connect/in-app-wallets/page.tsx | 3 +- .../team/[team_slug]/[project_slug]/page.tsx | 14 +- .../embedded-wallets/Analytics/index.tsx | 21 +- .../src/data/analytics/fetch-analytics.ts | 50 ----- .../src/data/analytics/wallets/ecosystem.ts | 62 ------ .../src/data/analytics/wallets/in-app.ts | 41 ---- apps/dashboard/src/types/analytics.ts | 12 +- 13 files changed, 201 insertions(+), 230 deletions(-) delete mode 100644 apps/dashboard/src/data/analytics/fetch-analytics.ts delete mode 100644 apps/dashboard/src/data/analytics/wallets/ecosystem.ts delete mode 100644 apps/dashboard/src/data/analytics/wallets/in-app.ts diff --git a/apps/dashboard/src/@/api/analytics.ts b/apps/dashboard/src/@/api/analytics.ts index a1a0d151aa5..b0d963976ce 100644 --- a/apps/dashboard/src/@/api/analytics.ts +++ b/apps/dashboard/src/@/api/analytics.ts @@ -1,7 +1,8 @@ -import { fetchAnalytics } from "data/analytics/fetch-analytics"; +import "server-only"; + import type { AnalyticsQueryParams, - AnalyticsQueryParamsV2, + EcosystemWalletStats, InAppWalletStats, RpcMethodStats, TransactionStats, @@ -9,26 +10,63 @@ import type { WalletStats, WalletUserStats, } from "types/analytics"; +import { getAuthToken } from "../../app/api/lib/getAuthToken"; import { getChains } from "./chain"; -function buildSearchParams( - params: AnalyticsQueryParams | AnalyticsQueryParamsV2, -): URLSearchParams { - const searchParams = new URLSearchParams(); +async function fetchAnalytics( + input: string | URL, + init?: RequestInit, +): Promise { + const token = await getAuthToken(); - // v1 params - if ("clientId" in params && params.clientId) { - searchParams.append("clientId", params.clientId); + if (!token) { + throw new Error("You are not authorized to perform this action"); } - if ("accountId" in params && params.accountId) { - searchParams.append("accountId", params.accountId); + + const [pathname, searchParams] = input.toString().split("?"); + if (!pathname) { + throw new Error("Invalid input, no pathname provided"); } - // v2 params - if ("teamId" in params && params.teamId) { - searchParams.append("teamId", params.teamId); + // create a new URL object for the analytics server + const ANALYTICS_SERVICE_URL = new URL( + process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com", + ); + ANALYTICS_SERVICE_URL.pathname = pathname; + for (const param of searchParams?.split("&") || []) { + const [key, value] = param.split("="); + if (!key || !value) { + throw new Error("Invalid input, no key or value provided"); + } + ANALYTICS_SERVICE_URL.searchParams.append( + decodeURIComponent(key), + decodeURIComponent(value), + ); } - if ("projectId" in params && params.projectId) { + // client id DEBUG OVERRIDE + // ANALYTICS_SERVICE_URL.searchParams.delete("clientId"); + // ANALYTICS_SERVICE_URL.searchParams.delete("accountId"); + // ANALYTICS_SERVICE_URL.searchParams.append( + // "clientId", + // "...", + // ); + + return fetch(ANALYTICS_SERVICE_URL, { + ...init, + headers: { + "content-type": "application/json", + ...init?.headers, + authorization: `Bearer ${token}`, + }, + }); +} + +function buildSearchParams(params: AnalyticsQueryParams): URLSearchParams { + const searchParams = new URLSearchParams(); + + searchParams.append("teamId", params.teamId); + + if (params.projectId) { searchParams.append("projectId", params.projectId); } @@ -46,7 +84,7 @@ function buildSearchParams( } export async function getWalletConnections( - params: AnalyticsQueryParamsV2, + params: AnalyticsQueryParams, ): Promise { const searchParams = buildSearchParams(params); const res = await fetchAnalytics( @@ -70,7 +108,7 @@ export async function getWalletConnections( } export async function getInAppWalletUsage( - params: AnalyticsQueryParamsV2, + params: AnalyticsQueryParams, ): Promise { const searchParams = buildSearchParams(params); const res = await fetchAnalytics( @@ -94,7 +132,7 @@ export async function getInAppWalletUsage( } export async function getUserOpUsage( - params: AnalyticsQueryParamsV2, + params: AnalyticsQueryParams, ): Promise { const searchParams = buildSearchParams(params); const res = await fetchAnalytics( @@ -118,7 +156,7 @@ export async function getUserOpUsage( } export async function getAggregateUserOpUsage( - params: Omit, + params: Omit, ): Promise { const [userOpStats, chains] = await Promise.all([ getUserOpUsage({ ...params, period: "all" }), @@ -152,7 +190,7 @@ export async function getAggregateUserOpUsage( } export async function getClientTransactions( - params: AnalyticsQueryParamsV2, + params: AnalyticsQueryParams, ): Promise { const searchParams = buildSearchParams(params); const res = await fetchAnalytics( @@ -180,7 +218,7 @@ export async function getRpcMethodUsage( ): Promise { const searchParams = buildSearchParams(params); const res = await fetchAnalytics( - `v1/rpc/evm-methods?${searchParams.toString()}`, + `v2/rpc/evm-methods?${searchParams.toString()}`, { method: "GET", headers: { "Content-Type": "application/json" }, @@ -201,7 +239,7 @@ export async function getWalletUsers( ): Promise { const searchParams = buildSearchParams(params); const res = await fetchAnalytics( - `v1/wallets/users?${searchParams.toString()}`, + `v2/wallet-connects/users?${searchParams.toString()}`, { method: "GET", headers: { "Content-Type": "application/json" }, @@ -220,23 +258,108 @@ export async function getWalletUsers( return json.data as WalletUserStats[]; } +type ActiveStatus = { + bundler: boolean; + storage: boolean; + rpc: boolean; + nebula: boolean; + sdk: boolean; + insight: boolean; + pay: boolean; + inAppWallet: boolean; + ecosystemWallet: boolean; +}; + export async function isProjectActive( params: AnalyticsQueryParams, -): Promise { +): Promise { const searchParams = buildSearchParams(params); - const res = await fetchAnalytics(`v1/active?${searchParams.toString()}`, { - method: "GET", - headers: { "Content-Type": "application/json" }, - }); + const res = await fetchAnalytics( + `v2/active-usage?${searchParams.toString()}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + }, + ); if (res?.status !== 200) { const reason = await res?.text(); console.error( `Failed to fetch project active status: ${res?.status} - ${res.statusText} - ${reason}`, ); - return false; + return { + bundler: false, + storage: false, + rpc: false, + nebula: false, + sdk: false, + insight: false, + pay: false, + inAppWallet: false, + ecosystemWallet: false, + } as ActiveStatus; } const json = await res.json(); - return json.data.isActive as boolean; + return json.data as ActiveStatus; +} + +export async function getEcosystemWalletUsage(args: { + teamId: string; + ecosystemSlug: string; + ecosystemPartnerId?: string; + projectId?: string; + from?: Date; + to?: Date; + period?: "day" | "week" | "month" | "year" | "all"; +}) { + const { + ecosystemSlug, + ecosystemPartnerId, + teamId, + projectId, + from, + to, + period, + } = args; + + const searchParams = new URLSearchParams(); + // required params + searchParams.append("ecosystemSlug", ecosystemSlug); + searchParams.append("teamId", teamId); + + // optional params + if (ecosystemPartnerId) { + searchParams.append("ecosystemPartnerId", ecosystemPartnerId); + } + if (projectId) { + searchParams.append("projectId", projectId); + } + if (from) { + searchParams.append("from", from.toISOString()); + } + if (to) { + searchParams.append("to", to.toISOString()); + } + if (period) { + searchParams.append("period", period); + } + const res = await fetchAnalytics( + `v2/wallets/connects/${ecosystemSlug}?${searchParams.toString()}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }, + ); + + if (res?.status !== 200) { + console.error("Failed to fetch ecosystem wallet stats"); + return null; + } + + const json = await res.json(); + + return json.data as EcosystemWalletStats[]; } diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/analytics/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/analytics/page.tsx index bcc86d74ea1..ec277af0edc 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/analytics/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/analytics/page.tsx @@ -5,21 +5,18 @@ import { getWalletConnections, getWalletUsers, } from "@/api/analytics"; -import { redirect } from "next/navigation"; - -import type { - InAppWalletStats, - WalletStats, - WalletUserStats, -} from "types/analytics"; - import { type DurationId, type Range, getLastNDaysRange, } from "components/analytics/date-range-selector"; - +import { redirect } from "next/navigation"; import { type WalletId, getWalletInfo } from "thirdweb/wallets"; +import type { + InAppWalletStats, + WalletStats, + WalletUserStats, +} from "types/analytics"; import { AnalyticsHeader } from "../../../../components/Analytics/AnalyticsHeader"; import { CombinedBarChartCard } from "../../../../components/Analytics/CombinedBarChartCard"; import { EmptyState } from "../../../../components/Analytics/EmptyState"; @@ -27,8 +24,6 @@ import { PieChartCard } from "../../../../components/Analytics/PieChartCard"; import { getTeamBySlug } from "@/api/team"; import { GenericLoadingPage } from "@/components/blocks/skeletons/GenericLoadingPage"; -import type { Account } from "@3rdweb-sdk/react/hooks/useApi"; -import { getValidAccount } from "app/account/settings/getAccount"; import { EmptyStateCard } from "app/team/components/Analytics/EmptyStateCard"; import { Suspense } from "react"; import { TotalSponsoredChartCardUI } from "../../_components/TotalSponsoredCard"; @@ -51,7 +46,6 @@ export default async function TeamOverviewPage(props: { props.searchParams, ]); - const account = await getValidAccount(`/team/${params.team_slug}`); const team = await getTeamBySlug(params.team_slug); if (!team) { @@ -75,7 +69,6 @@ export default async function TeamOverviewPage(props: { }> - +
diff --git a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx index aed5efadd88..d0ed85f435a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/[project_slug]/page.tsx @@ -70,7 +70,13 @@ export default async function ProjectOverviewPage(props: PageProps) { type: rangeType, }; - const isActive = await isProjectActive({ clientId: project.publishableKey }); + const activeStatus = await isProjectActive({ + teamId: project.teamId, + projectId: project.id, + }); + + // is any analytics data active? + const isActive = Object.values(activeStatus).some((v) => !!v); return (
@@ -127,7 +133,8 @@ async function ProjectAnalytics(props: { }), // Time series data for wallet users getWalletUsers({ - clientId: project.publishableKey, + teamId: project.teamId, + projectId: project.id, from: range.from, to: range.to, period: interval, @@ -227,7 +234,8 @@ async function ProjectAnalytics(props: { from={range.from} to={range.to} period={interval} - clientId={project.publishableKey} + teamId={project.teamId} + projectId={project.id} />
); diff --git a/apps/dashboard/src/components/embedded-wallets/Analytics/index.tsx b/apps/dashboard/src/components/embedded-wallets/Analytics/index.tsx index 04d74dc1826..23f825b70ea 100644 --- a/apps/dashboard/src/components/embedded-wallets/Analytics/index.tsx +++ b/apps/dashboard/src/components/embedded-wallets/Analytics/index.tsx @@ -2,21 +2,24 @@ import { type Range, getLastNDaysRange, } from "components/analytics/date-range-selector"; -import { getInAppWalletUsage } from "data/analytics/wallets/in-app"; +import { getInAppWalletUsage } from "../../../@/api/analytics"; import { RangeSelector } from "../../analytics/range-selector"; import { InAppWalletUsersChartCardUI } from "./InAppWalletUsersChartCard"; export async function InAppWalletAnalytics({ - clientId, + teamId, + projectId, interval, - range, -}: { clientId: string; interval: "day" | "week"; range?: Range }) { - if (!range) { - range = getLastNDaysRange("last-120"); - } - + range = getLastNDaysRange("last-120"), +}: { + teamId: string; + projectId: string; + interval: "day" | "week"; + range?: Range; +}) { const stats = await getInAppWalletUsage({ - clientId, + teamId, + projectId, from: range.from, to: range.to, period: interval, diff --git a/apps/dashboard/src/data/analytics/fetch-analytics.ts b/apps/dashboard/src/data/analytics/fetch-analytics.ts deleted file mode 100644 index fb0f6385aac..00000000000 --- a/apps/dashboard/src/data/analytics/fetch-analytics.ts +++ /dev/null @@ -1,50 +0,0 @@ -import "server-only"; -import { getAuthToken } from "../../app/api/lib/getAuthToken"; - -export async function fetchAnalytics( - input: string | URL, - init?: RequestInit, -): Promise { - const token = await getAuthToken(); - - if (!token) { - throw new Error("You are not authorized to perform this action"); - } - - const [pathname, searchParams] = input.toString().split("?"); - if (!pathname) { - throw new Error("Invalid input, no pathname provided"); - } - - // create a new URL object for the analytics server - const ANALYTICS_SERVICE_URL = new URL( - process.env.ANALYTICS_SERVICE_URL || "https://analytics.thirdweb.com", - ); - ANALYTICS_SERVICE_URL.pathname = pathname; - for (const param of searchParams?.split("&") || []) { - const [key, value] = param.split("="); - if (!key || !value) { - throw new Error("Invalid input, no key or value provided"); - } - ANALYTICS_SERVICE_URL.searchParams.append( - decodeURIComponent(key), - decodeURIComponent(value), - ); - } - // client id DEBUG OVERRIDE - // ANALYTICS_SERVICE_URL.searchParams.delete("clientId"); - // ANALYTICS_SERVICE_URL.searchParams.delete("accountId"); - // ANALYTICS_SERVICE_URL.searchParams.append( - // "clientId", - // "...", - // ); - - return fetch(ANALYTICS_SERVICE_URL, { - ...init, - headers: { - "content-type": "application/json", - ...init?.headers, - authorization: `Bearer ${token}`, - }, - }); -} diff --git a/apps/dashboard/src/data/analytics/wallets/ecosystem.ts b/apps/dashboard/src/data/analytics/wallets/ecosystem.ts deleted file mode 100644 index 5acd4b4076e..00000000000 --- a/apps/dashboard/src/data/analytics/wallets/ecosystem.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { EcosystemWalletStats } from "types/analytics"; -import { fetchAnalytics } from "../fetch-analytics"; - -export async function getEcosystemWalletUsage(args: { - teamId: string; - ecosystemSlug: string; - ecosystemPartnerId?: string; - projectId?: string; - from?: Date; - to?: Date; - period?: "day" | "week" | "month" | "year" | "all"; -}) { - const { - ecosystemSlug, - ecosystemPartnerId, - teamId, - projectId, - from, - to, - period, - } = args; - - const searchParams = new URLSearchParams(); - // required params - searchParams.append("ecosystemSlug", ecosystemSlug); - searchParams.append("teamId", teamId); - - // optional params - if (ecosystemPartnerId) { - searchParams.append("ecosystemPartnerId", ecosystemPartnerId); - } - if (projectId) { - searchParams.append("projectId", projectId); - } - if (from) { - searchParams.append("from", from.toISOString()); - } - if (to) { - searchParams.append("to", to.toISOString()); - } - if (period) { - searchParams.append("period", period); - } - const res = await fetchAnalytics( - `v2/wallets/connects/${ecosystemSlug}?${searchParams.toString()}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }, - ); - - if (res?.status !== 200) { - console.error("Failed to fetch ecosystem wallet stats"); - return null; - } - - const json = await res.json(); - - return json.data as EcosystemWalletStats[]; -} diff --git a/apps/dashboard/src/data/analytics/wallets/in-app.ts b/apps/dashboard/src/data/analytics/wallets/in-app.ts deleted file mode 100644 index a477f04219f..00000000000 --- a/apps/dashboard/src/data/analytics/wallets/in-app.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { InAppWalletStats } from "types/analytics"; -import { fetchAnalytics } from "../fetch-analytics"; - -export async function getInAppWalletUsage(args: { - clientId: string; - from?: Date; - to?: Date; - period?: "day" | "week" | "month" | "year" | "all"; -}) { - const { clientId, from, to, period } = args; - - const searchParams = new URLSearchParams(); - searchParams.append("clientId", clientId); - if (from) { - searchParams.append("from", from.toISOString()); - } - if (to) { - searchParams.append("to", to.toISOString()); - } - if (period) { - searchParams.append("period", period); - } - const res = await fetchAnalytics( - `v1/wallets/in-app?${searchParams.toString()}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }, - ); - - if (res?.status !== 200) { - console.error("Failed to fetch in-app wallet stats"); - return null; - } - - const json = await res.json(); - - return json.data as InAppWalletStats[]; -} diff --git a/apps/dashboard/src/types/analytics.ts b/apps/dashboard/src/types/analytics.ts index 823ede856e5..b539bab295c 100644 --- a/apps/dashboard/src/types/analytics.ts +++ b/apps/dashboard/src/types/analytics.ts @@ -42,17 +42,9 @@ export interface RpcMethodStats { } export interface AnalyticsQueryParams { - clientId?: string; - accountId?: string; + teamId: string; + projectId?: string; from?: Date; to?: Date; period?: "day" | "week" | "month" | "year" | "all"; } - -export type AnalyticsQueryParamsV2 = Omit< - AnalyticsQueryParams, - "clientId" | "accountId" -> & { - teamId: string; - projectId?: string; -};