Skip to content

Conversation

@prschulz
Copy link
Contributor

@prschulz prschulz commented Dec 10, 2025

ref https://linear.app/ghost/issue/NY-863

  • Allows publishers to easily see net change in subscriptions for a given time period

@cursor
Copy link

cursor bot commented Dec 10, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 10, 2025

Walkthrough

The pull request modifies the growth KPIs component to add net change calculations and UI display. A new netChange value is computed as the sum of new subscriptions and cancelled subscriptions (with cancelled treated as negative). The net change is formatted with a signed prefix. The UI is extended in two locations—the main KPI item tooltip and the paid-members tab's change view tooltip—to display a new "Net change" row when index equals 1. The implementation reuses existing payload fields with defaults to 0 for missing values.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title accurately describes the main change: adding net change display to the tooltip on the paid subscriptions change chart.
Description check ✅ Passed The description is relevant to the changeset, referencing the Linear issue and explaining that net change is added to the subscriptions chart tooltip.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@cmraible cmraible added the deploy-to-staging Optionally deploy PR to staging label Dec 10, 2025
@cmraible cmraible self-requested a review December 10, 2025 19:51
Co-authored-by: peter <peter@ghost.org>
@peterzimon peterzimon force-pushed the cursor/NY-863-add-net-change-tooltip-0f30 branch from 8b7cb06 to 5a02c65 Compare December 18, 2025 14:29
@peterzimon peterzimon self-requested a review December 18, 2025 14:43
Copy link
Contributor

@peterzimon peterzimon left a comment

Choose a reason for hiding this comment

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

Tested it, looks good to me

@peterzimon peterzimon marked this pull request as ready for review January 7, 2026 13:43
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: 0

🧹 Nitpick comments (3)
apps/stats/src/views/Stats/Growth/components/growth-kpis.tsx (3)

510-514: Net change calculation logic is correct.

The calculation correctly accounts for cancelled being stored as negative, and the formatting with "+" prefix for positive values is appropriate.

One minor optimization: the net change is calculated on every formatter invocation (for both "new" and "cancelled" series), but only displayed when index === 1. Consider hoisting this calculation outside the formatter return or memoizing it to avoid redundant computation.

♻️ Optional refactor to reduce redundant calculations
 formatter={(value, name, payload, index) => {
     const rawValue = Number(value);
     let displayValue = '0';
     if (rawValue === 0) {
         displayValue = '0';
     } else {
         displayValue = rawValue < 0 ? formatNumber(rawValue * -1) : formatNumber(rawValue);
     }

-    // Calculate net change (new + cancelled, since cancelled is negative)
-    const newValue = Number(payload?.payload?.new || 0);
-    const cancelledValue = Number(payload?.payload?.cancelled || 0);
-    const netChange = newValue + cancelledValue;
-    const netChangeFormatted = netChange === 0 ? '0' : (netChange > 0 ? `+${formatNumber(netChange)}` : formatNumber(netChange));
-
     return (
         <div className='flex w-full flex-col'>
             {index === 0 &&
-                <div className="mb-1 text-sm font-medium text-foreground">
-                    {payload?.payload?.date}
-                </div>
+                <>
+                    <div className="mb-1 text-sm font-medium text-foreground">
+                        {payload?.payload?.date}
+                    </div>
+                </>
             }
             <div className='flex w-full items-center justify-between gap-4'>
                 <div className='flex items-center gap-1'>
                     <div
                         className="size-2 shrink-0 rounded-full bg-[var(--color-bg)] opacity-50"
                         style={{
                             '--color-bg': `var(--color-${name})`
                         } as React.CSSProperties}
                     />
                     <span className='text-sm text-muted-foreground'>
                         {paidChangeChartConfig[name as keyof typeof paidChangeChartConfig]?.label || name}
                     </span>
                 </div>
                 <div className="ml-auto flex items-baseline gap-0.5 font-mono font-medium tabular-nums text-foreground">
                     {displayValue}
                 </div>
             </div>
             {index === 1 &&
+                (() => {
+                    // Calculate net change only once when displaying
+                    const newValue = Number(payload?.payload?.new || 0);
+                    const cancelledValue = Number(payload?.payload?.cancelled || 0);
+                    const netChange = newValue + cancelledValue;
+                    const netChangeFormatted = netChange === 0 ? '0' : (netChange > 0 ? `+${formatNumber(netChange)}` : formatNumber(netChange));
+                    return (
-                <div className='mt-1 flex w-full items-center justify-between gap-4 border-t pt-1'>
-                    <span className='text-sm text-muted-foreground'>
-                        Net change
-                    </span>
-                    <div className="ml-auto flex items-baseline gap-0.5 font-mono font-medium tabular-nums text-foreground">
-                        {netChangeFormatted}
-                    </div>
-                </div>
+                        <div className='mt-1 flex w-full items-center justify-between gap-4 border-t pt-1'>
+                            <span className='text-sm text-muted-foreground'>
+                                Net change
+                            </span>
+                            <div className="ml-auto flex items-baseline gap-0.5 font-mono font-medium tabular-nums text-foreground">
+                                {netChangeFormatted}
+                            </div>
+                        </div>
+                    );
+                })()
             }
         </div>
     );
 }}

539-548: Net change display is well-implemented.

The UI presentation with the border separator and consistent styling is clean. The condition index === 1 works correctly given the fixed chart configuration with two series.

For improved maintainability, consider using a more explicit condition like checking if it's the last item in the series or comparing against the series name, rather than relying on the hardcoded index.

♻️ Optional refactor for more robust display condition
-{index === 1 &&
+{name === 'cancelled' &&
     <div className='mt-1 flex w-full items-center justify-between gap-4 border-t pt-1'>
         <span className='text-sm text-muted-foreground'>
             Net change
         </span>
         <div className="ml-auto flex items-baseline gap-0.5 font-mono font-medium tabular-nums text-foreground">
             {netChangeFormatted}
         </div>
     </div>
 }

This approach is more explicit and won't break if the series order changes.


510-548: Consider adding test coverage for the net change feature.

The PR description notes that automated tests have not yet been added. Given that this feature affects user-facing tooltips and involves calculation logic, test coverage would help ensure correctness across different scenarios (positive net change, negative net change, zero net change).

Would you like me to help generate test cases for this feature, or open an issue to track adding test coverage?

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 17638a2 and 5a02c65.

📒 Files selected for processing (1)
  • apps/stats/src/views/Stats/Growth/components/growth-kpis.tsx
⏰ 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: Cursor Bugbot

Copy link
Collaborator

@cmraible cmraible left a comment

Choose a reason for hiding this comment

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

Looks good to me! Tested locally and the net change is showing up as expected 👌

@prschulz prschulz changed the title Add net change tooltip 🎨 Added net change to tooltip on change in paid subscriptions chart Jan 8, 2026
@prschulz prschulz merged commit 4f3f35b into main Jan 8, 2026
36 checks passed
@prschulz prschulz deleted the cursor/NY-863-add-net-change-tooltip-0f30 branch January 8, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy-to-staging Optionally deploy PR to staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants