Skip to content

Conversation

@joaquim-verges
Copy link
Member

@joaquim-verges joaquim-verges commented Sep 7, 2025


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

  • Improved error handling in isAnalyticsSupportedForChain.ts.
  • Changed aggregation from total to count in total-contract-events.ts.
  • Updated query parameters and logic in getContractEventAnalytics.
  • Adjusted date handling and grouping in contract-events.ts.
  • Modified variable names for clarity in contract-event-breakdown.ts.

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Analytics breakdown now groups events by day and by topic for clearer insights.
    • Added optional end date to analytics range for flexible time-window selection.
    • Results capped per-day to limit noisy topics and keep charts focused.
  • Bug Fixes

    • Corrected total events calculation for improved accuracy.
    • Improved error handling and messaging when analytics data fails to load or is unsupported.
    • Ensured charts remain correctly ordered chronologically using the new daily grouping.

@vercel
Copy link

vercel bot commented Sep 7, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
thirdweb-www Ready Ready Preview Comment Sep 7, 2025 11:29am
4 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
docs-v2 Skipped Skipped Sep 7, 2025 11:29am
nebula Skipped Skipped Sep 7, 2025 11:29am
thirdweb_playground Skipped Skipped Sep 7, 2025 11:29am
wallet-ui Skipped Skipped Sep 7, 2025 11:29am

@vercel vercel bot temporarily deployed to Preview – nebula September 7, 2025 10:25 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 7, 2025 10:25 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 September 7, 2025 10:25 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 7, 2025 10:25 Inactive
@changeset-bot
Copy link

changeset-bot bot commented Sep 7, 2025

⚠️ No Changeset found

Latest commit: 4cc0ba1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

Walkthrough

Analytics 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

Cohort / File(s) Summary
Support check / error logging
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
On non-ok fetch responses, reads response body as text and logs a detailed error including chainId and the response text; returns false. No signature changes.
Event breakdown: day/topic grouping & API windowing
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
Renamed aggregation fields to day and topic_0; added optional endDate parameter and compute daysDifference to set a limit (daysDifference * 10). Grouping changed to group_by=day,topic_0; parsing, accumulation and output now use day/topic_0. URL assigned to a url variable before fetch. Signature updated to accept endDate.
Contract events: day grouping & error detail
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
Compute daysDifference (default 30 or from start/end), switch grouping to day, add limit based on daysDifference, refactor URL into url variable, include response text in thrown errors on non-ok responses, parse day and numeric count, and sort by day-derived timestamp. No exported signature changes.
Total events: count aggregation & headers
apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/total-contract-events.ts
Change aggregation to count() and read count (was total); add x-client-id: NEXT_PUBLIC_DASHBOARD_CLIENT_ID header; throw on non-ok fetch responses. No signature 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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Warning

Review ran into problems

🔥 Problems

Errors were encountered while retrieving linked issues.

Errors (1)
  • TEAM-0000: Entity not found: Issue - Could not find referenced Issue.

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch _Dashboard_Update_analytics_API_integration_and_add_error_logging

Comment @coderabbitai help to get the list of available commands and usage tips.

@joaquim-verges joaquim-verges marked this pull request as ready for review September 7, 2025 10:25
@joaquim-verges joaquim-verges requested review from a team as code owners September 7, 2025 10:25
@github-actions github-actions bot added the Dashboard Involves changes to the Dashboard. label Sep 7, 2025
Copy link
Member Author

joaquim-verges commented Sep 7, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • merge-queue - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

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.

@codecov
Copy link

codecov bot commented Sep 7, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.64%. Comparing base (f789e76) to head (4cc0ba1).
⚠️ Report is 1 commits behind head on main.

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           
Flag Coverage Δ
packages 56.64% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions
Copy link
Contributor

github-actions bot commented Sep 7, 2025

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
thirdweb (esm) 64.01 KB (0%) 1.3 s (0%) 433 ms (+121.16% 🔺) 1.8 s
thirdweb (cjs) 357.32 KB (0%) 7.2 s (0%) 1.6 s (+5.87% 🔺) 8.8 s
thirdweb (minimal + tree-shaking) 5.73 KB (0%) 115 ms (0%) 194 ms (+1494.11% 🔺) 309 ms
thirdweb/chains (tree-shaking) 526 B (0%) 11 ms (0%) 104 ms (+1947.7% 🔺) 115 ms
thirdweb/react (minimal + tree-shaking) 19.15 KB (0%) 383 ms (0%) 105 ms (+292.13% 🔺) 488 ms

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 expects day. 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: Date

