Skip to content

Added is all of operator to member label filters#27945

Closed
kevinansfield wants to merge 3 commits into
mainfrom
codex/ber-3664-member-label-is-all
Closed

Added is all of operator to member label filters#27945
kevinansfield wants to merge 3 commits into
mainfrom
codex/ber-3664-member-label-is-all

Conversation

@kevinansfield

@kevinansfield kevinansfield commented May 18, 2026

Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/BER-3664/add-is-all-of-operator-to-member-label-filters

Member label filters could only express "has any of these labels" or "has none of these labels", so there was no way to find members that carry every selected label.

  • added an "is all of" operator to the member Label filter, serialized as grouped NQL ((label:alpha+label:vip)) so the backend ANDs the label clauses
  • grouped all-of filters round-trip back into a single filter row, while ungrouped repeated label clauses still parse as separate any-of rows
  • outer-grouping detection re-parses the filter with a probe predicate AND-ed alongside instead of using a hand-rolled paren/quote scanner, so NQL's own quoting rules apply (a string scanner misread trailing backslashes in quoted values, breaking round-trips)
  • all-of handling is driven by field config rather than special-cased for labels: any set field that declares the is-all operator gets serialization and parsing for free, with alias keys (labels) coming from the existing parseKeys mechanism, so extending it to e.g. tiers is a one-line change

@coderabbitai

coderabbitai Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 493f16a2-10b3-427c-bbc4-6ec0f5c2b601

📥 Commits

Reviewing files that changed from the base of the PR and between 76410ae and 4884ed7.

📒 Files selected for processing (10)
  • apps/posts/src/views/filters/filter-codecs.test.ts
  • apps/posts/src/views/filters/filter-codecs.ts
  • apps/posts/src/views/filters/filter-query-core.test.ts
  • apps/posts/src/views/filters/filter-query-core.ts
  • apps/posts/src/views/members/member-fields.test.ts
  • apps/posts/src/views/members/member-fields.ts
  • apps/posts/src/views/members/member-filter-query.test.ts
  • apps/posts/src/views/members/member-filter-query.ts
  • apps/posts/src/views/members/use-member-filter-fields.test.ts
  • apps/posts/src/views/members/use-member-filter-fields.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/posts/src/views/members/use-member-filter-fields.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/posts/src/views/members/member-fields.test.ts
  • apps/posts/src/views/filters/filter-query-core.test.ts
  • apps/posts/src/views/members/use-member-filter-fields.test.ts
  • apps/posts/src/views/filters/filter-codecs.test.ts
  • apps/posts/src/views/filters/filter-codecs.ts
  • apps/posts/src/views/members/member-fields.ts
  • apps/posts/src/views/members/member-filter-query.test.ts
  • apps/posts/src/views/members/member-filter-query.ts

Walkthrough

This PR adds support for the is-all operator for member label filters. Changes extend the codec serialization infrastructure to handle operator-specific output formats, introduce a predicate extraction helper that groups repeated single-value clauses into aggregated all-of predicates, configure the label field to support the new operator, enhance member filter parsing to detect grouped contexts and extract grouped clauses, and add UI display text for the operator. The implementation enables NQL expressions like (label:alpha+label:vip) to be parsed as a single is-all predicate and serialized back to the grouped form.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an 'is all of' operator to member label filters, which is the primary objective of this PR.
Description check ✅ Passed The description is directly related to the changeset, providing context (member label filters limitations), implementation details (grouped NQL serialization, field-config-driven approach), and a reference to the related issue.
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 codex/ber-3664-member-label-is-all

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

@nx-cloud

nx-cloud Bot commented Jun 12, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 4884ed7

Command Status Duration Result
nx build @tryghost/signup-form ✅ Succeeded <1s View ↗
nx build @tryghost/portal ✅ Succeeded <1s View ↗
nx build @tryghost/comments-ui ✅ Succeeded <1s View ↗
nx build @tryghost/sodo-search ✅ Succeeded <1s View ↗
nx build @tryghost/activitypub ✅ Succeeded 1s View ↗
nx build @tryghost/announcement-bar ✅ Succeeded <1s View ↗
nx build @tryghost/admin-toolbar ✅ Succeeded <1s View ↗
nx run-many -t test:unit -p @tryghost/posts,@tr... ✅ Succeeded 2m 48s View ↗
Additional runs (5) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-15 12:41:01 UTC

@kevinansfield kevinansfield marked this pull request as ready for review June 12, 2026 13:00

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 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/filters/filter-codecs.ts`:
- Around line 64-68: The single-value 'is-all' serializer currently emits a
grouped clause `(field:val)` which doesn't round-trip because
extractAllOfPredicates() only rebuilds 'is-all' from groups of two-or-more;
update the SET_OPERATOR_SERIALIZERS entry for 'is-all' to check values.length
and for a single value delegate to the existing serializeSetMembership('')
serializer (same behavior as 'is-any'), otherwise keep the current grouped
serialization that uses serializeScalarValue; reference
SET_OPERATOR_SERIALIZERS, serializeSetMembership, serializeScalarValue, and
extractAllOfPredicates when locating and changing the code.
🪄 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: 343dbedb-e215-4b37-9370-720858f46e16

📥 Commits

Reviewing files that changed from the base of the PR and between dbedeec and 76410ae.

📒 Files selected for processing (10)
  • apps/posts/src/views/filters/filter-codecs.test.ts
  • apps/posts/src/views/filters/filter-codecs.ts
  • apps/posts/src/views/filters/filter-query-core.test.ts
  • apps/posts/src/views/filters/filter-query-core.ts
  • apps/posts/src/views/members/member-fields.test.ts
  • apps/posts/src/views/members/member-fields.ts
  • apps/posts/src/views/members/member-filter-query.test.ts
  • apps/posts/src/views/members/member-filter-query.ts
  • apps/posts/src/views/members/use-member-filter-fields.test.ts
  • apps/posts/src/views/members/use-member-filter-fields.ts

Comment thread apps/posts/src/views/filters/filter-codecs.ts
ref https://linear.app/ghost/issue/BER-3664/add-is-all-of-operator-to-member-label-filters

Members need to filter for contacts that have every selected label, while preserving existing any-of and none-of semantics and round-tripping grouped NQL back into the filter UI.
ref https://linear.app/ghost/issue/BER-3664/add-is-all-of-operator-to-member-label-filters

- the hand-rolled paren/quote scanner misread a trailing backslash in a quoted value as an escaped quote, so grouped all-of filters containing such values reparsed as separate any-of predicates
- replaced the scanner with a probe parse: NQL only flattens parentheses when they are redundant, so parsing the filter with a probe predicate AND-ed alongside preserves the outer group as a nested $and, delegating all quoting rules to the real parser
ref https://linear.app/ghost/issue/BER-3664/add-is-all-of-operator-to-member-label-filters

- the parse side of is-all was label-specific: a hard-coded extractor recognised only label/labels keys, so any other set field adopting is-all would serialize the grouped form but silently fail to parse it back
- replaced it with a shared extractor in filter-query-core that activates for any field declaring the is-all operator, with alias keys coming from the existing parseKeys mechanism instead of being hard-coded
- converted setCodec's serialize branches into an operator-serializer table so adding a set operator is a table entry rather than a special-cased early return
@rob-ghost rob-ghost force-pushed the codex/ber-3664-member-label-is-all branch from 76410ae to 4884ed7 Compare June 15, 2026 12:34
@rob-ghost rob-ghost closed this Jun 16, 2026
@rob-ghost rob-ghost deleted the codex/ber-3664-member-label-is-all branch June 16, 2026 13:08
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