Skip to content

feat(dashboard): add weekly users metrics for projects#1412

Open
mantrakp04 wants to merge 1 commit intodevfrom
fix/daily-to-weekly-active-user
Open

feat(dashboard): add weekly users metrics for projects#1412
mantrakp04 wants to merge 1 commit intodevfrom
fix/daily-to-weekly-active-user

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

@mantrakp04 mantrakp04 commented May 5, 2026

  • Introduced a new API endpoint to fetch weekly and daily user metrics for managed projects.
  • Updated the dashboard to utilize this new endpoint, replacing the previous daily active users data.
  • Created a new component to visualize weekly users metrics in the project cards.
  • Refactored existing components to accommodate the new data structure and ensure proper rendering of user activity charts.

This change enhances the analytics capabilities of the dashboard, providing better insights into user engagement over time.

Summary by CodeRabbit

  • New Features
    • Updated project metrics endpoint to provide weekly user counts alongside daily activity data.
    • Dashboard project page now displays weekly user statistics instead of daily active users for improved trend analysis.
    • Refreshed metric visualization to show aggregated weekly user metrics with supporting daily activity chart.

- Introduced a new API endpoint to fetch weekly and daily user metrics for managed projects.
- Updated the dashboard to utilize this new endpoint, replacing the previous daily active users data.
- Created a new component to visualize weekly users metrics in the project cards.
- Refactored existing components to accommodate the new data structure and ensure proper rendering of user activity charts.

This change enhances the analytics capabilities of the dashboard, providing better insights into user engagement over time.
Copilot AI review requested due to automatic review settings May 5, 2026 17:20
@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment May 5, 2026 5:26pm
stack-backend Ready Ready Preview, Comment May 5, 2026 5:26pm
stack-dashboard Ready Ready Preview, Comment May 5, 2026 5:26pm
stack-demo Ready Ready Preview, Comment May 5, 2026 5:26pm
stack-docs Ready Ready Preview, Comment May 5, 2026 5:26pm
stack-preview-backend Ready Ready Preview, Comment May 5, 2026 5:26pm
stack-preview-dashboard Ready Ready Preview, Comment May 5, 2026 5:26pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

The PR migrates metrics display from Daily Active Users (DAU) to Weekly Users across the backend API and dashboard frontend. The backend endpoint restructures ClickHouse queries to fetch weekly user counts and daily user series separately, updating the response schema. The dashboard frontend updates to consume the new schema and passes weekly user metrics through component props to render a redesigned weekly users metric instead of the prior DAU sparkline.

Changes

Weekly Users Metrics Migration

Layer / File(s) Summary
API Response Schema
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx
Response validator updated: each project now maps to an object with weekly_users (number) and daily_users (array of { date, activity }), replacing the flat MetricsDataPointsSchema.
Backend Query Restructuring
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx
ClickHouse queries split into rows (for weekly user counts) and dailyRows (for daily distinct users); results assembled into per-project structures with both weekly_users and daily_users populated from respective query results.
Frontend State & Fetching
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
Fetch effect switches from /internal/projects-dau to /internal/projects-weekly-users; state management replaced with projectWeeklyUsers and projectWeeklyUsersChart to store weekly count and daily series separately.
Component Props & Wiring
apps/dashboard/src/components/project-card.tsx
ProjectCard prop type updated: dau prop removed; weeklyUsers (number) and weeklyUsersChart (array) props added; metric component switched from ProjectDauSparkline to ProjectWeeklyUsersMetric.
Metric Display Component
apps/dashboard/src/components/project-weekly-users-metric.tsx
Component renamed from ProjectDauSparkline to ProjectWeeklyUsersMetric with updated props (weeklyUsers added); main display value now shows weeklyUsers count; unit label changed to "users/wk"; chart gradient and tooltip labels updated to reflect weekly user context.

Sequence Diagram

sequenceDiagram
    participant Dashboard as Dashboard Client
    participant API as Backend API Endpoint
    participant CH as ClickHouse
    participant Card as ProjectCard
    participant Metric as ProjectWeeklyUsersMetric

    Dashboard->>API: GET /internal/projects-weekly-users
    activate API
    API->>CH: Query weekly user counts
    activate CH
    CH-->>API: weeklyUsers per project
    deactivate CH
    API->>CH: Query daily distinct users
    activate CH
    CH-->>API: dailyUsers per project/day
    deactivate CH
    API-->>Dashboard: { projects: { [id]: { weekly_users, daily_users } } }
    deactivate API
    
    Dashboard->>Dashboard: Update state:<br/>projectWeeklyUsers<br/>projectWeeklyUsersChart
    Dashboard->>Card: weeklyUsers=<count><br/>weeklyUsersChart=<data>
    Card->>Metric: weeklyUsers=<count><br/>data=<data>
    Metric->>Metric: Render weekly count<br/>+ daily activity chart
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 From DAU to weekly we hop,