Record<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 sort

Date-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 is isInsightSupportedForChain. 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. Prefer parseISO.

-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 merge

Avoid 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 payload

Safer 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 construction

Avoid 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.

📥 Commits

Reviewing files that changed from the base of the PR and between f789e76 and 7bd178b.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
  • 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-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.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
  • 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-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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
  • 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-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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/_utils/isAnalyticsSupportedForChain.ts
  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-events.ts
  • 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-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.ts
  • apps/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.ts
  • apps/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.ts
  • 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 : 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.ts
  • apps/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/timezone param 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 boundary

If 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/bash

Locate 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 -->

@joaquim-verges joaquim-verges force-pushed the _Dashboard_Update_analytics_API_integration_and_add_error_logging branch from 7bd178b to c526ea1 Compare September 7, 2025 11:14
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 7, 2025 11:14 Inactive
@vercel vercel bot temporarily deployed to Preview – nebula September 7, 2025 11:14 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 September 7, 2025 11:14 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 7, 2025 11:14 Inactive
@joaquim-verges joaquim-verges force-pushed the _Dashboard_Update_analytics_API_integration_and_add_error_logging branch from c526ea1 to 4cc0ba1 Compare September 7, 2025 11:15
@vercel vercel bot temporarily deployed to Preview – nebula September 7, 2025 11:15 Inactive
@vercel vercel bot temporarily deployed to Preview – wallet-ui September 7, 2025 11:15 Inactive
@vercel vercel bot temporarily deployed to Preview – thirdweb_playground September 7, 2025 11:15 Inactive
@vercel vercel bot temporarily deployed to Preview – docs-v2 September 7, 2025 11:15 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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=ts
apps/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 context

Allow 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"
done
apps/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 later

Adds 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 strings

Force 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 sort

Directly 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 7bd178b and 4cc0ba1.

📒 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 @/types or local types.ts barrels
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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 @/types where applicable
Prefer type aliases over interface except for nominal shapes
Avoid any and unknown unless 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.ts
  • apps/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.ts
  • apps/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
Use NavLink for internal navigation with automatic active states in dashboard and playground apps
Use Tailwind CSS only – no inline styles or CSS modules
Use cn() from @/lib/utils for conditional class logic
Use design system tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components (Node edge): Start files with import "server-only";
Client Components (browser): Begin files with 'use client';
Always call getAuthToken() to retrieve JWT from cookies on server side
Use Authorization: Bearer header – never embed tokens in URLs
Return typed results (e.g., Project[], User[]) – avoid any
Wrap client-side data fetching calls in React Query (@tanstack/react-query)
Use descriptive, stable queryKeys for React Query cache hits
Configure staleTime/cacheTime in React Query based on freshness (default ≥ 60s)
Keep tokens secret via internal API routes or server actions
Never import posthog-js in server components

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
  • apps/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)
Use NavLink for internal navigation to get active state handling
Use Tailwind CSS for styling; no inline styles
Merge class names with cn() from @/lib/utils for conditional classes
Stick to design tokens (e.g., bg-card, border-border, text-muted-foreground)
Server Components must start with import "server-only"; use next/headers, server‑only env, heavy data fetching, and redirect() where appropriate
Client Components must start with 'use client'; handle interactivity with hooks and browser APIs
Server-side data fetching: call getAuthToken() from cookies, send Authorization: Bearer <token> header, and return typed results (avoid any)
Client-side data fetching: wrap calls in React Query with descriptive, stable queryKeys and set sensible staleTime/cacheTime (≥ 60s default); keep tokens secret via internal routes or server actions
Do not import posthog-js in server components (client-side only)

Files:

  • apps/dashboard/src/app/(app)/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/utils/contract-event-breakdown.ts
  • apps/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.ts
  • apps/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) — LGTM

Matches 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

Comment on lines +29 to +35
const daysDifference =
params.startDate && params.endDate
? Math.ceil(
(params.endDate.getTime() - params.startDate.getTime()) /
(1000 * 60 * 60 * 24),
)
: 30;
Copy link
Contributor

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.

@joaquim-verges joaquim-verges merged commit a94cf80 into main Sep 7, 2025
25 checks passed
@joaquim-verges joaquim-verges deleted the _Dashboard_Update_analytics_API_integration_and_add_error_logging branch September 7, 2025 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Dashboard Involves changes to the Dashboard.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants