Remove previous and next post helper pagination counts#27431
Remove previous and next post helper pagination counts#27431jonatansberg wants to merge 1 commit intomainfrom
Conversation
WalkthroughA 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
e4b08d5 to
d4975a7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4975a78ea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
d4975a7 to
cf1e233
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ghost/core/test/unit/frontend/helpers/next-post.test.js (1)
520-527:⚠️ Potential issue | 🟡 MinorAuth test assertion will fail due to exact match on context.
Line 525 uses
context: {member}as an exact match, but the production code now setscontext: { member, includePagination: false }. This test will fail because the actual context object includes theincludePaginationproperty.Update to use
sinon.matchfor a partial match, consistent with the "with valid post data" test:🐛 Proposed fix
sinon.assert.calledOnceWithExactly( browsePostsStub, sinon.match({ include: 'author,authors,tags,tiers', // Check context passed - context: {member} + context: sinon.match({member, includePagination: false}) }) );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ghost/core/test/unit/frontend/helpers/next-post.test.js` around lines 520 - 527, The assertion uses an exact object match for the browsePostsStub call which fails because production now sends context with extra properties; update the expectation to allow a partial match by replacing the exact context object (context: {member}) with a sinon.match that checks context contains member (e.g. context: sinon.match({member})) so browsePostsStub is asserted with sinon.match({ include: 'author,authors,tags,tiers', context: sinon.match({member}) }); ensure you keep the existing include check and use sinon.match for partial matching of context in the test around browsePostsStub.
🧹 Nitpick comments (1)
ghost/core/test/unit/frontend/helpers/prev-post.test.js (1)
512-519: Auth test should also verifyincludePagination: falsefor consistency.The "should pass the member context" test at lines 512-519 only asserts
context: sinon.match({member}), but doesn't verify thatincludePagination: falseis also present. For consistency with the "with valid post data" test (lines 71-74), consider updating this assertion:♻️ Suggested fix
sinon.assert.calledOnceWithExactly( browsePostsStub, sinon.match({ include: 'author,authors,tags,tiers', // Check context passed - context: sinon.match({member}) + context: sinon.match({member, includePagination: false}) }) );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ghost/core/test/unit/frontend/helpers/prev-post.test.js` around lines 512 - 519, The test assertion for browsePostsStub should also assert includePagination: false; update the sinon.assert.calledOnceWithExactly call to match include: 'author,authors,tags,tiers' and context: sinon.match({member}) plus ensure the options object passed to browsePostsStub contains includePagination: false (e.g., add includePagination: false inside the sinon.match used for the second arg) so it mirrors the "with valid post data" test's expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@ghost/core/test/unit/frontend/helpers/next-post.test.js`:
- Around line 520-527: The assertion uses an exact object match for the
browsePostsStub call which fails because production now sends context with extra
properties; update the expectation to allow a partial match by replacing the
exact context object (context: {member}) with a sinon.match that checks context
contains member (e.g. context: sinon.match({member})) so browsePostsStub is
asserted with sinon.match({ include: 'author,authors,tags,tiers', context:
sinon.match({member}) }); ensure you keep the existing include check and use
sinon.match for partial matching of context in the test around browsePostsStub.
---
Nitpick comments:
In `@ghost/core/test/unit/frontend/helpers/prev-post.test.js`:
- Around line 512-519: The test assertion for browsePostsStub should also assert
includePagination: false; update the sinon.assert.calledOnceWithExactly call to
match include: 'author,authors,tags,tiers' and context: sinon.match({member})
plus ensure the options object passed to browsePostsStub contains
includePagination: false (e.g., add includePagination: false inside the
sinon.match used for the second arg) so it mirrors the "with valid post data"
test's expectations.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c12a6060-b854-4e61-bbed-8674f3b6a277
📒 Files selected for processing (6)
ghost/core/core/frontend/helpers/prev_post.jsghost/core/core/server/api/endpoints/posts-public.jsghost/core/core/server/models/base/plugins/crud.jsghost/core/test/unit/frontend/helpers/next-post.test.jsghost/core/test/unit/frontend/helpers/prev-post.test.jsghost/core/test/unit/server/models/post.test.js
cf1e233 to
670be2a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
ghost/core/test/e2e-api/content/posts.test.js (1)
454-456: Make SQL detection less quote/case sensitive.The current string includes can miss equivalent SQL forms. A regex-based check is more robust across SQL formatting differences.
Proposed tweak
- const postsCountQueries = queries.filter((query) => { - return query.sql.includes('count(') && query.sql.includes('`posts`'); - }); + const postsCountQueries = queries.filter((query) => { + return /\bcount\s*\(/i.test(query.sql) && /\bfrom\s+[`"]?posts[`"]?/i.test(query.sql); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ghost/core/test/e2e-api/content/posts.test.js` around lines 454 - 456, The SQL detection using query.sql.includes('count(') && query.sql.includes('`posts`') is too brittle; replace the string includes in the postsCountQueries filter with a case-insensitive regex test on query.sql (e.g., match \bcount\s*\( and \bposts\b allowing optional backticks) so different quoting/casing/spacing still matches; update the filter that constructs postsCountQueries to use regex.test(query.sql) (or two regex tests) instead of the current includes checks to make detection robust.ghost/core/core/server/models/base/plugins/crud.js (1)
177-182: Consider documenting themetashape change inline.When
includePaginationisfalse,metais intentionally empty. A short inline note here (or updated JSDoc) would reduce accidentalmeta.paginationassumptions by future callers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ghost/core/core/server/models/base/plugins/crud.js` around lines 177 - 182, Add a short inline comment (or update the surrounding JSDoc) where the early return occurs to explicitly state that when includePagination is false the returned object will contain data and an intentionally empty meta (no meta.pagination), so callers should not assume meta.pagination exists; reference the includePagination check and the returned meta object in your note so future readers seeing the return block (data: data, meta: {}) understand the shape change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ghost/core/core/server/models/base/plugins/crud.js`:
- Around line 153-155: Page and limit values from options can be negative
producing negative offsets; clamp parsed values to a minimum of 1 before
computing the offset. After parsing options.limit and options.page into limit
and page in the block guarded by includePagination (variables:
includePagination, options.limit, options.page, limit, page), enforce limit =
Math.max(1, limit) and page = Math.max(1, page) (or equivalent) so offset
calculation later uses non-negative inputs and then proceed to compute offset as
before.
---
Nitpick comments:
In `@ghost/core/core/server/models/base/plugins/crud.js`:
- Around line 177-182: Add a short inline comment (or update the surrounding
JSDoc) where the early return occurs to explicitly state that when
includePagination is false the returned object will contain data and an
intentionally empty meta (no meta.pagination), so callers should not assume
meta.pagination exists; reference the includePagination check and the returned
meta object in your note so future readers seeing the return block (data: data,
meta: {}) understand the shape change.
In `@ghost/core/test/e2e-api/content/posts.test.js`:
- Around line 454-456: The SQL detection using query.sql.includes('count(') &&
query.sql.includes('`posts`') is too brittle; replace the string includes in the
postsCountQueries filter with a case-insensitive regex test on query.sql (e.g.,
match \bcount\s*\( and \bposts\b allowing optional backticks) so different
quoting/casing/spacing still matches; update the filter that constructs
postsCountQueries to use regex.test(query.sql) (or two regex tests) instead of
the current includes checks to make detection robust.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 92e121ab-c1cd-454d-9499-2721f78f9e8d
📒 Files selected for processing (7)
ghost/core/core/frontend/helpers/prev_post.jsghost/core/core/server/api/endpoints/posts-public.jsghost/core/core/server/models/base/plugins/crud.jsghost/core/test/e2e-api/content/posts.test.jsghost/core/test/unit/frontend/helpers/next-post.test.jsghost/core/test/unit/frontend/helpers/prev-post.test.jsghost/core/test/unit/server/models/post.test.js
✅ Files skipped from review due to trivial changes (2)
- ghost/core/test/unit/frontend/helpers/prev-post.test.js
- ghost/core/test/unit/server/models/post.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
- ghost/core/test/unit/frontend/helpers/next-post.test.js
- ghost/core/core/frontend/helpers/prev_post.js
- ghost/core/core/server/api/endpoints/posts-public.js
94cab6c to
724ca98
Compare
ref https://linear.app/ghost/issue/ONC-1653/performance-issues-on-id1223916 The prev_post and next_post helpers only need one adjacent post, but the public browse path also calculated full pagination metadata. Skipping that count for these helper lookups avoids expensive post table scans on large, high-traffic sites while preserving normal pagination for other callers.
724ca98 to
62b7412
Compare
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ghost/core/core/server/models/base/plugins/crud.js (1)
22-22: Optional: preferNumber.parseIntfor consistency with modern JS style rules.Non-blocking, but this will satisfy the static-analysis preference and keep parsing style explicit.
Suggested diff
- const parsedValue = parseInt(value, 10); + const parsedValue = Number.parseInt(value, 10);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ghost/core/core/server/models/base/plugins/crud.js` at line 22, The code uses the global parseInt to convert value into an integer (const parsedValue = parseInt(value, 10)); replace that call with Number.parseInt(value, 10) to align with modern JS style and static-analysis preferences; update the expression where parsedValue is assigned so it uses Number.parseInt while leaving variable and surrounding logic (parsedValue, value) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@ghost/core/core/server/models/base/plugins/crud.js`:
- Line 22: The code uses the global parseInt to convert value into an integer
(const parsedValue = parseInt(value, 10)); replace that call with
Number.parseInt(value, 10) to align with modern JS style and static-analysis
preferences; update the expression where parsedValue is assigned so it uses
Number.parseInt while leaving variable and surrounding logic (parsedValue,
value) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 333b012d-68ff-47db-8e5d-b0b2936b3b7d
📒 Files selected for processing (8)
ghost/core/core/frontend/helpers/prev_post.jsghost/core/core/server/api/endpoints/posts-public.jsghost/core/core/server/models/base/plugins/crud.jsghost/core/core/server/models/base/plugins/sanitize.jsghost/core/test/e2e-api/content/posts.test.jsghost/core/test/unit/frontend/helpers/next-post.test.jsghost/core/test/unit/frontend/helpers/prev-post.test.jsghost/core/test/unit/server/models/post.test.js
✅ Files skipped from review due to trivial changes (1)
- ghost/core/core/frontend/helpers/prev_post.js
🚧 Files skipped from review as they are similar to previous changes (3)
- ghost/core/core/server/api/endpoints/posts-public.js
- ghost/core/test/unit/frontend/helpers/prev-post.test.js
- ghost/core/test/unit/server/models/post.test.js



Summary
This changes the previous and next post helpers so their adjacent-post lookups do not calculate pagination totals. Those helpers only render a single neighboring post, so the extra count query was unnecessary and expensive on sites with large post tables and high traffic.
The no-pagination path is opt-in from the helper context, and the public posts cache key includes that mode so cached helper responses stay separate from normal paginated browse responses.
Testing
pnpm --dir ghost/core test:single test/unit/frontend/helpers/prev-post.test.jspnpm --dir ghost/core test:single test/unit/frontend/helpers/next-post.test.jspnpm --dir ghost/core test:single test/unit/server/models/post.test.jspnpm --dir ghost/core test:single test/e2e-frontend/helpers/next-post.test.jspnpm --dir ghost/core lint