-
Notifications
You must be signed in to change notification settings - Fork 619
[Dashboard] Update analytics API integration and add error logging #8001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Dashboard] Update analytics API integration and add error logging #8001
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAnalytics utilities updated to group aggregations by day and topic, add optional endDate and dynamic limits, refactor fetch URL usage, improve error handling (including logging response text on non-ok responses), and change total-events aggregation to use a generic count with client-id header. One function signature was extended to accept endDate. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor UI as Dashboard UI
participant Util as Analytics Utils
participant API as Insight Service
UI->>Util: getContractEventBreakdown(contractAddress, chainId, startDate, endDate?)
Util->>Util: compute daysDifference, build query (group_by=day,topic_0, limit)
Util->>API: GET /v1/events/... with query
alt 200 OK
API-->>Util: aggregations [{ day, topic_0, count }, ...]
Util->>Util: aggregate counts per day/topic, build time-series (time = new Date(day))
Util-->>UI: EventBreakdownEntry[]
else non-OK
API-->>Util: error text
Util-->>UI: throw Error (includes response text)
end
UI->>Util: getContractEvents(contractAddress, chainId, startDate, endDate?)
Util->>API: GET ... (group_by=day, limit)
API-->>Util: [{ day, count }, ...] or error
Util-->>UI: AnalyticsEntry[] or thrown Error
UI->>Util: getTotalContractEvents(contractAddress, chainId)
Util->>API: GET ... aggregate=count()
API-->>Util: [{ count }]
Util-->>UI: total count or thrown Error
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Git: Failed to clone repository. Please run the ✨ Finishing Touches
🧪 Generate unit tests
Comment |
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
...(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
Outdated
Show resolved
Hide resolved
...(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
Outdated
Show resolved
Hide resolved
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #8001 +/- ##
=======================================
Coverage 56.64% 56.64%
=======================================
Files 904 904
Lines 58677 58677
Branches 4164 4164
=======================================
Hits 33236 33236
Misses 25335 25335
Partials 106 106
🚀 New features to boost your workflow:
|
size-limit report 📦
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts (1)
1-1: Prevent leaking service key: mark as server-only.This module uses a server secret (
INSIGHT_SERVICE_API_KEY). Guard against accidental client bundling.+import "server-only"; import { INSIGHT_SERVICE_API_KEY } from "@/constants/server-envs";apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts (1)
5-17: Fix InsightResponse type: use day instead of time.Current type says
time, but parsing expectsday. Correct the contract to prevent type drift hiding real issues.-type InsightResponse = { - aggregations: [ - Record< - string, - | { - time: string; - count: number; - } - | unknown - >, - ]; -}; +type InsightResponse = { + aggregations: [Record<string, { day: string; count: number } | unknown>]; +};apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (3)
16-18: Fix EventBreakdownEntry type: intersection conflicts with time: DateRecord<string, number> intersects poorly with { time: Date } (time becomes never). Type this as hex-topic keys to avoid overlap.
-type EventBreakdownEntry = Record<string, number> & { - time: Date; -}; +type EventBreakdownEntry = { + time: Date; +} & Record<`0x${string}`, number>;
54-62: Return actionable fetch errors and set Accept header (+ optional timeout)Surface status and response text; set Accept; add abort to prevent hangs.
- const res = await fetch(url, { - headers: { - "x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID, - }, - }); + const ac = new AbortController(); + const timeout = setTimeout(() => ac.abort(), 15_000); + const res = await fetch(url, { + headers: { + Accept: "application/json", + "x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID, + }, + signal: ac.signal, + }); + clearTimeout(timeout); - if (!res.ok) { - throw new Error("Failed to fetch analytics data"); - } + if (!res.ok) { + const body = await res.text().catch(() => ""); + throw new Error( + `Failed to fetch analytics data (${res.status} ${res.statusText}): ${body.slice( + 0, + 200, + )}`, + ); + }
109-109: Parse “day” as UTC to avoid TZ drift; simplify sortDate-only strings can shift by timezone.
- time: new Date(day), + // Ensure UTC midnight + time: new Date(`${day}T00:00:00Z`),- return new Date(a.time).getTime() - new Date(b.time).getTime(); + return a.time.getTime() - b.time.getTime();Also applies to: 115-116
🧹 Nitpick comments (14)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts (2)
22-27: Log status and URL on non-ok responses.Including status, statusText, and URL accelerates debugging and error triage.
- const errorText = await res.text(); - console.error( - "failed to fetch chain services for chain", - chainId, - errorText, - ); + const errorText = await res.text(); + console.error("insight chain services fetch failed", { + chainId, + status: res.status, + statusText: res.statusText, + url: res.url, + body: errorText, + });
7-9: Naming consistency: function vs file name.File is
isAnalyticsSupportedForChain.ts, function isisInsightSupportedForChain. Consider exporting an alias for consistency in imports.export async function isInsightSupportedForChain( chainId: number, ): Promise<boolean> { ... } + +export { isInsightSupportedForChain as isAnalyticsSupportedForChain };apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts (4)
26-28: URL-encode contract address.Prevents issues if a non-standard address or path-like string is passed.
- `https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`, + `https://insight.${thirdwebDomain}.com/v1/events/${encodeURIComponent( + params.contractAddress, + )}?${queryParams}`,
29-32: Add Accept header.Clarifies expected response and can help with intermediaries.
headers: { "x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID, + Accept: "application/json", },
35-37: Surface status code and body on failure.Improves observability and parity with error handling in sibling utils.
- if (!res.ok) { - throw new Error("Failed to fetch analytics data"); - } + if (!res.ok) { + const errorText = await res.text(); + throw new Error( + `Failed to fetch analytics data (${res.status} ${res.statusText}): ${errorText}`, + ); + }
41-43: Guard against missing aggregation path.Avoids runtime errors if the backend returns an empty aggregation.
- return { - count: json.aggregations[0][0].count, - }; + const count = json.aggregations?.[0]?.[0]?.count ?? 0; + return { count };apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts (5)
42-44: Add aggregate=count() for deterministic count field.Some backends don’t include implicit counts with group_by. Make it explicit for consistency with
total-contract-events.ts.const queryParams = [ `chain=${params.chainId}`, - "group_by=day", - `limit=${daysDifference}`, + "group_by=day", + "aggregate=count()", + `limit=${limit}`,
54-55: URL-encode contract address.Safety for unexpected input.
- const url = `https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`; + const url = `https://insight.${thirdwebDomain}.com/v1/events/${encodeURIComponent( + params.contractAddress, + )}?${queryParams}`;
57-60: Add Accept header.Small clarity/interop win.
const res = await fetch(url, { headers: { "x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID, + Accept: "application/json", }, });
62-65: Include status code in thrown error.Keeps parity with other modules and improves logs.
- if (!res.ok) { - const errorText = await res.text(); - throw new Error(`Failed to fetch analytics data: ${errorText}`); - } + if (!res.ok) { + const errorText = await res.text(); + throw new Error( + `Failed to fetch analytics data (${res.status} ${res.statusText}): ${errorText}`, + ); + }
81-83: Parse ISO dates to avoid timezone surprises.
new Date('YYYY-MM-DD')can be misinterpreted. PreferparseISO.-import { getUnixTime } from "date-fns"; +import { getUnixTime, parseISO } from "date-fns"; ... - time: new Date(value.day), + time: parseISO(value.day),apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (3)
67-68: Remove console.log before mergeAvoid noisy logs in server/client output.
- console.log("getContractEventBreakdown aggregations", aggregations); + // console.debug("getContractEventBreakdown aggregations", aggregations);
12-14: Loosen aggregations typing and guard for empty payloadSafer against shape changes and empty arrays.
-type InsightResponse = { - aggregations: [Record<string, InsightAggregationEntry | unknown>]; -}; +type InsightResponse = { + aggregations: Array<Record<string, unknown>>; +}; @@ - const aggregations = Object.values(json.aggregations[0]); + const firstAgg = json.aggregations?.[0] ?? {}; + const aggregations = Object.values(firstAgg);Also applies to: 65-66
36-49: Prefer URLSearchParams for query constructionAvoid empty strings and manual joins.
- const queryParams = [ - `chain=${params.chainId}`, - "group_by=day", - "group_by=topic_0", - `limit=${daysDifference * 10}`, // at most 10 topics per day - params.startDate - ? `filter_block_timestamp_gte=${getUnixTime(params.startDate)}` - : "", - params.endDate - ? `filter_block_timestamp_lte=${getUnixTime(params.endDate)}` - : "", - ] - .filter(Boolean) - .join("&"); + const qs = new URLSearchParams({ + chain: String(params.chainId), + group_by: "day", + }); + qs.append("group_by", "topic_0"); + qs.set("limit", String(daysDifference * 10)); // at most 10 topics per day + if (params.startDate) { + qs.set("filter_block_timestamp_gte", String(getUnixTime(params.startDate))); + } + if (params.endDate) { + qs.set("filter_block_timestamp_lte", String(getUnixTime(params.endDate))); + } + const queryParams = qs.toString();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts(5 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts
📚 Learning: 2025-05-27T19:54:55.885Z
Learnt from: MananTank
PR: thirdweb-dev/js#7177
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx:15-17
Timestamp: 2025-05-27T19:54:55.885Z
Learning: The `fetchDashboardContractMetadata` function from "3rdweb-sdk/react/hooks/useDashboardContractMetadata" has internal error handlers for all promises and cannot throw errors, so external error handling is not needed when calling this function.
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
📚 Learning: 2025-05-21T05:17:31.283Z
Learnt from: jnsdls
PR: thirdweb-dev/js#6929
File: apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/insight/webhooks/page.tsx:14-19
Timestamp: 2025-05-21T05:17:31.283Z
Learning: In Next.js server components, the `params` object can sometimes be a Promise that needs to be awaited, despite type annotations suggesting otherwise. In apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/insight/webhooks/page.tsx, it's necessary to await the params object before accessing its properties.
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Analytics event name: human-readable `<subject> <verb>` (e.g., "contract deployed"); function: `report<Subject><Verb>` (PascalCase)
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Size
🔇 Additional comments (6)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts (2)
9-10: Type rename to count is correct.Matches the updated aggregation shape.
22-24: LGTM on aggregate=count().This aligns the query with the new response shape.
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts (2)
57-60: LGTM on x-client-id header.Consistent with other utils; keeps tokens out of URLs.
42-44: Timezone of group_by=day.Confirm backend groups by UTC; otherwise off-by-one days can occur around TZ boundaries. If not UTC, consider adding/using a
tz/timezoneparam or normalizing server-side.apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (2)
54-57: Auth header (server) and component boundaryIf this runs server-side, prefer adding Authorization: Bearer from getAuthToken() and mark the module as server-only. If it must run client-side, keep public headers only.
Would you like me to add a server-only variant (import "server-only") and include the auth header when a JWT cookie is present, falling back to x-client-id?
29-35: ```shell
#!/bin/bashLocate the file and print lines around the snippet for inspection
file=$(fd 'contract-event-breakdown.ts' -t f)
echo "Using file: $file"
sed -n '20,60p' "$file"</blockquote></details> </blockquote></details> </details> <!-- This is an auto-generated comment by CodeRabbit for review status -->
...(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
Outdated
Show resolved
Hide resolved
...pp/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
Show resolved
Hide resolved
7bd178b to
c526ea1
Compare
c526ea1 to
4cc0ba1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts (3)
1-6: Add server-only guard to avoid leaking INSIGHT_SERVICE_API_KEY.This util must be server-only per app guidelines; without it, a client import could bundle the key.
+import "server-only"; import { INSIGHT_SERVICE_API_KEY } from "@/constants/server-envs"; import { getVercelEnv } from "@/utils/vercel";
10-19: Add a fetch timeout (AbortController) to prevent hung requests.External call should have a bounded lifetime.
try { - const res = await fetch( + const ac = new AbortController(); + const timeout = setTimeout(() => ac.abort(), 10_000); + const res = await fetch( `https://insight.${thirdwebDomain}.com/service/chains/${chainId}`, { headers: { // service api key required - because this is endpoint is internal "x-service-api-key": INSIGHT_SERVICE_API_KEY, }, + signal: ac.signal, }, ); + clearTimeout(timeout);
7-9: Exported name mismatch likely to break imports.File is named isAnalyticsSupportedForChain.ts but exports isInsightSupportedForChain. Keep the existing public symbol and optionally alias the new name.
-export async function isInsightSupportedForChain( +export async function isAnalyticsSupportedForChain( chainId: number, ): Promise<boolean> { try {return false; } + +// Transitional alias to match internal naming used elsewhere in the PR. +export const isInsightSupportedForChain = isAnalyticsSupportedForChain;Run to confirm call sites expect isAnalyticsSupportedForChain:
#!/bin/bash rg -nP -C2 '\bisAnalyticsSupportedForChain\b' --type=ts rg -nP -C2 '\bisInsightSupportedForChain\b' --type=tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (1)
63-66: Guard against empty/missing aggregations array to avoid runtime error
json.aggregations[0]may be undefined; current code would throw.Apply:
- const json = (await res.json()) as InsightResponse; - const aggregations = Object.values(json.aggregations[0]); + const json = (await res.json()) as InsightResponse; + const aggregations = Object.values(json.aggregations?.[0] ?? {});
♻️ Duplicate comments (1)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (1)
51-61: Make endpoint overridable via env; add timeout and richer error contextAllow staging/local overrides and improve observability on failures.
Apply:
- const url = `https://insight.${thirdwebDomain}.com/v1/events/${params.contractAddress}?${queryParams}`; - - const res = await fetch(url, { - headers: { - "x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID, - }, - }); + const baseUrl = + process.env.NEXT_PUBLIC_INSIGHT_URL ?? + `https://insight.${thirdwebDomain}.com`; + const url = `${baseUrl}/v1/events/${params.contractAddress}?${queryParams}`; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 15_000); + let res: Response; + try { + res = await fetch(url, { + headers: { + "x-client-id": NEXT_PUBLIC_DASHBOARD_CLIENT_ID, + Accept: "application/json", + }, + signal: controller.signal, + }); + } finally { + clearTimeout(timeoutId); + } if (!res.ok) { - throw new Error("Failed to fetch analytics data"); + const body = await res.text().catch(() => ""); + throw new Error( + `Failed to fetch analytics data (${res.status} ${res.statusText})` + + (body ? ` - ${body.slice(0, 300)}` : ""), + ); }
🧹 Nitpick comments (5)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts (2)
21-29: Improve non-OK logging: include status and cap body size.Log status/statusText and truncate response body to avoid noisy/PII logs.
- if (!res.ok) { - const errorText = await res.text(); - console.error( - "failed to fetch chain services for chain", - chainId, - errorText, - ); + if (!res.ok) { + const body = await res.text().catch(() => "<unreadable body>"); + const snippet = body.length > 2048 ? body.slice(0, 2048) + "…<truncated>" : body; + console.error("failed to fetch chain services for chain", { + chainId, + status: res.status, + statusText: res.statusText, + requestId: res.headers.get("x-request-id") ?? undefined, + body: snippet, + }); return false; }
31-37: Harden JSON parsing and streamline catch logging.Guard the shape to avoid throwing on unexpected responses; merge the two errors into one structured log.
- const json = (await res.json()) as { data: boolean }; - - return json.data; + const json = (await res.json()) as unknown; + return typeof (json as any)?.data === "boolean" ? (json as any).data : false; } catch (e) { - console.error(`Error checking analytics support for chain ${chainId}`); - console.error(e); + console.error("Error checking analytics support for chain", { + chainId, + error: e instanceof Error ? e.message : String(e), + }); }Check this server-only util isn’t imported by client components:
#!/bin/bash # Any client component importing this util? rg -nP --type=ts -C2 "^\s*'use client';" apps | sed -n 's/^\(.*\):.*/\1/p' | sort -u | while read -r f; do rg -nP "$(printf '%q' "_utils/isAnalyticsSupportedForChain")" "$f" doneapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (3)
71-76: Validate day format to prevent invalid Date laterAdds a light guard before pushing entries.
Apply:
- "topic_0" in value && - typeof value.topic_0 === "string" && - typeof value.day === "string" + "topic_0" in value && + typeof value.topic_0 === "string" && + typeof value.day === "string" && + /^\d{4}-\d{2}-\d{2}$/.test(value.day)Also applies to: 78-81
104-109: Avoid timezone drift when parsing day-only stringsForce UTC midnight to keep charting consistent across locales.
Apply:
- values.push({ - time: new Date(day), + values.push({ + time: new Date(`${day}T00:00:00Z`), ...value, } as EventBreakdownEntry);
111-113: Tiny cleanup: no need to re-wrap Date objects in sortDirectly use the Date instances you set on
time.Apply:
- return values.sort((a, b) => { - return new Date(a.time).getTime() - new Date(b.time).getTime(); - }); + return values.sort((a, b) => a.time.getTime() - b.time.getTime());
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts(1 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts(5 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts(3 hunks)apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts
- apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Write idiomatic TypeScript with explicit function declarations and return types
Limit each file to one stateless, single-responsibility function for clarity
Re-use shared types from@/typesor localtypes.tsbarrels
Prefer type aliases over interface except for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Choose composition over inheritance; leverage utility types (Partial,Pick, etc.)
Comment only ambiguous logic; avoid restating TypeScript in prose
**/*.{ts,tsx}: Use explicit function declarations and explicit return types in TypeScript
Limit each file to one stateless, single‑responsibility function
Re‑use shared types from@/typeswhere applicable
Prefertypealiases overinterfaceexcept for nominal shapes
Avoidanyandunknownunless unavoidable; narrow generics when possible
Prefer composition over inheritance; use utility types (Partial, Pick, etc.)
Lazy‑import optional features and avoid top‑level side‑effects to reduce bundle size
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Load heavy dependencies inside async paths to keep initial bundle lean (lazy loading)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
apps/{dashboard,playground-web}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/{dashboard,playground-web}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/*(Button, Input, Select, Tabs, Card, Sidebar, Badge, Separator) in dashboard and playground apps
UseNavLinkfor internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Usecn()from@/lib/utilsfor conditional class logic
Use design system tokens (e.g.,bg-card,border-border,text-muted-foreground)
Server Components (Node edge): Start files withimport "server-only";
Client Components (browser): Begin files with'use client';
Always callgetAuthToken()to retrieve JWT from cookies on server side
UseAuthorization: Bearerheader – never embed tokens in URLs
Return typed results (e.g.,Project[],User[]) – avoidany
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stablequeryKeysfor React Query cache hits
ConfigurestaleTime/cacheTimein React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never importposthog-jsin server components
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
apps/{dashboard,playground}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
apps/{dashboard,playground}/**/*.{ts,tsx}: Import UI primitives from@/components/ui/_(e.g., Button, Input, Tabs, Card)
UseNavLinkfor internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names withcn()from@/lib/utilsfor conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start withimport "server-only"; usenext/headers, server‑only env, heavy data fetching, andredirect()where appropriate
Client Components must start with'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: callgetAuthToken()from cookies, sendAuthorization: Bearer <token>header, and return typed results (avoidany)
Client-side data fetching: wrap calls in React Query with descriptive, stablequeryKeysand set sensiblestaleTime/cacheTime(≥ 60s default); keep tokens secret via internal routes or server actions
Do not importposthog-jsin server components (client-side only)
Files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Analytics event name: human-readable `<subject> <verb>` (e.g., "contract deployed"); function: `report<Subject><Verb>` (PascalCase)
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
📚 Learning: 2025-08-29T15:37:38.513Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: AGENTS.md:0-0
Timestamp: 2025-08-29T15:37:38.513Z
Learning: Applies to apps/{dashboard,playground}/src/@/analytics/report.ts : Analytics naming: event name as `<subject> <verb>`; helper function as `report<Subject><Verb>` (PascalCase)
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
📚 Learning: 2025-07-18T19:19:55.613Z
Learnt from: CR
PR: thirdweb-dev/js#0
File: CLAUDE.md:0-0
Timestamp: 2025-07-18T19:19:55.613Z
Learning: Applies to src/@/analytics/report.ts : Review `src/@/analytics/report.ts` before adding analytics events to check for duplicates
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.tsapps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
📚 Learning: 2025-05-27T19:54:55.885Z
Learnt from: MananTank
PR: thirdweb-dev/js#7177
File: apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/public-pages/erc20/erc20.tsx:15-17
Timestamp: 2025-05-27T19:54:55.885Z
Learning: The `fetchDashboardContractMetadata` function from "3rdweb-sdk/react/hooks/useDashboardContractMetadata" has internal error handlers for all promises and cannot throw errors, so external error handling is not needed when calling this function.
Applied to files:
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
🔇 Additional comments (1)
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts (1)
6-8: Rename aligns with Insight API fields (topic_0/day) — LGTMMatches the backend shape and the PR intent.
Run to catch any lingering references to the old keys or grouping:
#!/bin/bash rg -nP --type=ts -C2 '\bevent_signature\b|\btime\b|group_by=time' apps/dashboard/src
| const daysDifference = | ||
| params.startDate && params.endDate | ||
| ? Math.ceil( | ||
| (params.endDate.getTime() - params.startDate.getTime()) / | ||
| (1000 * 60 * 60 * 24), | ||
| ) | ||
| : 30; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Clamp daysDifference to avoid negative/zero limit; prevent bad queries
If endDate < startDate (or same day), limit can go ≤ 0. Clamp to ≥1.
Apply:
- const daysDifference =
- params.startDate && params.endDate
- ? Math.ceil(
- (params.endDate.getTime() - params.startDate.getTime()) /
- (1000 * 60 * 60 * 24),
- )
- : 30;
+ const daysDifference = Math.max(
+ 1,
+ params.startDate && params.endDate
+ ? Math.ceil(
+ (params.endDate.getTime() - params.startDate.getTime()) /
+ (1000 * 60 * 60 * 24),
+ )
+ : 30,
+ );Also applies to: 38-41
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
around lines 29-35 (and similarly 38-41), the computed daysDifference can be 0
or negative when endDate <= startDate which yields an invalid limit; clamp the
calculated daysDifference to a minimum of 1 before using it (e.g., wrap the
Math.ceil expression with Math.max(1, ...)) and apply the same clamp to the
other occurrence on lines 38-41 so limit is always ≥1.

PR-Codex overview
This PR focuses on enhancing the analytics functionality in the dashboard by refining data fetching, modifying aggregation logic, and updating variable names for clarity.
Detailed summary
isAnalyticsSupportedForChain.ts.totaltocountintotal-contract-events.ts.getContractEventAnalytics.contract-events.ts.contract-event-breakdown.ts.Summary by CodeRabbit
New Features
Bug Fixes