-
Notifications
You must be signed in to change notification settings - Fork 10.8k
[Expreimental] Show preview taxonomy filter options in hierarchy order #60144
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
[Expreimental] Show preview taxonomy filter options in hierarchy order #60144
Conversation
Testing GuidelinesHi @kmanijak @woocommerce/woo-fse, Apart from reviewing the code changes, please make sure to review the testing instructions (Guide) and verify that relevant tests (E2E, Unit, Integration, etc.) have been added or updated as needed. Reminder: PR reviewers are required to document testing performed. This includes:
|
📝 WalkthroughWalkthroughThis update introduces hierarchical ordering and depth tracking for taxonomy filter options in the WooCommerce product filters block editor UI. It adds a function to construct a hierarchical list of taxonomy terms, updates type definitions to include hierarchy metadata, and adjusts rendering logic to apply depth-based class names for filter items. Changes
Sequence Diagram(s)sequenceDiagram
participant Editor as Block Editor UI
participant Data as Data Store
participant Hierarchy as createHierarchicalList
Editor->>Data: Fetch taxonomy terms
Data-->>Editor: Return flat list of terms
Editor->>Hierarchy: Build hierarchical list with depth from terms
Hierarchy-->>Editor: Return depth-annotated, ordered list
Editor->>Editor: Render filter options with depth-based classNames
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15 minutes Possibly related PRs
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: .coderabbit.yml 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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). (21)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
Size Change: +186 B (0%) Total Size: 5.91 MB
|
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: 0
🧹 Nitpick comments (1)
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx (1)
31-69
: Consider edge case handling in hierarchical processing.The
createHierarchicalList
function implements a solid hierarchical structure, but consider these potential edge cases:
- Orphaned terms: Terms with a
parent
ID that doesn't exist in the dataset will be ignored- Circular references: No protection against parent-child loops
- Missing root terms: The function starts with
children.get( 0 )
but doesn't handle the case where no root terms existConsider adding defensive programming:
// Last: build hierarchical list const result: FilterOptionItem[] = []; function addTermsRecursively( termList: FilterOptionItem[], depth = 0 ) { + if ( depth > 10 ) { // Prevent infinite recursion + console.warn( 'Maximum hierarchy depth exceeded, possible circular reference' ); + return; + } termList.forEach( ( term ) => { term.depth = depth; result.push( term ); const termChildren = children.get( term.id ) || []; if ( termChildren.length > 0 ) { addTermsRecursively( termChildren, depth + 1 ); } } ); } -addTermsRecursively( children.get( 0 ) ); +const rootTerms = children.get( 0 ) || []; +addTermsRecursively( rootTerms ); return result;
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
(1 hunks)plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
(1 hunks)plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
(3 hunks)plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}
: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit Configuration File
**/*.{php,js,ts,jsx,tsx}
: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
🧠 Learnings (13)
📓 Common learnings
Learnt from: samueljseay
PR: woocommerce/woocommerce#59051
File: plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx:66-70
Timestamp: 2025-06-23T05:47:52.696Z
Learning: For WooCommerce mini-cart blocks in plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/, the standardized conditional pattern for experimental features should be `if ( isExperimentalMiniCartEnabled() ) { blockSettings.save = () => <InnerBlocks.Content />; }` - defaulting to the traditional Save component and only overriding when the experimental feature is enabled.
Learnt from: jorgeatorres
PR: woocommerce/woocommerce#59675
File: .github/workflows/release-bump-as-requirement.yml:48-65
Timestamp: 2025-07-15T15:39:21.856Z
Learning: In WooCommerce core repository, changelog entries for all PRs live in `plugins/woocommerce/changelog/` directory and are processed during releases, not at the repository root level.
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/generate-pr-description.mdc:0-0
Timestamp: 2025-07-21T05:22:46.426Z
Learning: Provide clear, step-by-step instructions for how to test the changes in the PR description.
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59900
File: plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/inspector.tsx:0-0
Timestamp: 2025-07-24T05:37:00.907Z
Learning: The DisplayStyleSwitcher component in plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/components/display-style-switcher/index.tsx has been updated so that its onChange prop accepts only a string type (not string | number | undefined), eliminating the need for type assertions when using this component.
Learnt from: samueljseay
PR: woocommerce/woocommerce#58716
File: plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/iapi-frontend.ts:83-101
Timestamp: 2025-06-17T07:07:53.443Z
Learning: In WooCommerce blocks, when porting existing code patterns that have known issues (like parseInt truncating decimal money values), maintain consistency with existing implementation rather than making isolated fixes. The preference is for systematic refactoring approaches (like broader Dinero adoption) over piecemeal changes.
Learnt from: lysyjan
PR: woocommerce/woocommerce#59632
File: packages/js/email-editor/src/layouts/flex-email.tsx:116-122
Timestamp: 2025-07-14T10:41:46.200Z
Learning: In WooCommerce projects, formatting suggestions should respect the project's Prettier configuration and linting rules. Changes that would break the lint job should be avoided, even if they appear to improve readability.
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59499
File: plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php:327-332
Timestamp: 2025-07-10T04:22:27.648Z
Learning: In the WooCommerce ProductFilters QueryClauses class, the $chosen_taxonomies parameter in add_taxonomy_clauses() already contains only validated public product taxonomies. The validation occurs upstream in the Params class during parameter registration, so additional taxonomy existence validation in the processing methods is redundant.
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59878
File: plugins/woocommerce/src/Internal/ProductFilters/FilterData.php:364-383
Timestamp: 2025-07-23T01:06:46.155Z
Learning: In WooCommerce ProductFilters FilterData class, the $product_ids parameter in methods like get_hierarchical_taxonomy_counts() is internally generated from WP_Query results using implode(',', array_column($results, 'ID')). This creates a safe comma-separated string of integers from database IDs, not user input, so SQL injection concerns don't apply to this parameter.
Learnt from: samueljseay
PR: woocommerce/woocommerce#59142
File: plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php:594-602
Timestamp: 2025-06-25T06:51:41.381Z
Learning: WooCommerce block templates have a predictable structure where each block has one top-level div with wp-block-woocommerce- class containing arbitrary nested content that should be preserved.
Learnt from: prettyboymp
PR: woocommerce/woocommerce#59048
File: .github/workflows/cherry-pick-milestoned-prs.yml:60-83
Timestamp: 2025-06-26T12:45:40.709Z
Learning: WooCommerce uses WordPress versioning conventions where minor versions in X.Y.Z format are constrained to 0-9 (Y cannot exceed 9). This means version increment logic should reset minor to 0 and increment major when minor reaches 9, rather than allowing two-digit minor versions like 9.10 or 9.11.
📚 Learning: in woocommerce core repository, changelog entries for all prs live in `plugins/woocommerce/changelog...
Learnt from: jorgeatorres
PR: woocommerce/woocommerce#59675
File: .github/workflows/release-bump-as-requirement.yml:48-65
Timestamp: 2025-07-15T15:39:21.856Z
Learning: In WooCommerce core repository, changelog entries for all PRs live in `plugins/woocommerce/changelog/` directory and are processed during releases, not at the repository root level.
Applied to files:
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
📚 Learning: to watch for changes, use 'pnpm --filter=@woocommerce/plugin-woocommerce watch:build' to ensure expe...
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/woo-build.mdc:0-0
Timestamp: 2025-07-28T05:05:41.091Z
Learning: To watch for changes, use 'pnpm --filter=@woocommerce/plugin-woocommerce watch:build' to ensure experimental features are active during development builds.
Applied to files:
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
📚 Learning: the displaystyleswitcher component in plugins/woocommerce/client/blocks/assets/js/blocks/product-fil...
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59900
File: plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/inspector.tsx:0-0
Timestamp: 2025-07-24T05:37:00.907Z
Learning: The DisplayStyleSwitcher component in plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/components/display-style-switcher/index.tsx has been updated so that its onChange prop accepts only a string type (not string | number | undefined), eliminating the need for type assertions when using this component.
Applied to files:
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
📚 Learning: woocommerce legacy javascript files in plugins/woocommerce/client/legacy/js/ must use older javascri...
Learnt from: opr
PR: woocommerce/woocommerce#0
File: :0-0
Timestamp: 2025-06-20T17:38:16.565Z
Learning: WooCommerce legacy JavaScript files in plugins/woocommerce/client/legacy/js/ must use older JavaScript syntax and cannot use modern features like optional chaining (?.) due to browser compatibility requirements. Explicit null checking with && operators should be used instead.
Applied to files:
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
📚 Learning: in woocommerce blocks typescript code, avoid type assertions (`as`) when accessing properties from u...
Learnt from: gigitux
PR: woocommerce/woocommerce#59659
File: plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts:136-147
Timestamp: 2025-07-22T09:30:43.528Z
Learning: In WooCommerce blocks TypeScript code, avoid type assertions (`as`) when accessing properties from unified stores. Instead, create proper union types that combine all the different store component types using `Partial<>` wrappers, and export the unified type to help third-party extenders. For example: `type UnifiedStore = BaseStore & Partial<StoreA> & Partial<StoreB>`.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
📚 Learning: in woocommerce blocks, when porting existing code patterns that have known issues (like parseint tru...
Learnt from: samueljseay
PR: woocommerce/woocommerce#58716
File: plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/iapi-frontend.ts:83-101
Timestamp: 2025-06-17T07:07:53.443Z
Learning: In WooCommerce blocks, when porting existing code patterns that have known issues (like parseInt truncating decimal money values), maintain consistency with existing implementation rather than making isolated fixes. The preference is for systematic refactoring approaches (like broader Dinero adoption) over piecemeal changes.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
📚 Learning: in wordpress blocks, when there's a styling mismatch between editor and frontend, check if the php r...
Learnt from: gigitux
PR: woocommerce/woocommerce#58902
File: plugins/woocommerce/client/blocks/assets/js/blocks/product-specifications/edit.tsx:205-206
Timestamp: 2025-06-17T12:40:54.118Z
Learning: In WordPress blocks, when there's a styling mismatch between editor and frontend, check if the PHP renderer (like in `ProductSpecifications.php`) adds specific classes to the output. If so, add those same classes to the `useBlockProps` className in the editor component (like in `edit.tsx`) to ensure consistent styling. For example, adding `wp-block-table` class to both frontend and editor ensures core table styles and theme customizations apply consistently.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
📚 Learning: in `plugins/woocommerce/src/blocks/blocktypes/productbutton.php`, the team intentionally relies on t...
Learnt from: Aljullu
PR: woocommerce/woocommerce#58809
File: plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php:222-225
Timestamp: 2025-06-13T17:11:13.732Z
Learning: In `plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php`, the team intentionally relies on toggling the `disabled` CSS class (via `data-wp-class--disabled`) instead of binding the `disabled` attribute, to mirror the behavior of the classic WooCommerce template.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
📚 Learning: in woocommerce blocks, bold styling for `.wc-block-components-product-details__name` should be scope...
Learnt from: ralucaStan
PR: woocommerce/woocommerce#58782
File: plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/product-details/style.scss:21-26
Timestamp: 2025-06-13T15:24:45.923Z
Learning: In WooCommerce blocks, bold styling for `.wc-block-components-product-details__name` should be scoped only to the Cart block (`.wc-block-cart__main`); on the Checkout block, product names are not bold because prices are highlighted instead.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
📚 Learning: for woocommerce mini-cart blocks in plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/, t...
Learnt from: samueljseay
PR: woocommerce/woocommerce#59051
File: plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx:66-70
Timestamp: 2025-06-23T05:47:52.696Z
Learning: For WooCommerce mini-cart blocks in plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/, the standardized conditional pattern for experimental features should be `if ( isExperimentalMiniCartEnabled() ) { blockSettings.save = () => <InnerBlocks.Content />; }` - defaulting to the traditional Save component and only overriding when the experimental feature is enabled.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
📚 Learning: in plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-addres...
Learnt from: ralucaStan
PR: woocommerce/woocommerce#58782
File: plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx:76-90
Timestamp: 2025-06-13T13:37:35.793Z
Learning: In plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx, the `shouldAnimate` flag is expected to be `false` only on initial render and may remain `true` after the first edit; it does not need to be reset.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
📚 Learning: in the woocommerce productfilters queryclauses class, the $chosen_taxonomies parameter in add_taxono...
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59499
File: plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php:327-332
Timestamp: 2025-07-10T04:22:27.648Z
Learning: In the WooCommerce ProductFilters QueryClauses class, the $chosen_taxonomies parameter in add_taxonomy_clauses() already contains only validated public product taxonomies. The validation occurs upstream in the Params class during parameter registration, so additional taxonomy existence validation in the processing methods is redundant.
Applied to files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
🔇 Additional comments (6)
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical (1)
1-4
: LGTM!The changelog entry follows WooCommerce conventions and correctly identifies this as an experimental patch-level tweak for hierarchical taxonomy filter display.
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts (1)
27-29
: LGTM!The optional properties for hierarchical metadata are well-designed. The
number
typing is appropriate for taxonomy IDs and depth levels, and maintaining these as optional ensures backward compatibility.plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx (1)
99-105
: LGTM!The conditional depth-based CSS class implementation is well done. The use of optional chaining safely handles cases where
depth
is undefined, and theclsx
usage follows React best practices.plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx (3)
102-115
: LGTM!The refactoring from array spread to object format for
getEntityRecords
arguments improves readability while maintaining equivalent functionality.
182-187
: LGTM!The replacement of direct sorting with hierarchical processing correctly implements the intended functionality. The
createHierarchicalList
function handles both hierarchy construction and sorting within each level.
173-174
: No changes needed for parent handling.
The WooCommerce REST API always returnsparent
as an integer (0 for root terms), so usingterm.parent || 0
correctly covers root and nested terms alike.
Test using WordPress PlaygroundThe changes in this pull request can be previewed and tested using a WordPress Playground instance. Test this pull request with WordPress Playground. Note that this URL is valid for 30 days from when this comment was last updated. You can update it by closing/reopening the PR or pushing a new commit. |
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.
There seems to be something wrong with the logic:
Here's my categories structure. Clearly there are items missing in editor (this is sorting by name. I'd expect all of the subcategories of Books & Toys be listed before the next major category which is Clothing and then Clothing is missing Tshirts and T-shirts:
Settings | Editor |
---|---|
![]() |
![]() |
...ocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
Show resolved
Hide resolved
@kmanijak I want to keep the preview simple by only fetching 15 items for preview in the editor, in the default order, so the missing categories are expected. The order of fetched categories should always be correct. From your screenshot, I think they're correctly ordered. The list doesn't have all 15 items, which indicates that there are some items are empty, which is also expected. With this explanation, what do you think? I'm happy to change my mind, too. |
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.
@kmanijak I want to keep the preview simple by only fetching 15 items for preview in the editor, in the default order, so the missing categories are expected. The order of fetched categories should always be correct. From your screenshot, I think they're correctly ordered. The list doesn't have all 15 items, which indicates that there are some items are empty, which is also expected. With this explanation, what do you think? I'm happy to change my mind, too.
Ah! That makes sense! I think it's fine, let's go with it and we can revisit in case there are any reports from users. So with that in mind, I'm approving!
f90ac45
to
c1fa0e0
Compare
c1fa0e0
to
d0b5dad
Compare
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: 2
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
(1 hunks)plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
(1 hunks)plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
(3 hunks)plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/checkbox-list/edit.tsx
- plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/types.ts
- plugins/woocommerce/changelog/60144-wooplug-5236-editor-preview-filter-options-dont-show-in-hierarchical
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{php,js,jsx,ts,tsx}
📄 CodeRabbit Inference Engine (.cursor/rules/code-quality.mdc)
**/*.{php,js,jsx,ts,tsx}
: Guard against unexpected inputs
Sanitize and validate any potentially dangerous inputs
Ensure code is backwards compatible
Write code that is readable and intuitive
Ensure code has unit or E2E tests where applicable
Files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
**/*.{php,js,ts,jsx,tsx}
⚙️ CodeRabbit Configuration File
**/*.{php,js,ts,jsx,tsx}
: Don't trust that extension developers will follow the best practices, make sure the code:
- Guards against unexpected inputs.
- Sanitizes and validates any potentially dangerous inputs.
- Is backwards compatible.
- Is readable and intuitive.
- Has unit or E2E tests where applicable.
Files:
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
🔇 Additional comments (2)
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx (2)
102-115
: LGTM! Improved argument clarity.The refactoring from array spread to object syntax for
getEntityRecords
arguments improves code readability and maintainability without changing functionality.
182-187
: Implementation correctly applies hierarchical ordering.The replacement of direct sorting with hierarchical processing successfully implements the PR objective of showing taxonomy filter options in hierarchical order.
Note: This change depends on addressing the input validation issues identified in the
createHierarchicalList
function above.
// Create hierarchical structure: parents followed by their children | ||
function createHierarchicalList( | ||
terms: FilterOptionItem[], | ||
sortOrder: string | ||
) { | ||
const children = new Map(); | ||
|
||
// First: categorize terms | ||
terms.forEach( ( term ) => { | ||
if ( ! children.has( term.parent ) ) { | ||
children.set( term.parent, [] ); | ||
} | ||
children.get( term.parent ).push( term ); | ||
} ); | ||
|
||
// Next: sort them | ||
children.keys().forEach( ( key ) => { | ||
children.set( | ||
key, | ||
sortFilterOptions( children.get( key ), sortOrder ) | ||
); | ||
} ); | ||
|
||
// Last: build hierarchical list | ||
const result: FilterOptionItem[] = []; | ||
function addTermsRecursively( termList: FilterOptionItem[], depth = 0 ) { | ||
termList.forEach( ( term ) => { | ||
term.depth = depth; | ||
result.push( term ); | ||
const termChildren = children.get( term.id ) || []; | ||
if ( termChildren.length > 0 ) { | ||
addTermsRecursively( termChildren, depth + 1 ); | ||
} | ||
} ); | ||
} | ||
|
||
addTermsRecursively( children.get( 0 ) ); | ||
return result; | ||
} |
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.
Add input validation and prevent object mutation.
The function has several critical issues that violate the coding guidelines:
- Missing input validation: No checks for null/undefined inputs or required properties
- Object mutation: Directly modifies input objects by adding
depth
property - Potential infinite loops: No protection against circular parent-child relationships
- Missing error handling: Assumes
term.id
andterm.parent
exist without validation
Apply this diff to address these issues:
-function createHierarchicalList(
- terms: FilterOptionItem[],
- sortOrder: string
-) {
+function createHierarchicalList(
+ terms: FilterOptionItem[],
+ sortOrder: string
+): FilterOptionItem[] {
+ // Input validation
+ if (!Array.isArray(terms) || terms.length === 0) {
+ return [];
+ }
+
+ // Validate required properties and detect potential cycles
+ const termIds = new Set<number>();
+ const validatedTerms = terms.filter(term => {
+ if (!term || typeof term.id !== 'number' || typeof term.parent !== 'number') {
+ return false;
+ }
+ if (termIds.has(term.id)) {
+ return false; // Duplicate ID
+ }
+ termIds.add(term.id);
+ return true;
+ });
+
const children = new Map();
-
- // First: categorize terms
- terms.forEach( ( term ) => {
+
+ // First: categorize terms
+ validatedTerms.forEach( ( term ) => {
if ( ! children.has( term.parent ) ) {
children.set( term.parent, [] );
}
children.get( term.parent ).push( term );
} );
// Next: sort them
children.keys().forEach( ( key ) => {
children.set(
key,
sortFilterOptions( children.get( key ), sortOrder )
);
} );
// Last: build hierarchical list
const result: FilterOptionItem[] = [];
- function addTermsRecursively( termList: FilterOptionItem[], depth = 0 ) {
+ function addTermsRecursively( termList: FilterOptionItem[], depth = 0, visited = new Set<number>() ) {
+ // Prevent infinite recursion
+ if (depth > 10) return; // Reasonable max depth limit
+
termList.forEach( ( term ) => {
- term.depth = depth;
- result.push( term );
+ // Avoid cycles
+ if (visited.has(term.id)) return;
+ visited.add(term.id);
+
+ // Create new object instead of mutating input
+ const termWithDepth = { ...term, depth };
+ result.push( termWithDepth );
+
const termChildren = children.get( term.id ) || [];
if ( termChildren.length > 0 ) {
- addTermsRecursively( termChildren, depth + 1 );
+ addTermsRecursively( termChildren, depth + 1, new Set(visited) );
}
+
+ visited.delete(term.id);
} );
}
- addTermsRecursively( children.get( 0 ) );
+ // Only process if root terms exist
+ const rootTerms = children.get( 0 );
+ if (rootTerms && rootTerms.length > 0) {
+ addTermsRecursively( rootTerms );
+ }
+
return result;
}
📝 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.
// Create hierarchical structure: parents followed by their children | |
function createHierarchicalList( | |
terms: FilterOptionItem[], | |
sortOrder: string | |
) { | |
const children = new Map(); | |
// First: categorize terms | |
terms.forEach( ( term ) => { | |
if ( ! children.has( term.parent ) ) { | |
children.set( term.parent, [] ); | |
} | |
children.get( term.parent ).push( term ); | |
} ); | |
// Next: sort them | |
children.keys().forEach( ( key ) => { | |
children.set( | |
key, | |
sortFilterOptions( children.get( key ), sortOrder ) | |
); | |
} ); | |
// Last: build hierarchical list | |
const result: FilterOptionItem[] = []; | |
function addTermsRecursively( termList: FilterOptionItem[], depth = 0 ) { | |
termList.forEach( ( term ) => { | |
term.depth = depth; | |
result.push( term ); | |
const termChildren = children.get( term.id ) || []; | |
if ( termChildren.length > 0 ) { | |
addTermsRecursively( termChildren, depth + 1 ); | |
} | |
} ); | |
} | |
addTermsRecursively( children.get( 0 ) ); | |
return result; | |
} | |
// Create hierarchical structure: parents followed by their children | |
function createHierarchicalList( | |
terms: FilterOptionItem[], | |
sortOrder: string | |
): FilterOptionItem[] { | |
// Input validation | |
if ( ! Array.isArray( terms ) || terms.length === 0 ) { | |
return []; | |
} | |
// Validate required properties and detect potential cycles | |
const termIds = new Set<number>(); | |
const validatedTerms = terms.filter( term => { | |
if ( | |
! term || | |
typeof term.id !== 'number' || | |
typeof term.parent !== 'number' | |
) { | |
return false; | |
} | |
if ( termIds.has( term.id ) ) { | |
return false; // Duplicate ID | |
} | |
termIds.add( term.id ); | |
return true; | |
} ); | |
const children = new Map<number, FilterOptionItem[]>(); | |
// First: categorize terms | |
validatedTerms.forEach( ( term ) => { | |
if ( ! children.has( term.parent ) ) { | |
children.set( term.parent, [] ); | |
} | |
children.get( term.parent )!.push( term ); | |
} ); | |
// Next: sort them | |
children.forEach( ( list, key ) => { | |
children.set( key, sortFilterOptions( list, sortOrder ) ); | |
} ); | |
// Last: build hierarchical list | |
const result: FilterOptionItem[] = []; | |
function addTermsRecursively( | |
termList: FilterOptionItem[], | |
depth = 0, | |
visited = new Set<number>() | |
) { | |
// Prevent infinite recursion | |
if ( depth > 10 ) { | |
return; | |
} | |
termList.forEach( ( term ) => { | |
// Avoid cycles | |
if ( visited.has( term.id ) ) { | |
return; | |
} | |
visited.add( term.id ); | |
// Create new object instead of mutating input | |
const termWithDepth = { ...term, depth }; | |
result.push( termWithDepth ); | |
const termChildren = children.get( term.id ) || []; | |
if ( termChildren.length > 0 ) { | |
addTermsRecursively( | |
termChildren, | |
depth + 1, | |
new Set( visited ) | |
); | |
} | |
visited.delete( term.id ); | |
} ); | |
} | |
// Only process if root terms exist | |
const rootTerms = children.get( 0 ); | |
if ( rootTerms && rootTerms.length > 0 ) { | |
addTermsRecursively( rootTerms ); | |
} | |
return result; | |
} |
🤖 Prompt for AI Agents
In
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
from lines 31 to 69, the createHierarchicalList function lacks input validation,
mutates input objects by adding a depth property, does not guard against
circular parent-child relationships, and assumes required properties exist. Add
checks to ensure the terms array and its objects are valid, avoid mutating the
original term objects by creating new objects with the depth property, implement
logic to detect and prevent infinite loops caused by circular references, and
add error handling for missing term.id or term.parent properties.
id: term.id, | ||
parent: term.parent || 0, |
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.
🛠️ Refactor suggestion
Add validation for hierarchy properties.
The code assumes term.id
exists and term.parent
is a valid number when present, but doesn't validate these assumptions.
Apply this diff to add proper validation:
acc.push( {
label: term.name,
value: term.slug,
selected: false,
count,
- id: term.id,
- parent: term.parent || 0,
+ id: typeof term.id === 'number' ? term.id : 0,
+ parent: typeof term.parent === 'number' ? term.parent : 0,
} );
📝 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.
id: term.id, | |
parent: term.parent || 0, | |
acc.push( { | |
label: term.name, | |
value: term.slug, | |
selected: false, | |
count, | |
id: typeof term.id === 'number' ? term.id : 0, | |
parent: typeof term.parent === 'number' ? term.parent : 0, | |
} ); |
🤖 Prompt for AI Agents
In
plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx
around lines 173 to 174, the code assumes term.id exists and term.parent is a
valid number without validation. Add checks to ensure term.id is defined and
term.parent is a valid number before assigning them, defaulting to safe fallback
values if not valid, to prevent runtime errors.
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.
Missing input validation: No checks for null/undefined inputs or required properties
Missing error handling: Assumes term.id and term.parent exist without validation
The input of function is built internally, not from users.
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.
@dinhtungdu Thank you for the clarification! You're absolutely right - since the taxonomy terms come from WordPress core data APIs via getEntityRecords()
, the data structure is controlled and consistent. The term.id
and term.parent
properties are guaranteed to be present and properly typed from the WordPress core data store.
I appreciate you explaining the context about internal vs external data sources. This makes sense given that WooCommerce can trust the data integrity from WordPress core APIs.
✏️ Learnings added
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#60144
File: plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx:183-184
Timestamp: 2025-08-04T14:32:56.238Z
Learning: In WooCommerce taxonomy filter components like plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/taxonomy-filter/edit.tsx, taxonomy term objects fetched from WordPress core data APIs via getEntityRecords() have guaranteed structure and don't require additional validation for properties like term.id and term.parent since they come from trusted internal WordPress core data sources.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
🧠 Learnings used
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#60142
File: plugins/woocommerce/src/Internal/ProductFilters/TaxonomyHierarchyData.php:71-93
Timestamp: 2025-08-01T05:52:57.768Z
Learning: In plugins/woocommerce/src/Internal/ProductFilters/TaxonomyHierarchyData.php, the get_descendants() and get_ancestors() methods don't need extensive input validation because: 1) the taxonomy parameter is always validated upstream in the controlled WooCommerce core context, and 2) the null coalescing operator (??) already efficiently handles invalid term_id keys in the returned map.
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59900
File: plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/inner-blocks/attribute-filter/inspector.tsx:0-0
Timestamp: 2025-07-24T05:37:00.907Z
Learning: The DisplayStyleSwitcher component in plugins/woocommerce/client/blocks/assets/js/blocks/product-filters/components/display-style-switcher/index.tsx has been updated so that its onChange prop accepts only a string type (not string | number | undefined), eliminating the need for type assertions when using this component.
Learnt from: dinhtungdu
PR: woocommerce/woocommerce#59499
File: plugins/woocommerce/src/Internal/ProductFilters/QueryClauses.php:327-332
Timestamp: 2025-07-10T04:22:27.648Z
Learning: In the WooCommerce ProductFilters QueryClauses class, the $chosen_taxonomies parameter in add_taxonomy_clauses() already contains only validated public product taxonomies. The validation occurs upstream in the Params class during parameter registration, so additional taxonomy existence validation in the processing methods is redundant.
Learnt from: samueljseay
PR: woocommerce/woocommerce#58716
File: plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/iapi-frontend.ts:83-101
Timestamp: 2025-06-17T07:07:53.443Z
Learning: In WooCommerce blocks, when porting existing code patterns that have known issues (like parseInt truncating decimal money values), maintain consistency with existing implementation rather than making isolated fixes. The preference is for systematic refactoring approaches (like broader Dinero adoption) over piecemeal changes.
Learnt from: triple0t
PR: woocommerce/woocommerce#59186
File: packages/js/email-editor/src/store/initial-state.ts:9-10
Timestamp: 2025-06-26T12:13:32.062Z
Learning: In WooCommerce email editor store initialization (packages/js/email-editor/src/store/initial-state.ts), the current_post_id and current_post_type from window.WooCommerceEmailEditor are required parameters that should cause explicit errors if missing, rather than using fallback values or optional chaining. The design preference is to fail fast when critical initialization data is unavailable.
Learnt from: samueljseay
PR: woocommerce/woocommerce#59051
File: plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx:66-70
Timestamp: 2025-06-23T05:47:52.696Z
Learning: For WooCommerce mini-cart blocks in plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/, the standardized conditional pattern for experimental features should be `if ( isExperimentalMiniCartEnabled() ) { blockSettings.save = () => <InnerBlocks.Content />; }` - defaulting to the traditional Save component and only overriding when the experimental feature is enabled.
Learnt from: jorgeatorres
PR: woocommerce/woocommerce#59675
File: .github/workflows/release-bump-as-requirement.yml:48-65
Timestamp: 2025-07-15T15:39:21.856Z
Learning: In WooCommerce core repository, changelog entries for all PRs live in `plugins/woocommerce/changelog/` directory and are processed during releases, not at the repository root level.
Learnt from: CR
PR: woocommerce/woocommerce#0
File: .cursor/rules/generate-pr-description.mdc:0-0
Timestamp: 2025-07-21T05:22:46.426Z
Learning: Provide clear, step-by-step instructions for how to test the changes in the PR description.
Learnt from: lysyjan
PR: woocommerce/woocommerce#59632
File: packages/js/email-editor/src/layouts/flex-email.tsx:116-122
Timestamp: 2025-07-14T10:41:46.200Z
Learning: In WooCommerce projects, formatting suggestions should respect the project's Prettier configuration and linting rules. Changes that would break the lint job should be avoided, even if they appear to improve readability.
Submission Review Guidelines
Changes proposed in this Pull Request
This PR updates the preview of hierarchical taxonomies, such as category or brand, by sorting the preview filter options in hierarchical order.
This also add the depth class to the editor preview of the Checkbox List block, to sync with the class added on the frontend in #60142.
Closes WOOPLUG-5236
Screenshots or screen recordings
How to test the changes in this Pull Request
Using the WooCommerce Testing Instructions Guide, include your detailed testing instructions:
Changelog entry
Changelog Entry Details
Significance
Type
Message
Experimental: Show preview taxonomy filter options in hierarchy order.