diff --git a/apps/dashboard/src/@/api/analytics.ts b/apps/dashboard/src/@/api/analytics.ts index 90daeebea72..65833bfc866 100644 --- a/apps/dashboard/src/@/api/analytics.ts +++ b/apps/dashboard/src/@/api/analytics.ts @@ -1,6 +1,8 @@ import "server-only"; +import { unstable_cache } from "next/cache"; import { ANALYTICS_SERVICE_URL } from "@/constants/server-envs"; +import { normalizeTime } from "@/lib/time"; import type { AnalyticsQueryParams, EcosystemWalletStats, @@ -16,7 +18,6 @@ import type { WebhookRequestStats, WebhookSummaryStats, } from "@/types/analytics"; -import { getAuthToken } from "./auth-token"; import { getChains } from "./chain"; export interface InsightChainStats { @@ -54,16 +55,20 @@ export interface RpcUsageTypeStats { count: number; } -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"); - } +function normalizedParams(params: T): T { + return { + ...params, + from: params.from ? normalizeTime(params.from) : undefined, + to: params.to ? normalizeTime(params.to) : undefined, + }; +} - const [pathname, searchParams] = input.toString().split("?"); +async function fetchAnalytics(params: { + authToken: string; + url: string | URL; + init?: RequestInit; +}): Promise { + const [pathname, searchParams] = params.url.toString().split("?"); if (!pathname) { throw new Error("Invalid input, no pathname provided"); } @@ -86,10 +91,10 @@ async function fetchAnalytics( } return fetch(analyticsServiceUrl, { - ...init, + ...params.init, headers: { - Authorization: `Bearer ${token}`, - ...init?.headers, + Authorization: `Bearer ${params.authToken}`, + ...params.init?.headers, }, }); } @@ -119,193 +124,316 @@ function buildSearchParams(params: AnalyticsQueryParams): URLSearchParams { return searchParams; } -export async function getWalletConnections( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/sdk/wallet-connects?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getWalletConnections = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + const res = await fetchAnalytics({ + authToken, + url: `v2/sdk/wallet-connects?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch wallet connections, ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch wallet connections, ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } + const json = await res.json(); + return json.data as WalletStats[]; + }, + ["getWalletConnections"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as WalletStats[]; +export function getWalletConnections( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getWalletConnections(normalizedParams(params), authToken); } -export async function getInAppWalletUsage( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/wallet/connects?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getInAppWalletUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + const res = await fetchAnalytics({ + authToken, + url: `v2/wallet/connects?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch in-app wallet usage, ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch in-app wallet usage, ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } + const json = await res.json(); + return json.data as InAppWalletStats[]; + }, + ["getInAppWalletUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as InAppWalletStats[]; +export function getInAppWalletUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getInAppWalletUsage(normalizedParams(params), authToken); } -export async function getUserOpUsage( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/bundler/usage?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getUserOpUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + const res = await fetchAnalytics({ + authToken, + url: `v2/bundler/usage?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch user ops usage: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch user ops usage: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } + const json = await res.json(); + return json.data as UserOpStats[]; + }, + ["getUserOpUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as UserOpStats[]; +export function getUserOpUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getUserOpUsage(normalizedParams(params), authToken); } -export async function getAggregateUserOpUsage( - params: Omit, -): Promise { - const [userOpStats, chains] = await Promise.all([ - getUserOpUsage({ ...params, period: "all" }), - getChains(), - ]); - // Aggregate stats across wallet types - return userOpStats.reduce( - (acc, curr) => { - // Skip testnets from the aggregated stats - if (curr.chainId) { - const chain = chains.data.find( - (c) => c.chainId.toString() === curr.chainId, - ); - if (chain?.testnet) { - return acc; +const cached_getAggregateUserOpUsage = unstable_cache( + async ( + params: Omit, + authToken: string, + ): Promise => { + const [userOpStats, chains] = await Promise.all([ + getUserOpUsage({ ...params, period: "all" }, authToken), + getChains(), + ]); + + // Aggregate stats across wallet types + return userOpStats.reduce( + (acc, curr) => { + // Skip testnets from the aggregated stats + if (curr.chainId) { + const chain = chains.data.find( + (c) => c.chainId.toString() === curr.chainId, + ); + if (chain?.testnet) { + return acc; + } } - } - acc.successful += curr.successful; - acc.failed += curr.failed; - acc.sponsoredUsd += curr.sponsoredUsd; - return acc; - }, - { - date: (params.from || new Date()).toISOString(), - failed: 0, - sponsoredUsd: 0, - successful: 0, - }, - ); + acc.successful += curr.successful; + acc.failed += curr.failed; + acc.sponsoredUsd += curr.sponsoredUsd; + return acc; + }, + { + date: (params.from || new Date()).toISOString(), + failed: 0, + sponsoredUsd: 0, + successful: 0, + }, + ); + }, + ["getAggregateUserOpUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getAggregateUserOpUsage( + params: Omit, + authToken: string, +) { + return cached_getAggregateUserOpUsage(normalizedParams(params), authToken); } -export async function getClientTransactions( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/sdk/contract-transactions?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getClientTransactions = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/sdk/contract-transactions?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch client transactions stats: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch client transactions stats: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } + const json = await res.json(); + return json.data as TransactionStats[]; + }, + ["getClientTransactions"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as TransactionStats[]; +export function getClientTransactions( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getClientTransactions(normalizedParams(params), authToken); } -export async function getRpcMethodUsage( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/rpc/evm-methods?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getRpcMethodUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/rpc/evm-methods?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + console.error("Failed to fetch RPC method usage"); + return []; + } - if (res?.status !== 200) { - console.error("Failed to fetch RPC method usage"); - return []; - } + const json = await res.json(); + return json.data as RpcMethodStats[]; + }, + ["getRpcMethodUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as RpcMethodStats[]; +export function getRpcMethodUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getRpcMethodUsage(normalizedParams(params), authToken); } -export async function getRpcUsageByType( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/rpc/usage-types?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getRpcUsageByType = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/rpc/usage-types?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + console.error("Failed to fetch RPC usage"); + return []; + } - if (res?.status !== 200) { - console.error("Failed to fetch RPC usage"); - return []; - } + const json = await res.json(); + return json.data as RpcUsageTypeStats[]; + }, + ["getRpcUsageByType"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as RpcUsageTypeStats[]; +export function getRpcUsageByType( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getRpcUsageByType(normalizedParams(params), authToken); } -export async function getWalletUsers( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/sdk/wallet-connects/users?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getWalletUsers = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + const res = await fetchAnalytics({ + authToken, + url: `v2/sdk/wallet-connects/users?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch wallet user stats: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch wallet user stats: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } + const json = await res.json(); + return json.data as WalletUserStats[]; + }, + ["getWalletUsers"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as WalletUserStats[]; +export function getWalletUsers( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getWalletUsers(normalizedParams(params), authToken); } type ActiveStatus = { @@ -320,41 +448,49 @@ type ActiveStatus = { ecosystemWallet: boolean; }; -export async function isProjectActive(params: { - teamId: string; - projectId: string; -}): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/active-usage?${searchParams.toString()}`, - { - method: "GET", - }, - ); - - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch project active status: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return { - bundler: false, - ecosystemWallet: false, - inAppWallet: false, - insight: false, - nebula: false, - pay: false, - rpc: false, - sdk: false, - storage: false, - } as ActiveStatus; - } +export const isProjectActive = unstable_cache( + async (params: { + teamId: string; + projectId: string; + authToken: string; + }): Promise => { + const searchParams = buildSearchParams(params); + const res = await fetchAnalytics({ + authToken: params.authToken, + url: `v2/active-usage?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch project active status: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return { + bundler: false, + ecosystemWallet: false, + inAppWallet: false, + insight: false, + nebula: false, + pay: false, + rpc: false, + sdk: false, + storage: false, + } as ActiveStatus; + } - const json = await res.json(); - return json.data as ActiveStatus; -} + const json = await res.json(); + return json.data as ActiveStatus; + }, + ["isProjectActive"], + { + revalidate: 60 * 60, // 1 hour + }, +); -export async function getEcosystemWalletUsage(args: { +type EcosystemWalletUsageParams = { teamId: string; ecosystemSlug: string; ecosystemPartnerId?: string; @@ -362,267 +498,458 @@ export async function getEcosystemWalletUsage(args: { 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/wallet/connects?${searchParams.toString()}`, - { - method: "GET", - }, - ); +}; - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch ecosystem wallet stats: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return null; - } +const cached_getEcosystemWalletUsage = unstable_cache( + async (args: EcosystemWalletUsageParams, authToken: string) => { + 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 json = await res.json(); + const res = await fetchAnalytics({ + authToken: authToken, + url: `v2/wallet/connects?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch ecosystem wallet stats: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return null; + } - return json.data as EcosystemWalletStats[]; + const json = await res.json(); + + return json.data as EcosystemWalletStats[]; + }, + ["getEcosystemWalletUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getEcosystemWalletUsage( + params: EcosystemWalletUsageParams, + authToken: string, +) { + return cached_getEcosystemWalletUsage(normalizedParams(params), authToken); } -export async function getUniversalBridgeUsage(args: { +type UniversalBridgeUsageParams = { teamId: string; projectId?: string; from?: Date; to?: Date; period?: "day" | "week" | "month" | "year" | "all"; -}) { - const searchParams = buildSearchParams(args); - const res = await fetchAnalytics(`v2/universal?${searchParams.toString()}`, { - method: "GET", - }); +}; - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch universal bridge stats: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } +const cached_getUniversalBridgeUsage = unstable_cache( + async (args: UniversalBridgeUsageParams, authToken: string) => { + const searchParams = buildSearchParams(args); + + const res = await fetchAnalytics({ + authToken, + url: `v2/universal?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch universal bridge stats: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - const json = await res.json(); - return json.data as UniversalBridgeStats[]; + const json = await res.json(); + return json.data as UniversalBridgeStats[]; + }, + ["getUniversalBridgeUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getUniversalBridgeUsage( + params: UniversalBridgeUsageParams, + authToken: string, +) { + return cached_getUniversalBridgeUsage(normalizedParams(params), authToken); } -export async function getUniversalBridgeWalletUsage(args: { +type UniversalBridgeWalletUsageParams = { teamId: string; projectId: string; from?: Date; to?: Date; period?: "day" | "week" | "month" | "year" | "all"; -}) { - const searchParams = buildSearchParams(args); - const res = await fetchAnalytics( - `v2/universal/wallets?${searchParams.toString()}`, - { - method: "GET", - }, - ); +}; - if (res?.status !== 200) { - const reason = await res?.text(); - console.error( - `Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`, - ); - return []; - } +const cached_getUniversalBridgeWalletUsage = unstable_cache( + async (args: UniversalBridgeWalletUsageParams, authToken: string) => { + const searchParams = buildSearchParams(args); + + const res = await fetchAnalytics({ + authToken, + url: `v2/universal/wallets?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + console.error( + `Failed to fetch universal bridge wallet stats: ${res?.status} - ${res.statusText} - ${reason}`, + ); + return []; + } - const json = await res.json(); - return json.data as UniversalBridgeWalletStats[]; + const json = await res.json(); + return json.data as UniversalBridgeWalletStats[]; + }, + ["getUniversalBridgeWalletUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getUniversalBridgeWalletUsage( + params: UniversalBridgeWalletUsageParams, + authToken: string, +) { + return cached_getUniversalBridgeWalletUsage( + normalizedParams(params), + authToken, + ); } -export async function getEngineCloudMethodUsage( - params: AnalyticsQueryParams, -): Promise { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/engine-cloud/requests?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getEngineCloudMethodUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise => { + const searchParams = buildSearchParams(params); + const res = await fetchAnalytics({ + authToken, + url: `v2/engine-cloud/requests?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + console.error("Failed to fetch Engine Cloud method usage"); + return []; + } - if (res?.status !== 200) { - console.error("Failed to fetch Engine Cloud method usage"); - return []; - } + const json = await res.json(); + return json.data as EngineCloudStats[]; + }, + ["getEngineCloudMethodUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return json.data as EngineCloudStats[]; +export function getEngineCloudMethodUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getEngineCloudMethodUsage(normalizedParams(params), authToken); } -export async function getWebhookSummary( - params: AnalyticsQueryParams & { webhookId: string }, -): Promise<{ data: WebhookSummaryStats[] } | { error: string }> { - const searchParams = buildSearchParams(params); - searchParams.append("webhookId", params.webhookId); +const cached_getWebhookSummary = unstable_cache( + async ( + params: AnalyticsQueryParams & { webhookId: string }, + authToken: string, + ): Promise<{ data: WebhookSummaryStats[] } | { error: string }> => { + const searchParams = buildSearchParams(params); + searchParams.append("webhookId", params.webhookId); - const res = await fetchAnalytics( - `v2/webhook/summary?${searchParams.toString()}`, - ); - if (!res.ok) { - const reason = await res.text(); - return { error: reason }; - } + const res = await fetchAnalytics({ + authToken, + url: `v2/webhook/summary?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + if (!res.ok) { + const reason = await res.text(); + return { error: reason }; + } + + return (await res.json()) as { data: WebhookSummaryStats[] }; + }, + ["getWebhookSummary"], + { + revalidate: 60 * 60, // 1 hour + }, +); - return (await res.json()) as { data: WebhookSummaryStats[] }; +export function getWebhookSummary( + params: AnalyticsQueryParams & { webhookId: string }, + authToken: string, +) { + return cached_getWebhookSummary(normalizedParams(params), authToken); } -export async function getWebhookRequests( - params: AnalyticsQueryParams & { webhookId?: string }, -): Promise<{ data: WebhookRequestStats[] } | { error: string }> { - const searchParams = buildSearchParams(params); - if (params.webhookId) { - searchParams.append("webhookId", params.webhookId); - } +const cached_getWebhookRequests = unstable_cache( + async ( + params: AnalyticsQueryParams & { webhookId?: string }, + authToken: string, + ): Promise<{ data: WebhookRequestStats[] } | { error: string }> => { + const searchParams = buildSearchParams(params); + if (params.webhookId) { + searchParams.append("webhookId", params.webhookId); + } - const res = await fetchAnalytics( - `v2/webhook/requests?${searchParams.toString()}`, - ); - if (!res.ok) { - const reason = await res.text(); - return { error: reason }; - } + const res = await fetchAnalytics({ + authToken, + url: `v2/webhook/requests?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + if (!res.ok) { + const reason = await res.text(); + return { error: reason }; + } - return (await res.json()) as { data: WebhookRequestStats[] }; -} + return (await res.json()) as { data: WebhookRequestStats[] }; + }, + ["getWebhookRequests"], + { + revalidate: 60 * 60, // 1 hour + }, +); -export async function getWebhookLatency( +export function getWebhookRequests( params: AnalyticsQueryParams & { webhookId?: string }, -): Promise<{ data: WebhookLatencyStats[] } | { error: string }> { - const searchParams = buildSearchParams(params); - if (params.webhookId) { - searchParams.append("webhookId", params.webhookId); - } - const res = await fetchAnalytics( - `v2/webhook/latency?${searchParams.toString()}`, - ); - if (!res.ok) { - const reason = await res.text(); - return { error: reason }; - } - - return (await res.json()) as { data: WebhookLatencyStats[] }; + authToken: string, +) { + return cached_getWebhookRequests(normalizedParams(params), authToken); } -export async function getInsightChainUsage( - params: AnalyticsQueryParams, -): Promise<{ data: InsightChainStats[] } | { errorMessage: string }> { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/insight/usage/by-chain?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getWebhookLatency = unstable_cache( + async ( + params: AnalyticsQueryParams & { webhookId?: string }, + authToken: string, + ): Promise<{ data: WebhookLatencyStats[] } | { error: string }> => { + const searchParams = buildSearchParams(params); + if (params.webhookId) { + searchParams.append("webhookId", params.webhookId); + } - if (res?.status !== 200) { - const reason = await res?.text(); - const errMsg = `Failed to fetch Insight chain usage: ${res?.status} - ${res.statusText} - ${reason}`; - console.error(errMsg); - return { errorMessage: errMsg }; - } + const res = await fetchAnalytics({ + authToken, + url: `v2/webhook/latency?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + if (!res.ok) { + const reason = await res.text(); + return { error: reason }; + } - const json = await res.json(); - return { data: json.data as InsightChainStats[] }; + return (await res.json()) as { data: WebhookLatencyStats[] }; + }, + ["getWebhookLatency"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getWebhookLatency( + params: AnalyticsQueryParams & { webhookId?: string }, + authToken: string, +) { + return cached_getWebhookLatency(normalizedParams(params), authToken); } -export async function getInsightStatusCodeUsage( - params: AnalyticsQueryParams, -): Promise<{ data: InsightStatusCodeStats[] } | { errorMessage: string }> { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/insight/usage/by-status-code?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getInsightChainUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise<{ data: InsightChainStats[] } | { errorMessage: string }> => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/insight/usage/by-chain?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + const errMsg = `Failed to fetch Insight chain usage: ${res?.status} - ${res.statusText} - ${reason}`; + console.error(errMsg); + return { errorMessage: errMsg }; + } - if (res?.status !== 200) { - const reason = await res?.text(); - const errMsg = `Failed to fetch Insight status code usage: ${res?.status} - ${res.statusText} - ${reason}`; - console.error(errMsg); - return { errorMessage: errMsg }; - } + const json = await res.json(); + return { data: json.data as InsightChainStats[] }; + }, + ["getInsightChainUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return { data: json.data as InsightStatusCodeStats[] }; +export function getInsightChainUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getInsightChainUsage(normalizedParams(params), authToken); } -export async function getInsightEndpointUsage( - params: AnalyticsQueryParams, -): Promise<{ data: InsightEndpointStats[] } | { errorMessage: string }> { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/insight/usage/by-endpoint?${searchParams.toString()}`, - { - method: "GET", - }, - ); +const cached_getInsightStatusCodeUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise<{ data: InsightStatusCodeStats[] } | { errorMessage: string }> => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/insight/usage/by-status-code?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + const errMsg = `Failed to fetch Insight status code usage: ${res?.status} - ${res.statusText} - ${reason}`; + console.error(errMsg); + return { errorMessage: errMsg }; + } - if (res?.status !== 200) { - const reason = await res?.text(); - const errMsg = `Failed to fetch Insight endpoint usage: ${res?.status} - ${res.statusText} - ${reason}`; - console.error(errMsg); - return { errorMessage: errMsg }; - } + const json = await res.json(); + return { data: json.data as InsightStatusCodeStats[] }; + }, + ["getInsightStatusCodeUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); - const json = await res.json(); - return { data: json.data as InsightEndpointStats[] }; +export function getInsightStatusCodeUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getInsightStatusCodeUsage(normalizedParams(params), authToken); } -export async function getInsightUsage( +const cached_getInsightEndpointUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise<{ data: InsightEndpointStats[] } | { errorMessage: string }> => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/insight/usage/by-endpoint?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + const errMsg = `Failed to fetch Insight endpoint usage: ${res?.status} - ${res.statusText} - ${reason}`; + console.error(errMsg); + return { errorMessage: errMsg }; + } + + const json = await res.json(); + return { data: json.data as InsightEndpointStats[] }; + }, + ["getInsightEndpointUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getInsightEndpointUsage( params: AnalyticsQueryParams, -): Promise<{ data: InsightUsageStats[] } | { errorMessage: string }> { - const searchParams = buildSearchParams(params); - const res = await fetchAnalytics( - `v2/insight/usage?${searchParams.toString()}`, - { - method: "GET", - }, - ); + authToken: string, +) { + return cached_getInsightEndpointUsage(normalizedParams(params), authToken); +} - if (res?.status !== 200) { - const reason = await res?.text(); - const errMsg = `Failed to fetch Insight usage: ${res?.status} - ${res.statusText} - ${reason}`; - console.error(errMsg); - return { errorMessage: errMsg }; - } +const cached_getInsightUsage = unstable_cache( + async ( + params: AnalyticsQueryParams, + authToken: string, + ): Promise<{ data: InsightUsageStats[] } | { errorMessage: string }> => { + const searchParams = buildSearchParams(params); + + const res = await fetchAnalytics({ + authToken, + url: `v2/insight/usage?${searchParams.toString()}`, + init: { + method: "GET", + }, + }); + + if (res?.status !== 200) { + const reason = await res?.text(); + const errMsg = `Failed to fetch Insight usage: ${res?.status} - ${res.statusText} - ${reason}`; + console.error(errMsg); + return { errorMessage: errMsg }; + } - const json = await res.json(); - return { data: json.data as InsightUsageStats[] }; + const json = await res.json(); + return { data: json.data as InsightUsageStats[] }; + }, + ["getInsightUsage"], + { + revalidate: 60 * 60, // 1 hour + }, +); + +export function getInsightUsage( + params: AnalyticsQueryParams, + authToken: string, +) { + return cached_getInsightUsage(normalizedParams(params), authToken); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/page.tsx index 65440078f7c..c2433fd65a4 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/page.tsx @@ -38,7 +38,10 @@ export default async function Page(props: { }); const projects = await getProjects(params.team_slug); - const projectsWithTotalWallets = await getProjectsWithAnalytics(projects); + const projectsWithTotalWallets = await getProjectsWithAnalytics( + projects, + authToken, + ); return (
@@ -78,6 +81,7 @@ export default async function Page(props: { async function getProjectsWithAnalytics( projects: Project[], + authToken: string, ): Promise> { return Promise.all( projects.map(async (project) => { @@ -85,13 +89,16 @@ async function getProjectsWithAnalytics( const today = new Date(); const thirtyDaysAgo = subDays(today, 30); - const data = await getWalletConnections({ - from: thirtyDaysAgo, - period: "all", - projectId: project.id, - teamId: project.teamId, - to: today, - }); + const data = await getWalletConnections( + { + from: thirtyDaysAgo, + period: "all", + projectId: project.id, + teamId: project.teamId, + to: today, + }, + authToken, + ); let uniqueWalletsConnected = 0; for (const d of data) { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/highlights-card.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/highlights-card.tsx index ec5d3af610b..9664d9a7f9d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/highlights-card.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/highlights-card.tsx @@ -73,7 +73,7 @@ export function TeamHighlightsCard({ activeChart={ selectedChart && selectedChart in chartConfig ? (selectedChart as keyof AggregatedMetrics) - : "totalVolume" + : "activeUsers" } aggregateFn={(_data, key) => { if (key === "activeUsers") { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx index 74d64b7116a..3eeb5f44afc 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/analytics/page.tsx @@ -15,6 +15,7 @@ import { getWalletConnections, getWalletUsers, } from "@/api/analytics"; +import { getAuthToken } from "@/api/auth-token"; import { getTeamBySlug } from "@/api/team/get-team"; import type { DurationId, @@ -28,6 +29,7 @@ import type { UserOpStats, WalletStats, } from "@/types/analytics"; +import { loginRedirect } from "@/utils/redirects"; import { PieChartCard } from "../../../../components/Analytics/PieChartCard"; import { TotalSponsoredChartCardUI } from "../../_components/TotalSponsoredCard"; import { TransactionsChartCardWithChainMapping } from "../../_components/transaction-card-with-chain-mapping"; @@ -53,6 +55,12 @@ export default async function TeamOverviewPage(props: { props.searchParams, ]); + const authToken = await getAuthToken(); + + if (!authToken) { + loginRedirect(`/team/${params.team_slug}/~/analytics`); + } + const team = await getTeamBySlug(params.team_slug); if (!team) { @@ -91,6 +99,7 @@ export default async function TeamOverviewPage(props: { fallback={} > } searchParamsUsed={["from", "to"]} > - +
@@ -133,6 +147,7 @@ export default async function TeamOverviewPage(props: { > undefined); + const walletConnections = await getWalletConnections( + { + from: props.range.from, + period: "all", + teamId: props.teamId, + to: props.range.to, + }, + props.authToken, + ).catch(() => undefined); return walletConnections && walletConnections.length > 0 ? ( @@ -237,13 +264,17 @@ async function AsyncWalletDistributionCard(props: { async function AsyncAuthMethodDistributionCard(props: { teamId: string; range: Range; + authToken: string; }) { - const inAppWalletUsage = await getInAppWalletUsage({ - from: props.range.from, - period: "all", - teamId: props.teamId, - to: props.range.to, - }).catch(() => undefined); + const inAppWalletUsage = await getInAppWalletUsage( + { + from: props.range.from, + period: "all", + teamId: props.teamId, + to: props.range.to, + }, + props.authToken, + ).catch(() => undefined); return inAppWalletUsage && inAppWalletUsage.length > 0 ? ( @@ -261,21 +292,28 @@ async function AsyncTransactionsChartCard(props: { interval: "day" | "week"; selectedChart: string | undefined; selectedChartQueryParam: string; + authToken: string; }) { const [clientTransactionsTimeSeries, clientTransactions] = await Promise.allSettled([ - getClientTransactions({ - from: props.range.from, - period: props.interval, - teamId: props.teamId, - to: props.range.to, - }), - getClientTransactions({ - from: props.range.from, - period: "all", - teamId: props.teamId, - to: props.range.to, - }), + getClientTransactions( + { + from: props.range.from, + period: props.interval, + teamId: props.teamId, + to: props.range.to, + }, + props.authToken, + ), + getClientTransactions( + { + from: props.range.from, + period: "all", + teamId: props.teamId, + to: props.range.to, + }, + props.authToken, + ), ]); return clientTransactionsTimeSeries.status === "fulfilled" && @@ -301,20 +339,27 @@ async function AsyncTotalSponsoredCard(props: { interval: "day" | "week"; selectedChart: string | undefined; selectedChartQueryParam: string; + authToken: string; }) { const [userOpUsageTimeSeries, userOpUsage] = await Promise.allSettled([ - getUserOpUsage({ - from: props.range.from, - period: props.interval, - teamId: props.teamId, - to: props.range.to, - }), - getUserOpUsage({ - from: props.range.from, - period: "all", - teamId: props.teamId, - to: props.range.to, - }), + getUserOpUsage( + { + from: props.range.from, + period: props.interval, + teamId: props.teamId, + to: props.range.to, + }, + props.authToken, + ), + getUserOpUsage( + { + from: props.range.from, + period: "all", + teamId: props.teamId, + to: props.range.to, + }, + props.authToken, + ), ]); return userOpUsageTimeSeries.status === "fulfilled" && diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/analytics/components/EcosystemAnalyticsPage.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/analytics/components/EcosystemAnalyticsPage.tsx index 51ae46eeaed..59241d62de9 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/analytics/components/EcosystemAnalyticsPage.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/analytics/components/EcosystemAnalyticsPage.tsx @@ -18,6 +18,7 @@ export async function EcosystemAnalyticsPage({ range, partners, defaultRange, + authToken, }: { ecosystemSlug: string; teamId: string; @@ -25,6 +26,7 @@ export async function EcosystemAnalyticsPage({ range: Range; defaultRange: DurationId; partners: Partner[]; + authToken: string; }) { return (
@@ -40,6 +42,7 @@ export async function EcosystemAnalyticsPage({ @@ -66,6 +69,7 @@ export async function EcosystemAnalyticsPage({ partners={partners} range={range} teamId={teamId} + authToken={authToken} />
@@ -75,24 +79,31 @@ export async function EcosystemAnalyticsPage({ async function AsyncEcosystemWalletsSummary(props: { ecosystemSlug: string; teamId: string; + authToken: string; }) { - const { ecosystemSlug, teamId } = props; + const { ecosystemSlug, teamId, authToken } = props; - const allTimeStatsPromise = getEcosystemWalletUsage({ - ecosystemSlug, - from: new Date(2022, 0, 1), - period: "all", - teamId, - to: new Date(), - }); + const allTimeStatsPromise = getEcosystemWalletUsage( + { + ecosystemSlug, + from: new Date(2022, 0, 1), + period: "all", + teamId, + to: new Date(), + }, + authToken, + ); - const monthlyStatsPromise = getEcosystemWalletUsage({ - ecosystemSlug, - from: new Date(new Date().getFullYear(), new Date().getMonth(), 1), - period: "month", - teamId, - to: new Date(), - }); + const monthlyStatsPromise = getEcosystemWalletUsage( + { + ecosystemSlug, + from: new Date(new Date().getFullYear(), new Date().getMonth(), 1), + period: "month", + teamId, + to: new Date(), + }, + authToken, + ); const [allTimeStats, monthlyStats] = await Promise.all([ allTimeStatsPromise, @@ -114,16 +125,20 @@ async function AsyncEcosystemWalletUsersAnalytics(props: { interval: "day" | "week"; range: Range; partners: Partner[]; + authToken: string; }) { - const { ecosystemSlug, teamId, interval, range, partners } = props; + const { ecosystemSlug, teamId, interval, range, partners, authToken } = props; - const stats = await getEcosystemWalletUsage({ - ecosystemSlug, - from: range.from, - period: interval, - teamId, - to: range.to, - }).catch(() => null); + const stats = await getEcosystemWalletUsage( + { + ecosystemSlug, + from: range.from, + period: interval, + teamId, + to: range.to, + }, + authToken, + ).catch(() => null); return ( ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/account-abstraction/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/account-abstraction/page.tsx index 7dcf93b74c8..832f86125e1 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/account-abstraction/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/account-abstraction/page.tsx @@ -61,13 +61,16 @@ export default async function Page(props: { type: rangeType, }; - const userOpStats = await getUserOpUsage({ - from: range.from, - period: interval, - projectId: project.id, - teamId: project.teamId, - to: range.to, - }); + const userOpStats = await getUserOpUsage( + { + from: range.from, + period: interval, + projectId: project.id, + teamId: project.teamId, + to: range.to, + }, + authToken, + ); const client = getClientThirdwebClient({ jwt: authToken, @@ -79,6 +82,7 @@ export default async function Page(props: {
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/EngineCloudChartCard/index.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/EngineCloudChartCard/index.tsx index 6168b06a93d..6272062341d 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/EngineCloudChartCard/index.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/EngineCloudChartCard/index.tsx @@ -2,7 +2,13 @@ import { getEngineCloudMethodUsage } from "@/api/analytics"; import type { AnalyticsQueryParams } from "@/types/analytics"; import { EngineCloudBarChartCardUI } from "./EngineCloudBarChartCardUI"; -export async function EngineCloudChartCardAsync(props: AnalyticsQueryParams) { - const rawData = await getEngineCloudMethodUsage(props); +export async function EngineCloudChartCardAsync(props: { + params: AnalyticsQueryParams; + authToken: string; +}) { + const rawData = await getEngineCloudMethodUsage( + props.params, + props.authToken, + ); return ; } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/RpcMethodBarChartCard/index.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/RpcMethodBarChartCard/index.tsx index 6f047c90f70..0b5169563d4 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/RpcMethodBarChartCard/index.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/RpcMethodBarChartCard/index.tsx @@ -2,8 +2,10 @@ import { getRpcMethodUsage } from "@/api/analytics"; import type { AnalyticsQueryParams } from "@/types/analytics"; import { RpcMethodBarChartCardUI } from "./RpcMethodBarChartCardUI"; -export async function RpcMethodBarChartCardAsync(props: AnalyticsQueryParams) { - const rawData = await getRpcMethodUsage(props); - +export async function RpcMethodBarChartCardAsync(props: { + params: AnalyticsQueryParams; + authToken: string; +}) { + const rawData = await getRpcMethodUsage(props.params, props.authToken); return ; } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/Transactions/index.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/Transactions/index.tsx index 3b403004d33..894f2f42060 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/Transactions/index.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/components/Transactions/index.tsx @@ -3,19 +3,24 @@ import { getClientTransactions } from "@/api/analytics"; import type { AnalyticsQueryParams } from "@/types/analytics"; import { TransactionsChartsUI } from "./TransactionCharts"; -export async function TransactionsChartCardAsync( - props: AnalyticsQueryParams & { - selectedChartQueryParam: string; - selectedChart: string | undefined; - client: ThirdwebClient; - }, -) { +export async function TransactionsChartCardAsync(props: { + params: AnalyticsQueryParams; + client: ThirdwebClient; + selectedChartQueryParam: string; + selectedChart: string | undefined; + authToken: string; +}) { + const { params, authToken, client, selectedChart, selectedChartQueryParam } = + props; const [data, aggregatedData] = await Promise.all([ - getClientTransactions(props), - getClientTransactions({ - ...props, - period: "all", - }), + getClientTransactions(params, authToken), + getClientTransactions( + { + ...params, + period: "all", + }, + authToken, + ), ]); if (!aggregatedData.length) { @@ -25,10 +30,10 @@ export async function TransactionsChartCardAsync( return ( ); } diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx index 2e905a0ca98..e907abf23cd 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/components/InsightAnalytics.tsx @@ -51,39 +51,52 @@ export async function InsightAnalytics(props: { teamId: string; range: Range; interval: "day" | "week"; + authToken: string; }) { const { projectId, teamId, range, interval } = props; - const allTimeRequestsPromise = getInsightUsage({ - from: range.from, - period: "all", - projectId: projectId, - teamId: teamId, - to: range.to, - }); - const chainsDataPromise = getInsightChainUsage({ - from: range.from, - limit: 10, - period: "all", - projectId: projectId, - teamId: teamId, - to: range.to, - }); - const statusCodesDataPromise = getInsightStatusCodeUsage({ - from: range.from, - period: interval, - projectId: projectId, - teamId: teamId, - to: range.to, - }); - const endpointsDataPromise = getInsightEndpointUsage({ - from: range.from, - limit: 10, - period: "all", - projectId: projectId, - teamId: teamId, - to: range.to, - }); + const allTimeRequestsPromise = getInsightUsage( + { + from: range.from, + period: "all", + projectId: projectId, + teamId: teamId, + to: range.to, + }, + props.authToken, + ); + const chainsDataPromise = getInsightChainUsage( + { + from: range.from, + limit: 10, + period: "all", + projectId: projectId, + teamId: teamId, + to: range.to, + }, + props.authToken, + ); + const statusCodesDataPromise = getInsightStatusCodeUsage( + { + from: range.from, + period: interval, + projectId: projectId, + teamId: teamId, + to: range.to, + }, + props.authToken, + ); + const endpointsDataPromise = getInsightEndpointUsage( + { + from: range.from, + limit: 10, + period: "all", + projectId: projectId, + teamId: teamId, + to: range.to, + }, + props.authToken, + ); const [allTimeRequestsData, statusCodesData, endpointsData, chainsData] = await Promise.all([ diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx index 50dd723c0ea..4bcae5279d4 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/insight/page.tsx @@ -54,6 +54,7 @@ export default async function Page(props: { projectId={project.id} range={range} teamId={project.teamId} + authToken={authToken} />
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/overview/highlights-card.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/overview/highlights-card.tsx index 62b376c61b8..ac498c580cd 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/overview/highlights-card.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/overview/highlights-card.tsx @@ -67,7 +67,7 @@ export function ProjectHighlightsCard(props: { activeChart={ selectedChart && selectedChart in chartConfig ? (selectedChart as keyof AggregatedMetrics) - : "totalVolume" + : "activeUsers" } aggregateFn={(_data, key) => { if (key === "activeUsers") { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/page.tsx index 4b43e76b050..1096465fd85 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/page.tsx @@ -91,6 +91,7 @@ export default async function ProjectOverviewPage(props: PageProps) { }); const activeStatus = await isProjectActive({ + authToken, projectId: project.id, teamId: project.teamId, }); @@ -124,6 +125,7 @@ export default async function ProjectOverviewPage(props: PageProps) { ) : (
@@ -157,6 +161,7 @@ async function ProjectAnalytics(props: { searchParamsUsed={["from", "to", "interval", "appHighlights"]} > } searchParamsUsed={["from", "to", "interval"]} > - + } searchParamsUsed={["from", "to", "interval"]} > - +
@@ -193,16 +206,20 @@ async function ProjectAnalytics(props: { > @@ -220,6 +237,7 @@ async function ProjectAnalytics(props: { : undefined } selectedChartQueryParam="totalSponsored" + authToken={authToken} /> @@ -228,11 +246,14 @@ async function ProjectAnalytics(props: { searchParamsUsed={["from", "to", "interval"]} > @@ -241,11 +262,14 @@ async function ProjectAnalytics(props: { searchParamsUsed={["from", "to", "interval"]} >
@@ -258,22 +282,29 @@ export async function AsyncTotalSponsoredCard(props: { interval: "day" | "week"; selectedChart: string | undefined; selectedChartQueryParam: string; + authToken: string; }) { const [userOpUsageTimeSeries, userOpUsage] = await Promise.allSettled([ - getUserOpUsage({ - from: props.range.from, - period: props.interval, - projectId: props.project.id, - teamId: props.project.teamId, - to: props.range.to, - }), - getUserOpUsage({ - from: props.range.from, - period: "all", - projectId: props.project.id, - teamId: props.project.teamId, - to: props.range.to, - }), + getUserOpUsage( + { + from: props.range.from, + period: props.interval, + projectId: props.project.id, + teamId: props.project.teamId, + to: props.range.to, + }, + props.authToken, + ), + getUserOpUsage( + { + from: props.range.from, + period: "all", + projectId: props.project.id, + teamId: props.project.teamId, + to: props.range.to, + }, + props.authToken, + ), ]); return userOpUsageTimeSeries.status === "fulfilled" && @@ -298,14 +329,18 @@ export async function AsyncTotalSponsoredCard(props: { async function AsyncAuthMethodDistributionCard(props: { project: Project; range: Range; + authToken: string; }) { - const inAppWalletUsage = await getInAppWalletUsage({ - from: props.range.from, - period: "all", - projectId: props.project.id, - teamId: props.project.teamId, - to: props.range.to, - }).catch(() => undefined); + const inAppWalletUsage = await getInAppWalletUsage( + { + from: props.range.from, + period: "all", + projectId: props.project.id, + teamId: props.project.teamId, + to: props.range.to, + }, + props.authToken, + ).catch(() => undefined); return inAppWalletUsage && inAppWalletUsage.length > 0 ? ( @@ -325,23 +360,30 @@ async function AsyncAppHighlightsCard(props: { selectedChart: string | undefined; client: ThirdwebClient; params: PageParams; + authToken: string; }) { const [walletUserStatsTimeSeries, universalBridgeUsage] = await Promise.allSettled([ - getWalletUsers({ - from: props.range.from, - period: props.interval, - projectId: props.project.id, - teamId: props.project.teamId, - to: props.range.to, - }), - getUniversalBridgeUsage({ - from: props.range.from, - period: props.interval, - projectId: props.project.id, - teamId: props.project.teamId, - to: props.range.to, - }), + getWalletUsers( + { + from: props.range.from, + period: props.interval, + projectId: props.project.id, + teamId: props.project.teamId, + to: props.range.to, + }, + props.authToken, + ), + getUniversalBridgeUsage( + { + from: props.range.from, + period: props.interval, + projectId: props.project.id, + teamId: props.project.teamId, + to: props.range.to, + }, + props.authToken, + ), ]); if ( @@ -380,14 +422,18 @@ async function AsyncAppHighlightsCard(props: { async function AsyncWalletDistributionCard(props: { project: Project; range: Range; + authToken: string; }) { - const walletConnections = await getWalletConnections({ - from: props.range.from, - period: "all", - projectId: props.project.id, - teamId: props.project.teamId, - to: props.range.to, - }).catch(() => undefined); + const walletConnections = await getWalletConnections( + { + from: props.range.from, + period: "all", + projectId: props.project.id, + teamId: props.project.teamId, + to: props.range.to, + }, + props.authToken, + ).catch(() => undefined); return walletConnections && walletConnections.length > 0 ? ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/analytics/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/analytics/page.tsx index 4488492f138..b6edb4902bc 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/analytics/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/analytics/page.tsx @@ -54,6 +54,7 @@ export default async function Page(props: { projectId={project.id} range={range} teamId={project.teamId} + authToken={authToken} />
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx index 1b824843852..f8181c376b4 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/payments/components/PayAnalytics.tsx @@ -26,6 +26,7 @@ export async function PayAnalytics(props: { teamId: string; range: Range; interval: "day" | "week"; + authToken: string; }) { const { projectId, teamId, range, interval } = props; @@ -37,23 +38,29 @@ export async function PayAnalytics(props: { month: "short" as const, }; - const volumeDataPromise = getUniversalBridgeUsage({ - from: range.from, - period: interval, - projectId: projectId, - teamId: teamId, - to: range.to, - }).catch((error) => { + const volumeDataPromise = getUniversalBridgeUsage( + { + from: range.from, + period: interval, + projectId: projectId, + teamId: teamId, + to: range.to, + }, + props.authToken, + ).catch((error) => { console.error(error); return []; }); - const walletDataPromise = getUniversalBridgeWalletUsage({ - from: range.from, - period: interval, - projectId: projectId, - teamId: teamId, - to: range.to, - }).catch((error) => { + const walletDataPromise = getUniversalBridgeWalletUsage( + { + from: range.from, + period: interval, + projectId: projectId, + teamId: teamId, + to: range.to, + }, + props.authToken, + ).catch((error) => { console.error(error); return []; }); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/components/RpcAnalytics.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/components/RpcAnalytics.tsx index a3854c546f5..5090a0415a7 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/components/RpcAnalytics.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/components/RpcAnalytics.tsx @@ -17,31 +17,41 @@ export async function RPCAnalytics(props: { teamId: string; range: Range; interval: "day" | "week"; + authToken: string; }) { - const { projectId, teamId, range, interval } = props; + const { projectId, teamId, range, interval, authToken } = props; // TODO: add requests by status code, but currently not performant enough - const allRequestsByUsageTypePromise = getRpcUsageByType({ - from: range.from, - period: "all", - projectId: projectId, - teamId: teamId, - to: range.to, - }); - const requestsByUsageTypePromise = getRpcUsageByType({ - from: range.from, - period: interval, - projectId: projectId, - teamId: teamId, - to: range.to, - }); - const evmMethodsPromise = getRpcMethodUsage({ - from: range.from, - period: "all", - projectId: projectId, - teamId: teamId, - to: range.to, - }).catch((error) => { + const allRequestsByUsageTypePromise = getRpcUsageByType( + { + from: range.from, + period: "all", + projectId: projectId, + teamId: teamId, + to: range.to, + }, + authToken, + ); + const requestsByUsageTypePromise = getRpcUsageByType( + { + from: range.from, + period: interval, + projectId: projectId, + teamId: teamId, + to: range.to, + }, + authToken, + ); + const evmMethodsPromise = getRpcMethodUsage( + { + from: range.from, + period: "all", + projectId: projectId, + teamId: teamId, + to: range.to, + }, + authToken, + ).catch((error) => { console.error(error); return []; }); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/page.tsx index 705e11306ed..993f62f7ccd 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/rpc/page.tsx @@ -52,6 +52,7 @@ export default async function Page(props: { projectClientId={project.publishableKey} projectId={project.id} range={range} + authToken={authToken} teamId={project.teamId} />
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/Summary.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/Summary.tsx index 482b4c8ddce..3d8b4a06ee6 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/Summary.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/Summary.tsx @@ -51,23 +51,30 @@ function InAppWalletsSummaryInner(props: { async function AsyncInAppWalletsSummary(props: { teamId: string; projectId: string; + authToken: string; }) { - const { teamId, projectId } = props; - const allTimeStatsPromise = getInAppWalletUsage({ - from: new Date(2022, 0, 1), - period: "all", - projectId, - teamId, - to: new Date(), - }); + const { teamId, projectId, authToken } = props; + const allTimeStatsPromise = getInAppWalletUsage( + { + from: new Date(2022, 0, 1), + period: "all", + projectId, + teamId, + to: new Date(), + }, + authToken, + ); - const monthlyStatsPromise = getInAppWalletUsage({ - from: subDays(new Date(), 30), - period: "month", - projectId, - teamId, - to: new Date(), - }); + const monthlyStatsPromise = getInAppWalletUsage( + { + from: subDays(new Date(), 30), + period: "month", + projectId, + teamId, + to: new Date(), + }, + authToken, + ); const [allTimeStats, monthlyStats] = await Promise.all([ allTimeStatsPromise, @@ -86,6 +93,7 @@ async function AsyncInAppWalletsSummary(props: { export function InAppWalletsSummary(props: { teamId: string; projectId: string; + authToken: string; }) { return ( ); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/index.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/index.tsx index 4c829b899c1..46fc4cf4a8c 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/index.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/analytics/chart/index.tsx @@ -34,6 +34,7 @@ type AsyncInAppWalletAnalyticsProps = Omit< > & { teamId: string; projectId: string; + authToken: string; }; async function AsyncInAppWalletAnalytics( @@ -41,13 +42,16 @@ async function AsyncInAppWalletAnalytics( ) { const range = props.range ?? getLastNDaysRange("last-120"); - const stats = await getInAppWalletUsage({ - from: range.from, - period: props.interval, - projectId: props.projectId, - teamId: props.teamId, - to: range.to, - }).catch((error) => { + const stats = await getInAppWalletUsage( + { + from: range.from, + period: props.interval, + projectId: props.projectId, + teamId: props.teamId, + to: range.to, + }, + props.authToken, + ).catch((error) => { console.error(error); return []; }); diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/page.tsx index c9e504eb94f..b350de9879f 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/wallets/page.tsx @@ -1,9 +1,11 @@ import { redirect } from "next/navigation"; import { ResponsiveSearchParamsProvider } from "responsive-rsc"; +import { getAuthToken } from "@/api/auth-token"; import { getProject } from "@/api/project/projects"; import type { DurationId } from "@/components/analytics/date-range-selector"; import { ResponsiveTimeFilters } from "@/components/analytics/responsive-time-filters"; import { getFiltersFromSearchParams } from "@/lib/time"; +import { loginRedirect } from "@/utils/redirects"; import { InAppWalletAnalytics } from "./analytics/chart"; import { InAppWalletsSummary } from "./analytics/chart/Summary"; @@ -21,6 +23,11 @@ export default async function Page(props: { props.params, ]); + const authToken = await getAuthToken(); + if (!authToken) { + loginRedirect(`/team/${params.team_slug}/${params.project_slug}/wallets`); + } + const defaultRange: DurationId = "last-30"; const { range, interval } = getFiltersFromSearchParams({ defaultRange, @@ -38,7 +45,11 @@ export default async function Page(props: { return (
- +
@@ -47,6 +58,7 @@ export default async function Page(props: { projectId={project.id} range={range} teamId={project.teamId} + authToken={authToken} />
diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx index 619cfd8ab06..91d8fd17304 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/analytics/page.tsx @@ -90,14 +90,17 @@ export default async function WebhooksAnalyticsPage(props: { const webhookId = selectedWebhookId === "all" ? undefined : selectedWebhookId; const [requestsData, latencyData] = await Promise.all([ (async () => { - const res = await getWebhookRequests({ - from: range.from, - period: interval, - projectId: project.id, - teamId: project.teamId, - to: range.to, - webhookId, - }); + const res = await getWebhookRequests( + { + from: range.from, + period: interval, + projectId: project.id, + teamId: project.teamId, + to: range.to, + webhookId, + }, + authToken, + ); if ("error" in res) { console.error("Failed to fetch webhook requests:", res.error); return []; @@ -105,14 +108,17 @@ export default async function WebhooksAnalyticsPage(props: { return res.data; })(), (async () => { - const res = await getWebhookLatency({ - from: range.from, - period: interval, - projectId: project.id, - teamId: project.teamId, - to: range.to, - webhookId, - }); + const res = await getWebhookLatency( + { + from: range.from, + period: interval, + projectId: project.id, + teamId: project.teamId, + to: range.to, + webhookId, + }, + authToken, + ); if ("error" in res) { console.error("Failed to fetch webhook latency:", res.error); return []; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx index 5eed6867e32..9a75d7c8918 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/webhooks/page.tsx @@ -80,14 +80,17 @@ export default async function WebhooksPage(props: { // Fetch metrics for all webhooks in parallel const webhookMetrics = await Promise.all( webhookConfigs.map(async (config) => { - const metricsResult = await getWebhookSummary({ - from: new Date(Date.now() - 24 * 60 * 60 * 1000), - period: "day", - projectId: project.id, - teamId: project.teamId, // 24 hours ago - to: new Date(), - webhookId: config.id, - }); + const metricsResult = await getWebhookSummary( + { + from: new Date(Date.now() - 24 * 60 * 60 * 1000), + period: "day", + projectId: project.id, + teamId: project.teamId, // 24 hours ago + to: new Date(), + webhookId: config.id, + }, + authToken, + ); return { metrics: