Skip to content

Added multiple subscriptions member filter#28232

Open
kevinansfield wants to merge 1 commit into
mainfrom
multiple-subs-filter-banner
Open

Added multiple subscriptions member filter#28232
kevinansfield wants to merge 1 commit into
mainfrom
multiple-subs-filter-banner

Conversation

@kevinansfield
Copy link
Copy Markdown
Member

@kevinansfield kevinansfield commented May 28, 2026

Summary

  • added exact backend members filter for members with active subscriptions across multiple Stripe customers
  • added labs-gated members banner and raw filter handling in posts admin
  • added per-user dismissal preferences using the accessibility field

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Review Change Stack

Walkthrough

This PR introduces support for filtering and analyzing members by multiple active Stripe customer subscriptions. It registers a new labs feature flag, implements backend API validation and database query logic for the count.active_stripe_customers:>1 filter, enhances the frontend filter state hook to preserve the raw filter string, adds a configurable banner on the Members page with dismissible state persisted to user accessibility preferences, and includes comprehensive testing across all layers.

Possibly related PRs

  • TryGhost/Ghost#27787: Both PRs extend useMembersFilterState with an options object to handle filter preservation, though this PR focuses on the count.active_stripe_customers raw filter while the related PR threads labs configuration for relative-date filter support.
  • TryGhost/Ghost#27964: Both PRs modify apps/posts/src/views/members/members.tsx with prop and config changes; this PR layers multiple-active-Stripe-customers banner logic onto an earlier members page architecture.

Suggested labels

preview

Suggested reviewers

  • weylandswart
  • 9larsons
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% 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 'Added multiple subscriptions member filter' directly and accurately summarizes the main change: adding a backend filter and UI components for members with active subscriptions across multiple Stripe customers.
Description check ✅ Passed The description is clearly related to the changeset, providing specific details about the three main components: backend filter, labs-gated banner with raw filter handling, and per-user dismissal preferences.
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 multiple-subs-filter-banner

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.43.0)
ghost/core/test/e2e-api/admin/members.test.js

[]


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

@kevinansfield kevinansfield force-pushed the multiple-subs-filter-banner branch 4 times, most recently from ff56765 to c973a4c Compare May 28, 2026 17:48
no issue

This adds a labs-gated warning and exact members filter for identifying members with active subscriptions across multiple Stripe customers.
@kevinansfield kevinansfield force-pushed the multiple-subs-filter-banner branch from c973a4c to 36b2594 Compare June 2, 2026 17:59
@kevinansfield kevinansfield marked this pull request as ready for review June 2, 2026 18:56
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 36b2594879

ℹ️ 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".