New queries split, fresh data on top,

Components renamed with metrics so bright,

Weekly users dancing in dashboard light! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding weekly users metrics visualization to the dashboard project cards.
Description check ✅ Passed The description clearly outlines the key changes: new API endpoint, dashboard integration, new component, and refactored components with proper context about analytics enhancements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/daily-to-weekly-active-user

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 5, 2026

Greptile Summary

This PR renames the projects-dau internal endpoint to projects-weekly-users and extends the response to include both a 7-day unique user count and a per-day breakdown. The dashboard project cards are updated to display the weekly unique user total as the headline metric alongside the existing daily sparkline.

  • Backend: Two sequential ClickHouse queries now replace the single daily query; however, they share one try/catch block, so a failure in the second query silently discards the successfully-fetched weekly count. The data accumulator also uses a plain Record object with dynamic project-ID keys rather than a Map.
  • Dashboard: Client-side state is cleanly split into projectWeeklyUsers and projectWeeklyUsersChart maps, with robust runtime validation of the API response shape.
  • Component: ProjectWeeklyUsersMetric renders the weekly count as the headline number and keeps the daily sparkline for trend context.

Confidence Score: 3/5

The dashboard and component changes are safe, but the backend route has a failure-mode where a transient error on the second ClickHouse query silently zeroes out the weekly count that was already successfully fetched.

The two ClickHouse queries share a single try/catch: if the daily query fails after the weekly query has already returned data, the handler discards the weekly results and returns all-zero counts. This is an observable regression in reliability relative to the single-query original.

apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx — the shared try/catch and sequential queries both warrant a closer look before merging.

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx Renamed from projects-dau; adds a second ClickHouse query for weekly unique users, but both queries share a single try/catch that discards partial results on failure, and they run sequentially instead of in parallel.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx Switches from projects-dau to projects-weekly-users endpoint; state is split into two Maps for weekly count and chart data; client-side parsing and validation logic is clean.
apps/dashboard/src/components/project-card.tsx Minimal prop rename from dau to weeklyUsers/weeklyUsersChart and component swap — straightforward change with no issues.
apps/dashboard/src/components/project-weekly-users-metric.tsx Renamed from project-dau-sparkline; now accepts a separate weeklyUsers count displayed as the headline number while the sparkline still shows the daily breakdown.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Dashboard as Dashboard (page-client.tsx)
    participant API as Backend /internal/projects-weekly-users
    participant CH as ClickHouse

    Browser->>Dashboard: Load projects page
    Dashboard->>API: GET /internal/projects-weekly-users
    API->>CH: Query 1 — weekly uniq users (7-day window)
    CH-->>API: rows [{projectId, weeklyUsers}]
    API->>CH: Query 2 — daily uniq users per day (7-day window)
    CH-->>API: dailyRows [{projectId, day, dailyUsers}]
    API-->>Dashboard: {projects: {id: {weekly_users, daily_users[]}}}
    Dashboard->>Dashboard: Split into weeklyUsersMap + weeklyUsersChartMap
    Dashboard-->>Browser: Render ProjectCard with ProjectWeeklyUsersMetric
    Note over Browser: Shows X users/wk headline + 7-day sparkline chart
Loading

Comments Outside Diff (1)

  1. apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx, line 79-137 (link)

    P1 Single try/catch swallows partial success

    Both ClickHouse queries run inside the same try block, so if the first query (weekly aggregation) succeeds and the second (daily breakdown) throws, the catch handler returns byProject with all weekly_users still at 0 — the data from the successful first query is never applied. A ClickHouse transient error on the daily query would silently zero out the weekly count that was already fetched. Splitting into independent try/catch blocks (or using Promise.allSettled) would let the handler return partial data instead of discarding everything.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx
    Line: 79-137
    
    Comment:
    **Single try/catch swallows partial success**
    
    Both ClickHouse queries run inside the same `try` block, so if the first query (weekly aggregation) succeeds and the second (daily breakdown) throws, the catch handler returns `byProject` with all `weekly_users` still at `0` — the data from the successful first query is never applied. A ClickHouse transient error on the daily query would silently zero out the weekly count that was already fetched. Splitting into independent try/catch blocks (or using `Promise.allSettled`) would let the handler return partial data instead of discarding everything.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code Fix in Cursor Fix in Codex

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx:79-137
**Single try/catch swallows partial success**

