-
-
Notifications
You must be signed in to change notification settings - Fork 11.2k
✨ Added email click rate filter for members #25398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Allows filtering members by their email click through rate (CTR), calculated as the percentage of tracked emails that resulted in at least one click. Follows the same pattern as email open rate.
|
It looks like this PR contains a migration 👀 General requirements
Schema changes
Data changes
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds member email click rate support across the stack: new nullable unsigned integer column Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (2)ghost/core/core/server/**📄 CodeRabbit inference engine (AGENTS.md)
Files:
ghost/core/core/server/services/**📄 CodeRabbit inference engine (AGENTS.md)
Files:
⏰ 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). (9)
🔇 Additional comments (3)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is being reviewed by Cursor Bugbot
Details
Your team is on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle for each member of your team.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (1)
ghost/core/test/e2e-api/admin/members.test.js (1)
769-825: Strengthen ordering assertions for email_click_rate (mirror open_rate tests)Currently only checks field presence. Add ordering checks to validate sort behavior.
.expect(({body}) => { const {members} = body; - // Verify email_click_rate field exists in response - members.forEach((member) => { - assert.ok(member.hasOwnProperty('email_click_rate'), 'Member should have email_click_rate field'); - }); + members.forEach((m) => assert.ok(m.hasOwnProperty('email_click_rate'), 'email_click_rate missing')); + assert.equal(members[0].email_click_rate > members[1].email_click_rate, true, 'Expected first to have greater click rate than second.'); }); @@ .expect(({body}) => { const {members} = body; - // Verify email_click_rate field exists in response - members.forEach((member) => { - assert.ok(member.hasOwnProperty('email_click_rate'), 'Member should have email_click_rate field'); - }); + members.forEach((m) => assert.ok(m.hasOwnProperty('email_click_rate'), 'email_click_rate missing')); + assert.equal(members[0].email_click_rate < members[1].email_click_rate, true, 'Expected first to have smaller click rate than second.'); });If fixtures don’t guarantee differing click rates, consider adjusting or seeding data for deterministic assertions (similar to open_rate test setup).
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (9)
ghost/admin/app/components/members/filter.js(2 hunks)ghost/admin/app/components/members/filters/email-click-rate.js(1 hunks)ghost/admin/app/components/members/filters/index.js(1 hunks)ghost/core/core/server/api/endpoints/utils/serializers/output/members.js(2 hunks)ghost/core/core/server/data/migrations/versions/6.7/2025-11-10-16-16-16-add-members-email-click-rate.js(1 hunks)ghost/core/core/server/data/schema/schema.js(1 hunks)ghost/core/core/server/models/member.js(1 hunks)ghost/core/core/server/services/email-analytics/lib/queries.js(2 hunks)ghost/core/test/e2e-api/admin/members.test.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
ghost/core/core/server/**
📄 CodeRabbit inference engine (AGENTS.md)
Backend core logic should reside under ghost/core/core/server/
Files:
ghost/core/core/server/models/member.jsghost/core/core/server/data/migrations/versions/6.7/2025-11-10-16-16-16-add-members-email-click-rate.jsghost/core/core/server/services/email-analytics/lib/queries.jsghost/core/core/server/data/schema/schema.jsghost/core/core/server/api/endpoints/utils/serializers/output/members.js
ghost/core/core/server/models/**
📄 CodeRabbit inference engine (AGENTS.md)
Backend models should be implemented under ghost/core/core/server/models/
Files:
ghost/core/core/server/models/member.js
ghost/core/core/server/services/**
📄 CodeRabbit inference engine (AGENTS.md)
Backend services should be implemented under ghost/core/core/server/services/
Files:
ghost/core/core/server/services/email-analytics/lib/queries.js
ghost/core/core/server/data/schema/**
📄 CodeRabbit inference engine (AGENTS.md)
Database schema changes must be placed under ghost/core/core/server/data/schema/
Files:
ghost/core/core/server/data/schema/schema.js
ghost/core/core/server/api/**
📄 CodeRabbit inference engine (AGENTS.md)
Backend API route handlers should be placed under ghost/core/core/server/api/
Files:
ghost/core/core/server/api/endpoints/utils/serializers/output/members.js
🧠 Learnings (4)
📚 Learning: 2025-10-15T07:53:49.814Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-10-15T07:53:49.814Z
Learning: Applies to apps/shade/src/index.ts : Export new UI components from src/index.ts
Applied to files:
ghost/admin/app/components/members/filters/index.js
📚 Learning: 2025-06-10T11:07:10.800Z
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 23770
File: apps/admin-x-settings/src/components/settings/email/newsletters/NewsletterDetailModalLabs.tsx:435-450
Timestamp: 2025-06-10T11:07:10.800Z
Learning: In Ghost's newsletter customization features, when promoting a feature from alpha to beta status, the feature flag guards are updated to make the feature available under both the `emailCustomization` (beta) and `emailCustomizationAlpha` (alpha) flags. This is done by either removing conditional guards entirely when the component is already behind both flags, or by updating conditionals to check for either flag.
Applied to files:
ghost/admin/app/components/members/filter.js
📚 Learning: 2025-10-09T17:25:12.439Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-09T17:25:12.439Z
Learning: Applies to ghost/core/core/server/data/schema/** : Database schema changes must be placed under ghost/core/core/server/data/schema/
Applied to files:
ghost/core/core/server/data/schema/schema.js
📚 Learning: 2025-10-08T14:20:28.632Z
Learnt from: CR
Repo: TryGhost/Ghost PR: 0
File: e2e/AGENTS.md:0-0
Timestamp: 2025-10-08T14:20:28.632Z
Learning: Applies to e2e/tests/**/*.ts : Test suite titles should follow: 'Ghost Admin - Feature' or 'Ghost Public - Feature'
Applied to files:
ghost/core/test/e2e-api/admin/members.test.js
🧬 Code graph analysis (2)
ghost/admin/app/components/members/filter.js (1)
ghost/admin/app/components/members/filters/email-click-rate.js (2)
EMAIL_CLICK_RATE_FILTER(3-9)EMAIL_CLICK_RATE_FILTER(3-9)
ghost/core/test/e2e-api/admin/members.test.js (2)
ghost/core/test/e2e-api/admin/members-exporter.test.js (8)
agent(21-21)member(11-16)member(110-113)member(127-130)member(142-150)member(165-169)member(182-188)member(201-204)ghost/core/core/server/api/endpoints/members.js (4)
member(87-87)member(124-124)member(147-147)member(172-172)
⏰ 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). (10)
- GitHub Check: Ghost-CLI tests
- GitHub Check: Legacy tests (Node 22.13.1, sqlite3)
- GitHub Check: Legacy tests (Node 22.13.1, mysql8)
- GitHub Check: Unit tests (Node 22.13.1)
- GitHub Check: Acceptance tests (Node 22.13.1, sqlite3)
- GitHub Check: Acceptance tests (Node 22.13.1, mysql8)
- GitHub Check: Admin tests - Chrome
- GitHub Check: Lint
- GitHub Check: Cursor Bugbot
- GitHub Check: Build & Push
🔇 Additional comments (5)
ghost/core/core/server/models/member.js (1)
391-395: Ordering by email_click_rate mirrors open_rate correctlyNulls last via
IS NOT NULL DESCthen value bydirection. Looks good.Please confirm
directionis validated upstream to onlyASC|DESCto avoid injection throughorderByRaw. If not, normalize before usage:- orderByRaw: `members.email_click_rate IS NOT NULL DESC, members.email_click_rate ${direction}` + const dir = String(direction).toUpperCase() === 'ASC' ? 'ASC' : 'DESC'; + orderByRaw: `members.email_click_rate IS NOT NULL DESC, members.email_click_rate ${dir}`ghost/core/core/server/api/endpoints/utils/serializers/output/members.js (1)
178-179: Expose email_click_rate in API payloadField addition and typedef look consistent with the data model.
Also applies to: 266-266
ghost/admin/app/components/members/filters/email-click-rate.js (1)
1-9: Setting key verified—no issues foundThe
'emailTrackClicks'setting is correctly defined and consistently used throughout the codebase with perfect parity to'emailTrackOpens'. Both filter components follow the same pattern, and the setting is already registered in the admin settings model.ghost/admin/app/components/members/filter.js (2)
4-4: LGTM! Import follows existing pattern.The
EMAIL_CLICK_RATE_FILTERimport is correctly added in alphabetical order and follows the established pattern for filter imports.
52-52: CODE CHANGES VERIFIED.The
emailTrackClickssetting name is correct and consistently used throughout the codebase. The EMAIL_CLICK_RATE_FILTER placement alongside EMAIL_OPEN_RATE_FILTER is logical and properly integrated—it will be correctly excluded when email tracking or the click tracking setting is disabled.
...core/server/data/migrations/versions/6.7/2025-11-10-16-16-16-add-members-email-click-rate.js
Show resolved
Hide resolved
- Scoped CTR calculation to only count clicks from emails member received - Added separate index migration for email_click_rate column - Removed duplicate export in filters - Updated test snapshots
Allows filtering members by their email click through rate (CTR), calculated as the percentage of tracked emails that resulted in at least one click. Follows the same pattern as email open rate.
Got some code for us? Awesome 🎊!
Please take a minute to explain the change you're making:
Why are you making it?
Ghost currently provides an email open rate filter to identify highly engaged members, but lacks a comparable metric for click engagement. Click-through rate (CTR) is a critical engagement metric that shows which members actively interact with email content by clicking links, not just opening emails. This completes Ghost's email engagement analytics by adding the CTR metric alongside the existing open rate.
What does it do?
This adds a "Click rate (all time)" filter to the members list in Ghost Admin. The CTR is calculated as:
(emails clicked / emails sent with click tracking enabled) × 100The calculation requires a minimum of 5 tracked emails before displaying a rate (same threshold as open rate), and updates automatically as new email analytics are processed.
Why is this something Ghost users or developers need?
This gives Ghost publishers better options to segment their audience and improve their reporting. Example use case: https://forum.ghost.org/t/memberlist-cleaning/60818/
Please check your PR against these items:
We appreciate your contribution! 🙏
Note
Adds
email_click_rateto members with DB/storage, analytics computation, API exposure, admin filter, and list ordering, plus tests and migrations.EMAIL_CLICK_RATE_FILTER(“Click rate (all time)”) inghost/admin/app/components/members/filter.jsand export viafilters/index.js.components/members/filters/email-click-rate.js(gated bysettings.emailTrackClicks).email_click_ratein member serializercore/server/api/.../members.js.email_click_rateincore/server/models/member.js(orderRawQuery).core/server/services/email-analytics/lib/queries.jsusing click events and tracked-click emails.email_click_ratecolumn and index tomembers(migrations incore/server/data/migrations/versions/6.7/...) and schema incore/server/data/schema/schema.js.email_click_rateand content-length changes.email_click_rateincore/test/e2e-api/admin/members.test.js.Written by Cursor Bugbot for commit cd057d9. This will update automatically on new commits. Configure here.