browse(apiConfig, frame) {
debug('browse');
defaultRelations(frame);
extractActiveStripeCustomersCountFilter(frame);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle the custom filter for bulk member actions

Because the new count.active_stripe_customers:>1 filter is only extracted in the browse/exportCSV path, viewing the banner filter and then using the existing actions menu to add/remove a label or unsubscribe sends that raw filter to PUT /members/bulk. The bulk serializer never sets activeStripeCustomersCount, and member-repository.bulkEdit passes options.filter straight into getFilteredCollectionQuery, so those actions cannot apply to the listed members. Either translate the filter in bulkEdit as well or disable those actions for this raw-filter view.

Useful? React with 👍 / 👎.

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: 1

🧹 Nitpick comments (1)
apps/posts/src/views/members/multiple-active-stripe-customers.ts (1)

42-58: ⚡ Quick win

Consider reducing redundant parsing calls.

The function calls parseAccessibilityPreferences(accessibility) at line 47, then calls getMultipleActiveStripeCustomersBannerPreference(accessibility) at line 48, which internally parses the same string again. While this doesn't affect correctness, it's inefficient.

♻️ Suggested refactor to parse once
 export function buildDismissedMultipleActiveStripeCustomersPreference(
     accessibility: string | null | undefined,
     dismissedCount: number,
     dismissedAt: string
 ): string {
     const preferences = parseAccessibilityPreferences(accessibility);
-    const currentBannerPreference = getMultipleActiveStripeCustomersBannerPreference(accessibility);
+    const currentBannerPreference = preferences.multipleActiveStripeCustomersBanner ?? {};

     return JSON.stringify({
         ...preferences,
         multipleActiveStripeCustomersBanner: {
             ...currentBannerPreference,
             dismissedCount,
             dismissedAt
         }
     });
 }
🤖 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/posts/src/views/members/multiple-active-stripe-customers.ts` around
lines 42 - 58, The function
buildDismissedMultipleActiveStripeCustomersPreference is calling
parseAccessibilityPreferences(accessibility) and then
getMultipleActiveStripeCustomersBannerPreference(accessibility), which causes
the accessibility string to be parsed twice; refactor to parse once by calling
parseAccessibilityPreferences(accessibility) into a local variable and extract
the existing multipleActiveStripeCustomersBanner preference from that parsed
object (instead of calling getMultipleActiveStripeCustomersBannerPreference with
the raw string), then merge dismissedCount/dismissedAt and JSON.stringify the
result; update references to parseAccessibilityPreferences and
getMultipleActiveStripeCustomersBannerPreference accordingly.
🤖 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/posts/src/views/members/members.tsx`:
- Around line 312-314: The banner text in members.tsx uses a fixed verb "were
found" which breaks subject-verb agreement when
multipleActiveStripeCustomersCount === 1; update the JSX inside the div
rendering {formatNumber(multipleActiveStripeCustomersCount)} ... so the verb is
conditional (use "was found" when multipleActiveStripeCustomersCount === 1,
otherwise "were found") while keeping the existing pluralization for
"member/members" based on multipleActiveStripeCustomersCount.

---

Nitpick comments:
In `@apps/posts/src/views/members/multiple-active-stripe-customers.ts`:
- Around line 42-58: The function
buildDismissedMultipleActiveStripeCustomersPreference is calling
parseAccessibilityPreferences(accessibility) and then
getMultipleActiveStripeCustomersBannerPreference(accessibility), which causes
the accessibility string to be parsed twice; refactor to parse once by calling
parseAccessibilityPreferences(accessibility) into a local variable and extract
the existing multipleActiveStripeCustomersBanner preference from that parsed
object (instead of calling getMultipleActiveStripeCustomersBannerPreference with
the raw string), then merge dismissedCount/dismissedAt and JSON.stringify the
result; update references to parseAccessibilityPreferences and
getMultipleActiveStripeCustomersBannerPreference accordingly.
🪄 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: 2fde2b30-2393-4796-a326-805aa2d2dddc

📥 Commits

Reviewing files that changed from the base of the PR and between 80b1fcf and 36b2594.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/config.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (11)
  • apps/admin-x-settings/src/components/settings/advanced/labs/private-features.tsx
  • apps/posts/src/views/members/components/members-actions.tsx
  • apps/posts/src/views/members/hooks/use-members-filter-state.test.tsx
  • apps/posts/src/views/members/hooks/use-members-filter-state.ts
  • apps/posts/src/views/members/members.tsx
  • apps/posts/src/views/members/multiple-active-stripe-customers.test.ts
  • apps/posts/src/views/members/multiple-active-stripe-customers.ts
  • ghost/core/core/server/api/endpoints/utils/serializers/input/members.js
  • ghost/core/core/server/models/member.js
  • ghost/core/core/shared/labs.js
  • ghost/core/test/e2e-api/admin/members.test.js

Comment on lines +312 to +314
<div className="text-sm font-medium">
{formatNumber(multipleActiveStripeCustomersCount)} {multipleActiveStripeCustomersCount === 1 ? 'member' : 'members'} with active subscriptions across multiple Stripe customers were found.
</div>
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 | 🟡 Minor | ⚡ Quick win

Fix subject-verb agreement when count is 1.

When multipleActiveStripeCustomersCount === 1, the banner reads "1 member ... were found". The verb should also be pluralized conditionally.

✏️ Proposed fix
-                                        {formatNumber(multipleActiveStripeCustomersCount)} {multipleActiveStripeCustomersCount === 1 ? 'member' : 'members'} with active subscriptions across multiple Stripe customers were found.
+                                        {formatNumber(multipleActiveStripeCustomersCount)} {multipleActiveStripeCustomersCount === 1 ? 'member' : 'members'} with active subscriptions across multiple Stripe customers {multipleActiveStripeCustomersCount === 1 ? 'was' : 'were'} found.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="text-sm font-medium">
{formatNumber(multipleActiveStripeCustomersCount)} {multipleActiveStripeCustomersCount === 1 ? 'member' : 'members'} with active subscriptions across multiple Stripe customers were found.
</div>
<div className="text-sm font-medium">
{formatNumber(multipleActiveStripeCustomersCount)} {multipleActiveStripeCustomersCount === 1 ? 'member' : 'members'} with active subscriptions across multiple Stripe customers {multipleActiveStripeCustomersCount === 1 ? 'was' : 'were'} found.
</div>
🤖 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/posts/src/views/members/members.tsx` around lines 312 - 314, The banner
text in members.tsx uses a fixed verb "were found" which breaks subject-verb
agreement when multipleActiveStripeCustomersCount === 1; update the JSX inside
the div rendering {formatNumber(multipleActiveStripeCustomersCount)} ... so the
verb is conditional (use "was found" when multipleActiveStripeCustomersCount ===
1, otherwise "were found") while keeping the existing pluralization for
"member/members" based on multipleActiveStripeCustomersCount.

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.

1 participant