Both ClickHouse queries run inside the same `try` block, so if the first query (weekly aggregation) succeeds and the second (daily breakdown) throws, the catch handler returns `byProject` with all `weekly_users` still at `0` — the data from the successful first query is never applied. A ClickHouse transient error on the daily query would silently zero out the weekly count that was already fetched. Splitting into independent try/catch blocks (or using `Promise.allSettled`) would let the handler return partial data instead of discarding everything.

### Issue 2 of 3
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx:79-125
**Sequential ClickHouse queries double API latency**

The weekly and daily queries are independent and fired sequentially. Since neither depends on the other's result, they can be issued concurrently with `Promise.all`, roughly halving the round-trip time for this endpoint.

### Issue 3 of 3
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx:57-63
**Plain object with dynamic keys — use `Map` instead**

`byProject` is a plain `Record<string, …>` indexed by ClickHouse-returned project IDs, which violates the team rule of using `Map<K, V>` for dynamic-key collections to avoid prototype pollution. A project ID equal to `__proto__`, `constructor`, or `toString` would silently corrupt the object. Switching to `new Map<string, …>()` with `map.set` / `map.get` eliminates this class of risk.

Reviews (1): Last reviewed commit: "feat(dashboard): add weekly users metric..." | Re-trigger Greptile

Comment on lines 79 to 125
@@ -92,13 +124,13 @@ export const GET = createSmartRouteHandler({
},
format: "JSONEachRow",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Sequential ClickHouse queries double API latency

The weekly and daily queries are independent and fired sequentially. Since neither depends on the other's result, they can be issued concurrently with Promise.all, roughly halving the round-trip time for this endpoint.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx
Line: 79-125

Comment:
**Sequential ClickHouse queries double API latency**

The weekly and daily queries are independent and fired sequentially. Since neither depends on the other's result, they can be issued concurrently with `Promise.all`, roughly halving the round-trip time for this endpoint.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

Comment on lines 57 to +63

const byProject: Record<string, { date: string, activity: number }[]> = {};
const byProject: Record<string, { weekly_users: number, daily_users: { date: string, activity: number }[] }> = {};
for (const id of projectIds) {
byProject[id] = emptySeries();
byProject[id] = {
weekly_users: 0,
daily_users: emptySeries(),
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Plain object with dynamic keys — use Map instead

byProject is a plain Record<string, …> indexed by ClickHouse-returned project IDs, which violates the team rule of using Map<K, V> for dynamic-key collections to avoid prototype pollution. A project ID equal to __proto__, constructor, or toString would silently corrupt the object. Switching to new Map<string, …>() with map.set / map.get eliminates this class of risk.

Rule Used: Use Map<A, B> instead of plain objects when using ... (source)

Learned From
stack-auth/stack-auth#769
stack-auth/stack-auth#835
stack-auth/stack-auth#839
+4 more

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx
Line: 57-63

Comment:
**Plain object with dynamic keys — use `Map` instead**

`byProject` is a plain `Record<string, …>` indexed by ClickHouse-returned project IDs, which violates the team rule of using `Map<K, V>` for dynamic-key collections to avoid prototype pollution. A project ID equal to `__proto__`, `constructor`, or `toString` would silently corrupt the object. Switching to `new Map<string, …>()` with `map.set` / `map.get` eliminates this class of risk.

**Rule Used:** Use Map<A, B> instead of plain objects when using ... ([source](https://app.greptile.com/review/custom-context?memory=cd0e08f7-0df2-43c8-8c71-97091bba4120))

**Learned From**
[stack-auth/stack-auth#769](https://github.com/stack-auth/stack-auth/pull/769)
[stack-auth/stack-auth#835](https://github.com/stack-auth/stack-auth/pull/835)
[stack-auth/stack-auth#839](https://github.com/stack-auth/stack-auth/pull/839)
*+4 more*

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Cursor Fix in Codex

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds project-level “weekly users” analytics to the dashboard by introducing a new internal backend endpoint that returns both a weekly unique-user count and a 7-day daily series, and updating the Projects page/cards to consume and visualize that data.

Changes:

  • Added /internal/projects-weekly-users backend endpoint returning { weekly_users, daily_users[] } per managed project.
  • Updated the Projects page client to fetch and map the new response into per-project weekly counts + chart series.
  • Replaced the project-card sparkline with a new weekly-users metric component and updated labeling/gradient IDs accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
apps/dashboard/src/components/project-weekly-users-metric.tsx Renames/reworks the metric widget to display weekly users and a daily users sparkline.
apps/dashboard/src/components/project-card.tsx Switches the card footer visualization to the new weekly users metric component and new props.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx Fetches the new weekly-users endpoint and plumbs weekly count + daily series into ProjectCard.
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx Implements the new internal API endpoint and ClickHouse queries for weekly and daily user metrics.
Comments suppressed due to low confidence (1)

apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx:107

  • The weekly and daily ClickHouse queries are wrapped in a single try/catch. If the weekly query succeeds but the daily query throws (or vice versa), the catch returns the all-zero fallback and discards any partial results already fetched. Consider handling these queries independently (e.g., separate try/catch blocks or Promise.allSettled) so one failing query doesn’t zero-out the other metric.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 133 to +140
const body = await response.json();
if (body == null || typeof body !== "object" || !("projects" in body) || body.projects == null || typeof body.projects !== "object") {
console.warn("[projects-dau] unexpected body", body);
console.warn("[projects-weekly-users] unexpected body", body);
return;
}
const map = new Map<string, { date: string, activity: number }[]>();
for (const [projectId, series] of Object.entries(body.projects as Record<string, unknown>)) {
if (!Array.isArray(series)) continue;
const weeklyUsersMap = new Map<string, number>();
const weeklyUsersChartMap = new Map<string, { date: string, activity: number }[]>();
for (const [projectId, value] of Object.entries(body.projects)) {
Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx (1)

76-141: ⚡ Quick win

Two independent ClickHouse queries run sequentially, and a failure of the second query discards results from the first.

Because both queries share a single try/catch, if the weekly-users query succeeds and rows is populated but the daily-users query throws, the catch block returns the zero-initialized byProject — silently losing the successfully fetched weekly_users data. Parallelizing with Promise.all also halves the latency.

♻️ Proposed parallel execution
-   let rows: { projectId: string, weeklyUsers: number }[] = [];
-   let dailyRows: { projectId: string, day: string, dailyUsers: number }[] = [];
    try {
      const clickhouseClient = getClickhouseAdminClient();
-     const result = await clickhouseClient.query({
-       query: `...weekly...`,
-       query_params: { ... },
-       format: "JSONEachRow",
-     });
-     rows = await result.json();
-
-     const dailyResult = await clickhouseClient.query({
-       query: `...daily...`,
-       query_params: { ... },
-       format: "JSONEachRow",
-     });
-     dailyRows = await dailyResult.json();
+     const [weeklyResult, dailyResult] = await Promise.all([
+       clickhouseClient.query({
+         query: `...weekly...`,
+         query_params: { ... },
+         format: "JSONEachRow",
+       }),
+       clickhouseClient.query({
+         query: `...daily...`,
+         query_params: { ... },
+         format: "JSONEachRow",
+       }),
+     ]);
+     const [rows, dailyRows]: [
+       { projectId: string, weeklyUsers: number }[],
+       { projectId: string, day: string, dailyUsers: number }[]
+     ] = await Promise.all([weeklyResult.json(), dailyResult.json()]);
    } catch (error) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx`
around lines 76 - 141, The current single try/catch wraps two sequential
ClickHouse queries (the weekly query that sets rows and the daily query that
sets dailyRows), so if the second query fails you silently return the zeroed
byProject and lose the successful weekly result; fix this by executing both
queries concurrently (use Promise.all to run clickhouseClient.query(...) for the
weekly and daily queries in parallel), await both responses and call .json() for
each result, or alternatively isolate each query in its own try/catch so a
failure in the daily query does not discard rows from the weekly query; refer to
clickhouseClient.query, rows, dailyRows, and the outer try/catch/captureError to
locate where to change execution to parallel or split error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx`:
- Around line 142-144: The loop assigns into
byProject[row.projectId].weekly_users without checking that
byProject[row.projectId] exists; add a defensive guard in the rows iteration
(and mirror it in the dailyRows loop) to skip or initialize when
byProject[row.projectId] is undefined to avoid TypeError. Specifically, inside
the for (const row of rows) { ... } and for (const row of dailyRows) { ... }
blocks, check if (byProject[row.projectId]) before setting
weekly_users/daily_users (or create a new entry if your logic requires
initialization), and ensure you reference the same property names weekly_users
and daily_users on the validated object.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:
- Around line 126-170: Remove the inner try/catch and the console.warn bail-outs
so errors propagate to runAsynchronously; specifically, in the runAsynchronously
callback that calls appInternals.sendRequest("/internal/projects-weekly-users",
...) remove the catch block and replace the response failure and unexpected-body
console.warns with thrown Errors (or throw new Error with a descriptive message)
so runAsynchronously can handle logging/alerts, while preserving the logic that
builds weeklyUsersMap and weeklyUsersChartMap and only calls
setProjectWeeklyUsers / setProjectWeeklyUsersChart when cancelled is false.

---

Nitpick comments:
In `@apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx`:
- Around line 76-141: The current single try/catch wraps two sequential
ClickHouse queries (the weekly query that sets rows and the daily query that
sets dailyRows), so if the second query fails you silently return the zeroed
byProject and lose the successful weekly result; fix this by executing both
queries concurrently (use Promise.all to run clickhouseClient.query(...) for the
weekly and daily queries in parallel), await both responses and call .json() for
each result, or alternatively isolate each query in its own try/catch so a
failure in the daily query does not discard rows from the weekly query; refer to
clickhouseClient.query, rows, dailyRows, and the outer try/catch/captureError to
locate where to change execution to parallel or split error handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1f34ad5-ba86-47ea-b522-335d59f4130d

📥 Commits

Reviewing files that changed from the base of the PR and between 7a54e82 and 12ccfae.

📒 Files selected for processing (4)
  • apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
  • apps/dashboard/src/components/project-card.tsx
  • apps/dashboard/src/components/project-weekly-users-metric.tsx

Comment on lines 142 to +144
for (const row of rows) {
byProject[row.projectId].weekly_users = Number(row.weeklyUsers);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unguarded property access on byProject[row.projectId] — potential TypeError if ClickHouse returns an unexpected row.

byProject is a Record<string, …> and TypeScript will not flag byProject[row.projectId] as possibly undefined. At runtime, if the ClickHouse result includes a projectId outside projectIds (e.g., due to a misbehaving ClickHouse node or stale query cache), line 143 throws TypeError: Cannot set properties of undefined. The same guard should be applied to the dailyRows loop at line 147 as a belt-and-suspenders measure.

🛡️ Proposed defensive guard
  for (const row of rows) {
+   if (!(row.projectId in byProject)) continue;
    byProject[row.projectId].weekly_users = Number(row.weeklyUsers);
  }

  const dailyIndex = new Map<string, Map<string, number>>();
  for (const row of dailyRows) {
+   if (!(row.projectId in byProject)) continue;
    const dayKey = row.day.split("T")[0];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/src/app/api/latest/internal/projects-weekly-users/route.tsx`
around lines 142 - 144, The loop assigns into
byProject[row.projectId].weekly_users without checking that
byProject[row.projectId] exists; add a defensive guard in the rows iteration
(and mirror it in the dailyRows loop) to skip or initialize when
byProject[row.projectId] is undefined to avoid TypeError. Specifically, inside
the for (const row of rows) { ... } and for (const row of dailyRows) { ... }
blocks, check if (byProject[row.projectId]) before setting
weekly_users/daily_users (or create a new entry if your logic requires
initialization), and ensure you reference the same property names weekly_users
and daily_users on the validated object.

Comment on lines 126 to 170
runAsynchronously(async () => {
try {
const response = await appInternals.sendRequest("/internal/projects-dau", {}, "client");
const response = await appInternals.sendRequest("/internal/projects-weekly-users", {}, "client");
if (!response.ok) {
console.warn("[projects-dau] request failed", response.status, await response.text());
console.warn("[projects-weekly-users] request failed", response.status, await response.text());
return;
}
const body = await response.json();
if (body == null || typeof body !== "object" || !("projects" in body) || body.projects == null || typeof body.projects !== "object") {
console.warn("[projects-dau] unexpected body", body);
console.warn("[projects-weekly-users] unexpected body", body);
return;
}
const map = new Map<string, { date: string, activity: number }[]>();
for (const [projectId, series] of Object.entries(body.projects as Record<string, unknown>)) {
if (!Array.isArray(series)) continue;
const weeklyUsersMap = new Map<string, number>();
const weeklyUsersChartMap = new Map<string, { date: string, activity: number }[]>();
for (const [projectId, value] of Object.entries(body.projects)) {
if (value == null || typeof value !== "object") {
continue;
}
const weeklyUsers = "weekly_users" in value ? value.weekly_users : undefined;
if (typeof weeklyUsers === "number") {
weeklyUsersMap.set(projectId, weeklyUsers);
}
const dailyUsers = "daily_users" in value ? value.daily_users : undefined;
if (!Array.isArray(dailyUsers)) {
continue;
}
const points: { date: string, activity: number }[] = [];
for (const point of series) {
if (point != null && typeof point === "object" && "date" in point && "activity" in point && typeof (point as any).date === "string" && typeof (point as any).activity === "number") {
points.push({ date: (point as any).date, activity: (point as any).activity });
for (const point of dailyUsers) {
if (point != null && typeof point === "object" && "date" in point && "activity" in point) {
const date = point.date;
const activity = point.activity;
if (typeof date === "string" && typeof activity === "number") {
points.push({ date, activity });
}
}
}
map.set(projectId, points);
weeklyUsersChartMap.set(projectId, points);
}
if (!cancelled) {
setProjectDau(map);
setProjectWeeklyUsers(weeklyUsersMap);
setProjectWeeklyUsersChart(weeklyUsersChartMap);
}
} catch (e) {
console.warn("[projects-dau] fetch error", e);
console.warn("[projects-weekly-users] fetch error", e);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Inner try-catch-all with console.warn violates the project guidelines and silences every error from runAsynchronously.

The catch block at line 168 catches every possible error and swallows it with console.warn, which is exactly the pattern the guidelines prohibit: "NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error) (or similar). Use runAsynchronously or runAsynchronouslyWithAlert instead as it deals with error logging." Because all errors are consumed internally, runAsynchronously never sees them and provides no logging benefit. The console.warn calls for a failed HTTP response (line 130) and unexpected body shape (line 135) have the same problem — they silently bail out instead of propagating through runAsynchronously.

🛡️ Proposed fix — let `runAsynchronously` handle error logging
  useEffect(() => {
    let cancelled = false;
    runAsynchronously(async () => {
-     try {
        const response = await appInternals.sendRequest("/internal/projects-weekly-users", {}, "client");
        if (!response.ok) {
-         console.warn("[projects-weekly-users] request failed", response.status, await response.text());
-         return;
+         throw new Error(`[projects-weekly-users] request failed: ${response.status} ${await response.text()}`);
        }
        const body = await response.json();
        if (body == null || typeof body !== "object" || !("projects" in body) || body.projects == null || typeof body.projects !== "object") {
-         console.warn("[projects-weekly-users] unexpected body", body);
-         return;
+         throw new Error("[projects-weekly-users] unexpected response body");
        }
        const weeklyUsersMap = new Map<string, number>();
        const weeklyUsersChartMap = new Map<string, { date: string, activity: number }[]>();
        for (const [projectId, value] of Object.entries(body.projects)) {
          // ... existing parsing logic unchanged ...
        }
        if (!cancelled) {
          setProjectWeeklyUsers(weeklyUsersMap);
          setProjectWeeklyUsersChart(weeklyUsersChartMap);
        }
-     } catch (e) {
-       console.warn("[projects-weekly-users] fetch error", e);
-     }
    });
    return () => {
      cancelled = true;
    };
  }, [appInternals, rawProjects.length]);

As per coding guidelines: "NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error) (or similar). Use runAsynchronously or runAsynchronouslyWithAlert instead as it deals with error logging."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
around lines 126 - 170, Remove the inner try/catch and the console.warn
bail-outs so errors propagate to runAsynchronously; specifically, in the
runAsynchronously callback that calls
appInternals.sendRequest("/internal/projects-weekly-users", ...) remove the
catch block and replace the response failure and unexpected-body console.warns
with thrown Errors (or throw new Error with a descriptive message) so
runAsynchronously can handle logging/alerts, while preserving the logic that
builds weeklyUsersMap and weeklyUsersChartMap and only calls
setProjectWeeklyUsers / setProjectWeeklyUsersChart when cancelled is false.

Copy link
Copy Markdown

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Suggestion:

formatDay function constructs a Date from YYYY-MM-DD string without UTC timezone, causing potential off-by-one day errors in some timezones

Fix on Vercel

const response = await appInternals.sendRequest("/internal/projects-weekly-users", {}, "client");
if (!response.ok) {
console.warn("[projects-dau] request failed", response.status, await response.text());
console.warn("[projects-weekly-users] request failed", response.status, await response.text());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error state handling when projects-weekly-users API request fails causes UI to conflate failed data loads with legitimate zero activity

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants