Skip to content

feat(cms-base-layer): add filter by category - #2574

Open
Maciek Kucmus (mkucmus) wants to merge 6 commits into
mainfrom
feat/category-aggegations
Open

feat(cms-base-layer): add filter by category#2574
Maciek Kucmus (mkucmus) wants to merge 6 commits into
mainfrom
feat/category-aggegations

Conversation

@mkucmus

@mkucmus Maciek Kucmus (mkucmus) commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

closes: #2560

one note: there is a hidden count number next to a category's name due to issue that is already addressed in the backend. I will make sure the fix is provided afterwards.

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontends-starter-template-extended Ready Ready Preview Aug 3, 2026 2:42pm
frontends-vue-starter-template Ready Ready Preview Aug 3, 2026 2:42pm

Request Review

Copilot AI 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.

Pull request overview

Adds first-class support in the frontends repo for category-based filtering on search result listings by requesting category aggregations and applying selected categories via a criteria post-filter. This extends @shopware/helpers (criteria builders + filter merging), updates @shopware/cms-base-layer to render a categories filter when aggregations are present, and wires the feature into the Nuxt template search pages and docs.

Changes:

  • Add @shopware/helpers utilities to request category aggregations and build a category post-filter, plus getListingFilters support to merge category entities + counts.
  • Extend @shopware/cms-base-layer listing filters UI/state to support a categories URL param and render a new SwFilterCategories checkbox filter.
  • Update demo/starter template search pages and docs to demonstrate requesting category aggregations and filtering via post-filter.

Reviewed changes

Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
templates/vue-starter-template/app/pages/search.vue Requests category aggregations and applies selected categories as post-filter from ?categories=.
templates/vue-demo-store/app/pages/search.vue Adds category aggregations + category post-filter handling for the demo search page.
templates/vue-demo-store/app/components/listing-filters/ListingFilters.vue Tracks selected categories in filter UI state and syncs them to/from route.query.
templates/vue-demo-store/app/components/listing-filters/ListingFilter.vue Hides the navigation root category from the category filter options.
pnpm-lock.yaml Locks additional tool dependencies (oxfmt/oxlint) for the workspace.
packages/helpers/src/listing/index.ts Exposes new category listing helpers from the listing entrypoint.
packages/helpers/src/listing/filters.ts Merges categories + categories-counts into a single categories listing filter with per-entity counts.
packages/helpers/src/listing/filters.test.ts Adds unit coverage for categories filter merging and edge cases.
packages/helpers/src/listing/categoryFilter.ts Introduces getCategoryFilterAggregations, getCategoryFilterPostFilter, and count resolution helper.
packages/helpers/src/listing/categoryFilter.test.ts Adds unit coverage for the new category filter helper utilities.
packages/helpers/src/index.test.ts Ensures new helpers are exported on the helpers public API.
packages/cms-base-layer/app/utils/useSelectedListingFilters.ts Adds categories into shared listing filter state syncing from URL query.
packages/cms-base-layer/app/utils/useSelectedListingFilters.test.ts Extends tests to cover category query parsing and reset behavior.
packages/cms-base-layer/app/components/SwProductListingFiltersHorizontal.vue Enables category selection + URL syncing for horizontal filter UI (search listing only).
packages/cms-base-layer/app/components/SwProductListingFilters.vue Enables category selection + URL syncing for sidebar filter UI (search listing only).
packages/cms-base-layer/app/components/SwProductListingFilter.vue Wires categories to a dedicated SwFilterCategories component.
packages/cms-base-layer/app/components/SwFilterChips.vue Adds chips for selected categories when the categories filter is available.
packages/cms-base-layer/app/components/listing-filters/SwFilterCategories.vue New categories checkbox filter UI component (hides navigation root category).
examples/sanity-cms/package.json Adds oxlint/oxfmt dev dependencies to the example.
apps/docs/src/getting-started/e-commerce/product-listing.md Documents how to request category aggregations and filter via post-filter.
.changeset/curly-donuts-search.md Changeset for publishing @shopware/helpers additions.
.changeset/curly-donuts-search-ui.md Changeset for publishing @shopware/cms-base-layer UI/state changes.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread templates/vue-demo-store/app/pages/search.vue Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

@patzick Patryk Tomczyk (patzick) 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.

🔍 Code Review

Verdict: request changes — 2 majors. CI is green (all 11 checks pass), no conflicts, @beta helpers are well tested.

🚫 Majors

1. SwFilterChips crashes when filters.categories is absentpackages/cms-base-layer/app/components/SwFilterChips.vue:9,92
FilterState.categories is a new required field and Array.from(props.filters.categories) runs unguarded. Any consumer of the published component that builds its own filter-state object (not via useSelectedListingFilters) now gets TypeError: undefined is not iterable and a dead chips row — on a minor bump. Fix: categories?: Set<string> + Array.from(props.filters.categories ?? []).

2. Category filter renders on non-search listings but does nothingSwProductListingFilters.vue:66,88,159 and SwProductListingFiltersHorizontal.vue:65,87,158
All three write paths are gated on isProductSearch (post-filter, ?categories= param, reset button), but the render loop at SwProductListingFilters.vue:284 is not. On a category listing whose criteria include the category aggregations, checking a box mutates the set, executeSearch pushes a query without categories, applyQueryToFilters clears the set again → the checkbox flips back and the result set never changes. Either gate the render (skip code === "categories" when !isProductSearch) or drop the gates and let the post-filter apply everywhere.

⚠️ Minors

  • getListingFilters silently changes shape for existing categories consumers — entities are reordered by count, count is injected, apiAlias is dropped, and a categories-counts aggregation is now omitted from the filter list (packages/helpers/src/listing/filters.ts:61,114). It is @beta, but the changeset should say so.
  • Root-category hiding is duplicated in SwFilterCategories.vue:60 and templates/vue-demo-store/.../ListingFilter.vue:57. Better as an opt-in helper next to getCategoryFilterAggregations.
  • examples/sanity-cms/package.json + lockfile add oxfmt/oxlint. It fixes a genuinely undeclared dep for that example's lint script, but it is unrelated to this feature and unmentioned in the description — split it out or note it.
  • templates/vue-demo-store/app/pages/search.vue:157: p: route.query.p ? Number(route.query.p) : 1 skips the firstQueryValue/toNumber pair used two lines above; ?p=1&p=2 sends p: NaN.
  • CATEGORY_PARENTS_AGGREGATION_NAME is not re-exported from packages/helpers/src/listing/index.ts, so consumers following the new doc section must hardcode "categories-parents".
  • No component-level test for SwFilterCategories or for the isProductSearch gating — behavioral logic landed in three components with helper-level tests only.

💡 Nits

  • SwFilterCategories.vue:139: :key="${option.id}-${isChecked(option.id)}" tears down and recreates each label on every toggle; :key="option.id" suffices since SwCheckbox is reactively bound.
  • SwFilterChips.vue:93: availableFilters.find(...) is re-run per selected category inside the loop; hoist it (same pre-existing pattern for manufacturer).
  • vue-starter-template has no listing.categories i18n entry, so the heading reads "Categories" in de/pl. Consistent with the existing gap for listing.filters there, so only worth knowing.

✅ Good

  • Post-filter choice for faceted behavior is correct and well documented, including the ES nested-aggregation caveat.
  • resolveCategoryBucketCount variant collapsing and the getListingFilters merge are covered by focused unit tests, including the missing-counts and counts-without-entities paths.
  • Removing the search() / changeCurrentPage() double-fetch in favor of the useAsyncData watch: [() => route.query] refetch is a real fix; the demo-store's limit still round-trips via v-model + query.

🔁 Inherited feedback

Both Copilot inline comments (string[] query params in search.vue and ListingFilters.vue) are already addressed — the current head uses firstQueryValue(...) in search.vue:148 and ListingFilters.vue:65.

🧪 Validation

Not run locally by request — verdict is based on CI: Build, Test, lint, Check audit, CodeQL and all Vercel deployments pass on fb3205c.

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.

Support category aggregations for search result listings in the Store API

3 participants