Skip to content

feat(boost): extensible browse filters via NFS (RHIDP-15449)#3750

Open
rohitkrai03 wants to merge 3 commits into
redhat-developer:mainfrom
rohitkrai03:feat/boost-ai-catalog-extensible-filters
Open

feat(boost): extensible browse filters via NFS (RHIDP-15449)#3750
rohitkrai03 wants to merge 3 commits into
redhat-developer:mainfrom
rohitkrai03:feat/boost-ai-catalog-extensible-filters

Conversation

@rohitkrai03

@rohitkrai03 rohitkrai03 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

FIxes

Summary

  • Introduces AiCatalogFilterBlueprint — an NFS extension blueprint that lets deployers disable built-in filters and third-party plugins add new filters to the AI Catalog browse sidebar via app-config.yaml
  • Replaces hardcoded FilterSidebar with a data-driven approach: each filter is a plain FilterDefinition object (urlParam, label/labelKey, getOptions, matchEntity, priority) rendered by a generic <Select>
  • Adds specs, design decisions, and tasks for stories 4–7 under RHIDP-15164

Changes

New files:

  • src/blueprints/AiCatalogFilterBlueprint.tsFilterDefinition, filterDefinitionDataRef, AiCatalogFilterBlueprint
  • src/filters/builtInFilterDefinitions.ts — 4 built-in definitions (category, provider, owner, tags)
  • packages/app/src/modules/sampleFilter/ — lifecycle filter as third-party contribution example
  • specs/{filter-customization,translations,e2e-tests,dynamic-plugin-export}/spec.md

Refactored:

  • plugin.tsx — page makeWithOverrides with name: 'ai-catalog' and filters input
  • FilterSidebar — generic <Select> per FilterDefinition, label/labelKey i18n
  • useUrlFilters — dynamic filterParams[], generic setFilter(), clearFilters preserves view/pageSize
  • applyEntityFilters — AND loop over matchEntity, no hardcoded field names
  • useAiAssets — new signature (search, filters, filterValues)

Public API: AiCatalogFilterBlueprint, filterDefinitionDataRef, FilterDefinition

Test plan

  • yarn tsc --noEmit — 0 errors
  • yarn build:api-reports:only — report updated with page:boost/ai-catalog and new exports
  • yarn test --no-watch — 51 suites, 526 tests passing
  • Changeset added (minor for @red-hat-developer-hub/backstage-plugin-boost)
  • Smoke test: yarn start → verify 4 built-in filters + lifecycle sample filter render at /ai-catalog
  • Verify ai-catalog-filter:boost/owner: false in app-config disables the owner filter

Screenshot

Filter.Customization.mov

Made with Cursor

@rhdh-gh-app

rhdh-gh-app Bot commented Jul 14, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
app workspaces/boost/packages/app none v0.0.0
@red-hat-developer-hub/backstage-plugin-boost workspaces/boost/plugins/boost minor v0.3.0

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:39 PM UTC · Completed 1:44 PM UTC
Commit: 8dea3d4 · View workflow run →

@rhdh-qodo-merge

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

Sorry, something went wrong

We weren't able to complete the code review on our side. Please try again

Grey Divider

Qodo Logo

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review — approve

This PR introduces extensible browse filters for the AI Catalog via the Backstage New Frontend System (NFS). The architecture is clean: filters are data (FilterDefinition objects), not per-filter React components. The AiCatalogFilterBlueprint wraps each definition as an NFS extension, enabling deployers to disable built-in filters and third-party plugins to contribute new ones — all via app-config.yaml.

What was reviewed

  • AiCatalogFilterBlueprint — Well-designed blueprint with reserved-param validation (q, view, page, pageSize), single data ref, and clear JSDoc. The priority default of 100 is sensible.
  • builtInFilterDefinitions — Clean extraction of the 4 existing filters (category, provider, owner, tags) into plain objects. Case-insensitive matching is consistent across all filters. The categoryFilterDefinition.getOptions correctly delegates to getAllCategories() (static list) while other filters derive options dynamically from entities.
  • useUrlFilters refactor — Dynamic filter params instead of hardcoded names. clearFilters now correctly preserves view and pageSize (improvement over old behavior that wiped all URL params). setFilter correctly resets page on filter change.
  • applyEntityFilters refactor — Clean replacement of 4 hardcoded if blocks with a single generic loop over FilterDefinition[] calling matchEntity. AND logic preserved.
  • useAiAssets refactor — Signature change from (filters: AiAssetFilters) to (search, filters, filterValues) is appropriate since the data-driven approach separates concerns.
  • FilterSidebar refactor — Generic <FilterSelect> per definition, returns null when no filters registered. Labels support both plain text and i18n keys via labelKey.
  • plugin.tsxPageBlueprint.makeWithOverrides with filters input correctly resolves and sorts FilterDefinition[] by priority.
  • Breaking change — Page extension ID page:boostpage:boost/ai-catalog is documented in the changeset. Changeset correctly uses minor (pre-1.0 plugin).
  • Sample filter module — Good example of third-party contribution via createFrontendModule({ pluginId: 'boost' }).
  • TestsbuiltInFilterDefinitions.test.ts (164 lines) and entityHelpers.test.ts (151 lines) provide solid coverage of core filter logic. useAiAssets.test.ts and AiCatalogPage.test.tsx correctly updated for new signatures.
  • Spec documents — 4 draft specs for future stories (filter-customization, translations, e2e-tests, dynamic-plugin-export) are well-structured with BDD scenarios.

Findings

Low: Missing unit tests for useUrlFilters and FilterSidebar

File: workspaces/boost/plugins/boost/src/hooks/useUrlFilters.ts

Task 4.16 in the task list specifies unit tests for FilterSidebar (renders N selects, returns null when empty) and useUrlFilters (dynamic param read/write, setFilter, clearFilters preserves view/pageSize). These tests are not included in this PR. The refactored useUrlFilters has new behavior (dynamic params, setFilter replacing 4 named setters, clearFilters preserving view/pageSize) that would benefit from direct test coverage. The existing AiCatalogPage.test.tsx provides integration coverage, but dedicated hook tests would catch regressions more precisely.

Suggested follow-up: Add useUrlFilters.test.ts and FilterSidebar.test.tsx per task 4.16.

Low: FilterSidebar creates inline closures in map

File: workspaces/boost/plugins/boost/src/components/catalog/FilterSidebar.tsx

The onChange handler vals => onFilterChange(filter.urlParam, vals) creates a new closure for each filter on every FilterSidebar render, defeating FilterSelect's internal useCallback on handleChange. With 4-10 filters this is negligible, but if the filter count grows, consider memoizing with useCallback or extracting the closure to avoid unnecessary FilterSelect re-renders.

Summary

The data-driven filter architecture is well-designed and follows Backstage NFS conventions correctly. The refactoring cleanly separates concerns (filter definitions, URL state, entity matching, UI rendering) while preserving existing behavior. Tests cover the core logic. The breaking change is documented. No correctness, security, or architectural concerns.


Labels: PR refactors the AI Catalog browse page filter system to use NFS extension blueprints, touching plugin architecture and frontend components

Previous run

Review — Approve

PR: feat(boost): extensible browse filters via NFS (RHIDP-15449)

Summary

This PR replaces the hardcoded filter sidebar in the AI Catalog browse page with a data-driven, NFS-extensible architecture. The core change introduces FilterDefinition — a plain-object interface describing each filter's URL param, label, option extraction, entity matching, and render priority — and AiCatalogFilterBlueprint, an NFS extension blueprint that wraps a FilterDefinition for deployer enable/disable via app-config.yaml.

The refactoring is clean and well-structured across all layers:

  • Blueprint + data ref (AiCatalogFilterBlueprint.ts): Properly validates reserved URL params (q, view, page, pageSize), defaults priority to 100, and uses a single createExtensionDataRef — no over-engineering.
  • Built-in definitions (builtInFilterDefinitions.ts): 4 filters extracted as plain objects with case-insensitive matching and labelKey i18n support.
  • Page factory (plugin.tsx): PageBlueprint.makeWithOverrides with filters input cleanly resolves and sorts FilterDefinition[] before passing to the page component.
  • FilterSidebar: Generic <Select> per definition, returns null when no filters are registered — correct graceful degradation.
  • useUrlFilters: Dynamic param reading/writing from the registered filter set. clearFilters now correctly preserves view and pageSize (improvement over old behavior that wiped all params).
  • applyEntityFilters: Single AND loop over matchEntity replaces 4 hardcoded filter blocks — extensible and correct.
  • useAiAssets: Clean new signature (search, filters, filterValues) separating concerns.

Correctness Assessment

onCategoryClick gracefully handles disabled category filter (categoryUrlParam is undefined when filter not in resolved set, so onCategoryClick becomes undefined)
✅ Reserved URL param validation in blueprint factory prevents collisions with pagination/view params
✅ Test coverage is thorough — new tests for builtInFilterDefinitions, entityHelpers.applyEntityFilters, and updated tests for useAiAssets and AiCatalogPage
✅ Breaking change (page extension ID page:boostpage:boost/ai-catalog) is clearly documented in the changeset
✅ Sample filter module is commented out in App.tsx (not accidentally enabled)
✅ Public API exports (AiCatalogFilterBlueprint, filterDefinitionDataRef, FilterDefinition) are properly declared in report.api.md

Minor Documentation Inconsistencies

  1. tasks.md task 4.5 says "Register 5 built-in filters" but only 4 are implemented (category, provider, owner, tags). Likely a typo — no missing filter.
  2. filter-customization/spec.md "Disable multiple filters" scenario: States the deployer disables tags and owner, then asserts "the category, provider, and owner filters render normally" — but owner was listed as disabled. Should read "category and provider filters render normally."

Neither affects code correctness; both are in draft spec documents.

Architecture Notes

The data-driven approach (plain objects instead of per-filter React components) is the right call for this use case — all filters share the same <Select> rendering pattern, so per-filter components would be unnecessary abstraction. The FilterDefinition contract is minimal and well-typed.

The filterValues Map is recreated on every searchParams change (including pagination/view changes), which triggers unnecessary applyEntityFilters recomputation. This is a pre-existing performance pattern (the old filters object had the same characteristic) and is negligible for the expected dataset size (<500 entities). No action needed now.

Verdict: Clean, well-tested refactoring that correctly introduces extensible filters via NFS. The breaking change is documented, test coverage is adequate, and the architecture aligns with the Backstage NFS patterns.


Labels: PR adds extensible NFS filter architecture to the boost plugin with documentation and test coverage

Previous run (2)

Review

Verdict: Approve

Well-structured feature that replaces hardcoded browse filters with an NFS-extensible data-driven architecture. The FilterDefinitionAiCatalogFilterBlueprintFilterSidebar pipeline is clean, the refactoring preserves existing behavior while unlocking new extensibility, and test coverage is good across filter definitions, the filter pipeline, and the asset hook.

What was reviewed

  • Blueprint & data model (AiCatalogFilterBlueprint.ts): FilterDefinition interface is minimal and well-chosen. Using a generator factory with a single filterDefinitionDataRef output is the correct NFS pattern. Default priority of 100 is sensible.
  • Built-in filter migration (builtInFilterDefinitions.ts): All 4 existing filters faithfully reproduced as plain objects. Case-insensitive matching matches prior behavior. getAllCategories() remains static (correct — categories are derived from a metadata map, not entities).
  • Page input wiring (plugin.tsx): makeWithOverrides + createExtensionInput + resolution/sort in factory is the standard NFS pattern for child extensions. Filter definitions resolved once at extension time — stable reference.
  • FilterSidebar refactor: Generic <Select> per definition with labelKey i18n support. Correctly returns null when no filters are registered.
  • useUrlFilters refactor: Dynamic filterParams array, generic setFilter(urlParam, values). clearFilters now correctly preserves view/pageSize (improvement over the old new URLSearchParams() approach).
  • applyEntityFilters refactor (entityHelpers.ts): Clean AND-loop over filter definitions with matchEntity. Search remains built-in. No hardcoded field names.
  • useAiAssets signature change: Clean separation of search, filters, and filterValues parameters.
  • Sample filter module: Good third-party contribution example with lifecycle filter.
  • Spec documents: Thorough and well-structured specifications for future stories (translations, e2e, dynamic export).
  • Tests: builtInFilterDefinitions.test.ts and entityHelpers.test.ts provide solid unit coverage for the new filter pipeline. Existing tests updated for the new hook signatures.

Findings

1. Page extension ID rename — deployer migration note (low · style/conventions)

The page extension changes from page:boost (unnamed) to page:boost/ai-catalog (named). This is the right NFS pattern for a named page, but it's a breaking change for any deployer referencing page:boost in app-config.yaml. Since this is a minor changeset on a plugin still in active development, this is acceptable — just worth noting in release notes or changelog for adopters.

2. No urlParam collision guard (low · correctness)

The AiCatalogFilterBlueprint factory accepts any urlParam string without validating against reserved URL params (q, view, page, pageSize). A third-party filter using urlParam: 'q' would conflict with search state. Consider adding a validation check in the factory or documenting reserved param names. Not blocking since the current built-in filters use safe names and the extension API is new.

Notes

  • The clearFilters behavior improvement (preserving view/pageSize) is a nice quality-of-life fix that aligns with the spec requirement.
  • The onCategoryClick conditional on AiAssetCard correctly handles the case where the category filter is disabled — no click handler is passed when the type filter isn't registered.
  • Task 4.5 in tasks.md says "Register 5 built-in filters" but only 4 exist — minor doc inconsistency.
Previous run (3)

Review — approve

Summary: This PR introduces an extensible, data-driven filter architecture for the AI Catalog browse sidebar using Backstage New Frontend System (NFS) blueprints. The FilterDefinition interface, AiCatalogFilterBlueprint, and filterDefinitionDataRef form a clean extensibility model. Built-in filters (category, provider, owner, tags) are refactored from hardcoded components into plain objects, and the FilterSidebar, useUrlFilters, useAiAssets, and applyEntityFilters are all updated to operate dynamically over registered filter definitions. The architecture is sound and follows Backstage NFS conventions well.

Highlights

  • Clean data-driven design: Filters are plain objects, not per-filter components. The generic <Select> rendering pattern in FilterSidebar is straightforward and avoids component proliferation.
  • Good NFS integration: AiCatalogFilterBlueprint with makeWithOverrides on the page, createExtensionInput, and filterDefinitionDataRef follows the Backstage extension blueprint pattern correctly.
  • Solid test coverage for core logic: New tests for builtInFilterDefinitions (164 lines) and entityHelpers (151 lines) cover getOptions, matchEntity, priority ordering, AND logic, search+filter combination, and edge cases. Existing useAiAssets and AiCatalogPage tests are updated for the new signatures.
  • Proper behavior improvements: clearFilters now preserves view and pageSize (previously it nuked all URL params), which is the correct UX behavior.
  • Third-party extensibility example: The sampleFilter module demonstrates the contribution pattern for deployers and third-party plugins.

Findings (low)

  1. Spec doc contradiction (filter-customization/spec.md): The "Disable multiple filters" scenario states the deployer disables both tags and owner, but then says "the category, provider, and owner filters render normally." Owner should not render if it was disabled. Minor copy error in a Draft spec.

  2. Missing unit tests for useUrlFilters and FilterSidebar: Both were significantly refactored — useUrlFilters now accepts dynamic filterParams[], replaced four named setters with a generic setFilter(), and changed clearFilters behavior. FilterSidebar went from hardcoded selects to a data-driven map. Task 4.16 explicitly calls for these tests but they are not included in this PR. The core filter logic is tested via builtInFilterDefinitions and entityHelpers, but the URL-to-state synchronization and component rendering of dynamic filters lack dedicated coverage.

  3. filterValues Map recomputes on unrelated URL changes: In useUrlFilters, filterValues depends on searchParams (the full URLSearchParams object), so any URL param change — including page, view, pageSize — recreates the Map and triggers applyEntityFilters to recompute. The old code's per-filter useMemo was more targeted. Not a correctness issue — just unnecessary work on pagination that could matter at scale.

  4. Page extension renamed page:boost to page:boost/ai-catalog: This is a breaking change for anyone who configured page:boost in their app-config.yaml. The blast radius is likely small since the boost frontend is new, but the changeset should note this rename for adopters.

All findings are low severity and do not block merge.


Labels: PR modifies the boost workspace, adds a new extensibility feature, and includes spec documentation

Previous run (4)

Review — approve

Well-structured PR that introduces NFS-extensible browse filters to the AI Catalog. The data-driven FilterDefinition approach is a clean architectural choice — filters as plain objects with a single generic <Select> renderer avoids per-filter component sprawl and aligns well with Backstage NFS patterns.

What was reviewed

  • AiCatalogFilterBlueprint and filterDefinitionDataRef — NFS extension blueprint and data ref
  • builtInFilterDefinitions.ts — 4 built-in filter definitions (category, provider, owner, tags)
  • plugin.tsxPageBlueprint.makeWithOverrides with filters extension input, built-in filter extension registration
  • FilterSidebar.tsx — refactored to generic data-driven rendering from FilterDefinition[]
  • useUrlFilters.ts — refactored to dynamic filterParams with generic setFilter(urlParam, values)
  • useAiAssets.ts — updated signature to (search, filters, filterValues)
  • entityHelpers.tsapplyEntityFilters replaced hardcoded filter blocks with AND loop over matchEntity
  • AiCatalogPage.tsx — receives FilterDefinition[] prop, dynamic hasActiveFilters detection
  • sampleFilter/index.ts — lifecycle filter example for third-party contribution
  • Tests: builtInFilterDefinitions.test.ts, entityHelpers.test.ts, updated useAiAssets.test.ts and AiCatalogPage.test.ts
  • Spec documents, design doc, architecture doc, changeset, API report

Strengths

  • Clean separation of concerns: Filter definitions are pure data, the blueprint handles NFS wiring, and the sidebar handles rendering. No leaky abstractions.
  • Improved clearFilters: The old implementation (setSearchParams(new URLSearchParams())) wiped all URL params including view and pageSize. The new version correctly preserves non-filter params — a behavioral improvement.
  • Comprehensive tests: New test suites cover built-in filter definitions (getOptions, matchEntity, case-insensitivity, priority ordering) and the refactored applyEntityFilters AND logic. Existing tests updated to match new signatures.
  • NFS integration follows Backstage patterns: makeWithOverrides with createExtensionInput, createExtensionBlueprint with generator factory, createExtensionDataRef — all used correctly.
  • Graceful degradation: FilterSidebar returns null when zero filters are registered. onCategoryClick is undefined when the category filter is disabled.

Findings

[low] Page extension name change — page:boostpage:boost/ai-catalog
plugin.tsx — The page extension ID changed from page:boost (unnamed) to page:boost/ai-catalog (named). Any deployer referencing page:boost in their app-config.yaml would need to update to page:boost/ai-catalog. Since the plugin appears new and this is the first extensibility PR, the impact is likely minimal, but the changeset should mention this as a breaking change for deployers who have already configured the page extension.

[low] Inline callback allocation in FilterSidebar
FilterSidebar.tsx — The onChange prop on each FilterSelect creates a new closure per render: onChange={vals => onFilterChange(filter.urlParam, vals)}. This defeats the useCallback inside FilterSelect. For a sidebar with 4–5 filters this is negligible, but if the filter count grows significantly, consider memoizing with useCallback and the urlParam as a dependency. Not blocking.

[low] Task doc says "5 built-in filters" but only 4 are implemented
tasks.md line 654: "Register 5 built-in filters" — the code registers 4 (category, provider, owner, tags). This appears to be a doc typo. Minor; doesn't affect code correctness.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jul 14, 2026
@rohitkrai03
rohitkrai03 force-pushed the feat/boost-ai-catalog-extensible-filters branch 2 times, most recently from c1377d2 to e15e24b Compare July 14, 2026 13:52
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 1:53 PM UTC · Ended 1:59 PM UTC
Commit: 8dea3d4 · View workflow run →

@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

feat(boost): Make AI Catalog browse filters NFS-extensible via FilterDefinition blueprint

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add AiCatalogFilterBlueprint to contribute/disable AI Catalog browse filters via NFS.
• Refactor browse filtering to be data-driven (FilterDefinition array + generic sidebar selects).
• Add unit tests, example third-party filter module, and design/spec documentation for stories 4–7.
Diagram

graph TD
  A["app-config.yaml (app.extensions)"] --> B["NFS Extension Registry"] --> C["PageBlueprint: ai-catalog"] --> D["AiCatalogPage"] --> E["FilterSidebar (generic Selects)"]
  D --> F["useUrlFilters (dynamic params)"] --> G["useAiAssets + applyEntityFilters"]
  H["Built-in filter extensions"] --> B
  I["3rd-party filter module"] --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Component-per-filter extensions
  • ➕ Max UI flexibility per filter (custom widgets, async option loading, complex layouts).
  • ➕ Avoids passing functions (getOptions/matchEntity) through extension data refs.
  • ➖ Higher maintenance: many components, more coupling to page internals, harder to test uniformly.
  • ➖ More complex extension contract; higher risk of inconsistent UX/accessibility across filters.
2. Schema-configured generic filters (YAML-driven)
  • ➕ Filters could be added/modified by deployers without authoring TypeScript modules.
  • ➕ Potentially safer than function-based predicates (restrict to known entity fields/operators).
  • ➖ Much less expressive for advanced matching/option derivation.
  • ➖ Requires designing and maintaining a DSL + validation + migration strategy.
3. Server-side filtering (catalog queryEntities)
  • ➕ Scales better for large catalogs; reduces client-side work.
  • ➕ Would align with URL params mapping directly to backend query filters.
  • ➖ Not all custom filter semantics map cleanly to catalog query filters.
  • ➖ More backend coupling and potentially multiple requests per filter change.

Recommendation: The PR’s data-driven FilterDefinition + NFS blueprint approach is the best trade-off for near-term extensibility: it keeps the sidebar generic/consistent, supports third-party contributions with minimal surface area, and preserves existing client-side behavior. If catalog scale becomes a concern, consider evolving useAiAssets to translate a subset of FilterDefinitions into server-side catalog filters while keeping the same page/extension API.

Files changed (25) +1729 / -332

Enhancement (10) +508 / -277
index.tsAdd sample frontend module contributing a lifecycle filter +71/-0

Add sample frontend module contributing a lifecycle filter

• Implements an example 'createFrontendModule' that contributes a custom 'lifecycle' filter via 'AiCatalogFilterBlueprint'. Demonstrates option derivation from entities and case-insensitive matching.

workspaces/boost/packages/app/src/modules/sampleFilter/index.ts

AiCatalogFilterBlueprint.tsIntroduce FilterDefinition + AiCatalogFilterBlueprint (NFS extension kind) +109/-0

Introduce FilterDefinition + AiCatalogFilterBlueprint (NFS extension kind)

• Adds the public 'FilterDefinition' interface and a single extension data ref ('ai-catalog-filter.definition'). Implements 'AiCatalogFilterBlueprint' to attach filter definitions to the AI Catalog page via a 'filters' input, with priority-based ordering support.

workspaces/boost/plugins/boost/src/blueprints/AiCatalogFilterBlueprint.ts

AiCatalogPage.tsxMake AiCatalogPage accept dynamic filters + new URL/filter pipeline +25/-16

Make AiCatalogPage accept dynamic filters + new URL/filter pipeline

• Changes 'AiCatalogPage' to accept 'FilterDefinition[]' and derive active URL params dynamically. Integrates 'useUrlFilters(filterParams)' and calls 'useAiAssets(search, filters, filterValues)', with dynamic active-filter detection and optional category click wiring.

workspaces/boost/plugins/boost/src/components/catalog/AiCatalogPage.tsx

FilterSidebar.tsxRefactor FilterSidebar to render generic Select per FilterDefinition +52/-86

Refactor FilterSidebar to render generic Select per FilterDefinition

• Replaces hardcoded per-filter Selects with a 'filters.map(...)' rendering pipeline. Adds 'labelKey' translation support and returns 'null' when no filters are registered/enabled.

workspaces/boost/plugins/boost/src/components/catalog/FilterSidebar.tsx

builtInFilterDefinitions.tsAdd built-in FilterDefinition objects (type/provider/owner/tag) +93/-0

Add built-in FilterDefinition objects (type/provider/owner/tag)

• Defines four built-in 'FilterDefinition' objects with stable urlParam keys and priorities. Encapsulates option extraction and matching logic (including case-insensitive comparisons) for use by the generic sidebar and filter pipeline.

workspaces/boost/plugins/boost/src/filters/builtInFilterDefinitions.ts

useAiAssets.tsRefactor useAiAssets to accept search + FilterDefinition[] + values map +9/-13

Refactor useAiAssets to accept search + FilterDefinition[] + values map

• Changes the hook API to separate 'search' from filter definitions and selected values. Applies filtering through the new 'applyEntityFilters(allEntities, search, filters, filterValues)' contract while keeping catalog fetch behavior unchanged.

workspaces/boost/plugins/boost/src/hooks/useAiAssets.ts

useUrlFilters.tsGeneralize URL filter state to dynamic filter params and setFilter API +71/-114

Generalize URL filter state to dynamic filter params and setFilter API

• Refactors URL synchronization to accept a dynamic list of filter param names and expose a generic 'setFilter(urlParam, values)' method. Produces 'filterValues' as a Map and updates 'clearFilters' to preserve view/pageSize while clearing search + registered filters.

workspaces/boost/plugins/boost/src/hooks/useUrlFilters.ts

index.tsExport new public filter extension API surface +7/-0

Export new public filter extension API surface

• Exports 'AiCatalogFilterBlueprint', 'filterDefinitionDataRef', and the 'FilterDefinition' type from the package entrypoint to enable third-party module contributions.

workspaces/boost/plugins/boost/src/index.ts

plugin.tsxWire filter extensions into ai-catalog page via makeWithOverrides input +55/-7

Wire filter extensions into ai-catalog page via makeWithOverrides input

• Registers built-in filters as 'ai-catalog-filter:*' extensions using 'AiCatalogFilterBlueprint.make'. Upgrades the AI Catalog page blueprint to 'makeWithOverrides' to collect 'filters' inputs, sort by priority, and pass them into the page loader.

workspaces/boost/plugins/boost/src/plugin.tsx

entityHelpers.tsRefactor applyEntityFilters to iterate FilterDefinitions + selected values map +16/-41

Refactor applyEntityFilters to iterate FilterDefinitions + selected values map

• Replaces the fixed EntityFilters shape and hardcoded field checks with a loop over registered FilterDefinitions. Applies search first, then AND-combines each active filter via 'filter.matchEntity(entity, values)'.

workspaces/boost/plugins/boost/src/utils/entityHelpers.ts

Tests (4) +376 / -16
AiCatalogPage.test.tsxUpdate AiCatalogPage test to pass resolved filter definitions +14/-1

Update AiCatalogPage test to pass resolved filter definitions

• Refactors the page test to construct a default 'FilterDefinition[]' using the built-in filter definitions. Ensures 'AiCatalogPage' is rendered with the new 'filters' prop.

workspaces/boost/plugins/boost/src/components/catalog/AiCatalogPage.test.tsx

builtInFilterDefinitions.test.tsAdd unit tests for built-in filter definitions +164/-0

Add unit tests for built-in filter definitions

• Adds tests validating each built-in filter’s option derivation and matching behavior, including case-insensitive matching. Verifies ascending priority ordering across the built-ins.

workspaces/boost/plugins/boost/src/filters/builtInFilterDefinitions.test.ts

useAiAssets.test.tsUpdate useAiAssets tests for new signature and FilterDefinition-driven filtering +47/-15

Update useAiAssets tests for new signature and FilterDefinition-driven filtering

• Updates hook tests to call 'useAiAssets(search, filters, values)' and verify filtering via provided FilterDefinitions. Preserves coverage for loading, fetch success, search, and error flows.

workspaces/boost/plugins/boost/src/hooks/useAiAssets.test.ts

entityHelpers.test.tsAdd unit tests for applyEntityFilters dynamic AND logic +151/-0

Add unit tests for applyEntityFilters dynamic AND logic

• Adds tests verifying search matching across name/description/tags, filter application through matchEntity, AND-combination across multiple filters, and case-insensitive search behavior.

workspaces/boost/plugins/boost/src/utils/entityHelpers.test.ts

Documentation (8) +837 / -39
design.mdAdd Decision 8: data-driven, NFS-extensible filter sidebar +49/-1

Add Decision 8: data-driven, NFS-extensible filter sidebar

• Documents the architectural decision to model browse filters as 'FilterDefinition' data rather than per-filter components. Describes blueprint wiring, page input collection, AND filtering logic, and deployer/third-party contribution examples.

workspaces/boost/openspec/changes/ai-catalog-frontend/design.md

spec.mdAdd draft spec for dynamic plugin export + overlay registration +119/-0

Add draft spec for dynamic plugin export + overlay registration

• Introduces requirements and scenarios for packaging the Boost frontend as an RHDH dynamic plugin using NFS Module Federation. Covers export scripts, default dynamic config, overlays registration, and adopter overrides.

workspaces/boost/openspec/changes/ai-catalog-frontend/specs/dynamic-plugin-export/spec.md

spec.mdAdd draft Playwright E2E testing spec +131/-0

Add draft Playwright E2E testing spec

• Defines requirements/scenarios for Playwright E2E coverage of the AI Catalog browse page. Includes multi-locale testing, URL assertions, and axe-core accessibility audits using translation keys.

workspaces/boost/openspec/changes/ai-catalog-frontend/specs/e2e-tests/spec.md

spec.mdAdd draft spec for NFS-based filter customization +172/-0

Add draft spec for NFS-based filter customization

• Specifies the 'FilterDefinition' contract, 'AiCatalogFilterBlueprint' behavior, built-in filter conversion, deployer disable semantics, and dynamic URL/filter pipeline behavior (including clear-filters rules).

workspaces/boost/openspec/changes/ai-catalog-frontend/specs/filter-customization/spec.md

spec.mdAdd draft spec for multi-locale translations +96/-0

Add draft spec for multi-locale translations

• Defines requirements for adding de/es/fr/it/ja translations, lazy locale imports, and dynamic-plugin translation auto-discovery. Includes quality and verification scenarios.

workspaces/boost/openspec/changes/ai-catalog-frontend/specs/translations/spec.md

tasks.mdAdd implementation task breakdown for stories 4–7 +76/-0

Add implementation task breakdown for stories 4–7

• Adds detailed task lists for extensible filters, translations, E2E testing, and dynamic plugin export/overlay registration. Encodes key refactor steps (URL state, sidebar rendering, applyEntityFilters loop, tests).

workspaces/boost/openspec/changes/ai-catalog-frontend/tasks.md

report.api.mdUpdate API report with new filter blueprint exports and page input +182/-26

Update API report with new filter blueprint exports and page input

• Updates the generated API report to include 'AiCatalogFilterBlueprint', 'FilterDefinition', and 'filterDefinitionDataRef' as public exports. Reflects the new 'page:boost/ai-catalog' extension shape with a 'filters' input.

workspaces/boost/plugins/boost/report.api.md

boost-frontend-architecture.mdUpdate architecture doc for NFS Module Federation, i18n, and testing plans +12/-12

Update architecture doc for NFS Module Federation, i18n, and testing plans

• Updates the technology stack documentation to reflect NFS Module Federation (not Scalprum), planned multi-locale i18n, and Playwright + axe-core E2E testing. Adjusts icon library reference as well.

workspaces/boost/specifications/boost-frontend-architecture.md

Other (3) +8 / -0
extensible-browse-filters.mdAdd minor changeset for extensible AI Catalog filters +5/-0

Add minor changeset for extensible AI Catalog filters

• Introduces a changeset bumping '@red-hat-developer-hub/backstage-plugin-boost' minor. Notes the new NFS-extensible filter blueprint and deployer/plugin customization support.

workspaces/boost/.changeset/extensible-browse-filters.md

app-config.yamlDocument how to disable a built-in filter via NFS config +2/-0

Document how to disable a built-in filter via NFS config

• Adds commented example showing how to disable a built-in AI Catalog filter using 'app.extensions' ('ai-catalog-filter:boost/owner: false').

workspaces/boost/app-config.yaml

App.tsxAdd (commented) import hook for sample filter module +1/-0

Add (commented) import hook for sample filter module

• Adds a commented import line for 'sampleFilterModule', indicating how the dev app can opt into the example third-party filter contribution.

workspaces/boost/packages/app/src/App.tsx

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 1:53 PM UTC · Completed 1:59 PM UTC
Commit: 8dea3d4 · View workflow run →

@rohitkrai03
rohitkrai03 force-pushed the feat/boost-ai-catalog-extensible-filters branch from e15e24b to a026c11 Compare July 14, 2026 14:01
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 2:02 PM UTC · Ended 2:08 PM UTC
Commit: 8dea3d4 · View workflow run →

@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 55.20000% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.65%. Comparing base (a04cf01) to head (3da5eaa).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3750      +/-   ##
==========================================
- Coverage   54.65%   54.65%   -0.01%     
==========================================
  Files        2360     2362       +2     
  Lines       90140    90147       +7     
  Branches    25214    25208       -6     
==========================================
- Hits        49270    49266       -4     
- Misses      40598    40661      +63     
+ Partials      272      220      -52     
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from a04cf01
ai-integrations 67.53% <ø> (ø) Carriedforward from a04cf01
app-defaults 69.79% <ø> (ø) Carriedforward from a04cf01
augment 46.39% <ø> (ø) Carriedforward from a04cf01
boost 72.84% <55.20%> (-0.30%) ⬇️
bulk-import 72.46% <ø> (ø) Carriedforward from a04cf01
cost-management 14.10% <ø> (ø) Carriedforward from a04cf01
dcm 61.81% <ø> (ø) Carriedforward from a04cf01
extensions 61.53% <ø> (ø) Carriedforward from a04cf01
global-floating-action-button 71.18% <ø> (ø) Carriedforward from a04cf01
global-header 59.71% <ø> (ø) Carriedforward from a04cf01
homepage 50.23% <ø> (ø) Carriedforward from a04cf01
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from a04cf01
konflux 91.49% <ø> (ø) Carriedforward from a04cf01
lightspeed 69.02% <ø> (ø) Carriedforward from a04cf01
mcp-integrations 85.46% <ø> (ø) Carriedforward from a04cf01
orchestrator 43.74% <ø> (ø) Carriedforward from a04cf01
quickstart 65.63% <ø> (ø) Carriedforward from a04cf01
sandbox 79.56% <ø> (ø) Carriedforward from a04cf01
scorecard 82.93% <ø> (ø) Carriedforward from a04cf01
theme 61.26% <ø> (ø) Carriedforward from a04cf01
translations 7.25% <ø> (ø) Carriedforward from a04cf01
x2a 78.68% <ø> (ø) Carriedforward from a04cf01

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a04cf01...3da5eaa. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed ready-for-merge All reviewers approved — ready to merge labels Jul 14, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:02 PM UTC · Completed 2:08 PM UTC
Commit: 8dea3d4 · View workflow run →

@rhdh-qodo-merge rhdh-qodo-merge Bot added documentation Improvements or additions to documentation enhancement New feature or request Tests labels Jul 14, 2026
@rohitkrai03
rohitkrai03 force-pushed the feat/boost-ai-catalog-extensible-filters branch from a026c11 to 0910e44 Compare July 14, 2026 14:34
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 2:35 PM UTC · Ended 2:41 PM UTC
Commit: 8dea3d4 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed ready-for-merge All reviewers approved — ready to merge labels Jul 14, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:35 PM UTC · Completed 2:41 PM UTC
Commit: 8dea3d4 · View workflow run →

@gabemontero gabemontero 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.

I posted my 2 cents on the latest fs-review comments @rohitkrai03 but I'm fine with whatever you do there

going ahead and approving

my review fwiw was totally centered on confirming my first take impression ... that this was totally a frontend issue, with no bearing on dependency on present/future backend work

@rohitkrai03
rohitkrai03 force-pushed the feat/boost-ai-catalog-extensible-filters branch from 0910e44 to 1c6fba1 Compare July 14, 2026 17:46
rohitkrai03 and others added 3 commits July 14, 2026 23:16
Add behavioral specs, tasks, and design decisions for new stories under
RHIDP-15164:

- Story 4 (RHIDP-15449): Extensible browse filters via NFS
  AiCatalogFilterBlueprint — specs/filter-customization/spec.md
- Story 5 (RHIDP-15479): Translations (de, es, fr, it, ja) —
  specs/translations/spec.md
- Story 6 (RHIDP-15480): E2E tests with Playwright —
  specs/e2e-tests/spec.md
- Story 7 (RHIDP-15481): Dynamic plugin export and overlay registration —
  specs/dynamic-plugin-export/spec.md

Updated design.md with Decision 8 (extensible filters), removed dynamic
plugin packaging from non-goals. Updated boost-frontend-architecture.md
to correct dynamic plugin mechanism (NFS Module Federation, not
Scalprum), i18n (5 locales planned), and testing (Playwright E2E).

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace per-filter React components and multiple extension data refs
with a single FilterDefinition interface. Filters are plain objects
(urlParam, label, getOptions, matchEntity, priority) rendered by a
generic <Select> in FilterSidebar. Reduces complexity from ~19 tasks
to 16, eliminates 5 component files, and cuts estimated change from
~1000 lines to ~400.

Key changes to spec, design, and tasks:
- Drop loader/AiCatalogFilterProps — filters are data, not components
- Single filterDefinitionDataRef instead of 3 separate data refs
- builtinFilters.ts holds 5 plain objects, no React imports
- FilterSidebar maps over FilterDefinition[] with generic Select
- useUrlFilters accepts dynamic urlParam[] instead of hardcoded setters

Co-authored-by: Cursor <cursoragent@cursor.com>
Replace hardcoded filter sidebar with data-driven FilterDefinition
objects registered as NFS extensions via AiCatalogFilterBlueprint.

New files:
- blueprints/AiCatalogFilterBlueprint.ts: FilterDefinition interface,
  filterDefinitionDataRef, AiCatalogFilterBlueprint
- filters/builtInFilterDefinitions.ts: 4 built-in definitions
  (category, provider, owner, tags)
- modules/sampleFilter/: dev app lifecycle filter demonstrating
  third-party extension contribution

Refactored:
- plugin.tsx: page makeWithOverrides with name 'ai-catalog' and
  filters input, 4 filter extensions registered
- FilterSidebar: generic Select per FilterDefinition, label/labelKey
  i18n pattern
- useUrlFilters: dynamic filterParams, generic setFilter, clearFilters
  preserves view/pageSize
- applyEntityFilters: AND loop over matchEntity
- AiCatalogPage: receives filters prop, derives categoryUrlParam from
  filters array
- useAiAssets: new signature (search, filters, filterValues)

Public API: AiCatalogFilterBlueprint, filterDefinitionDataRef,
FilterDefinition exported from index.ts

Co-authored-by: Cursor <cursoragent@cursor.com>
@rohitkrai03
rohitkrai03 force-pushed the feat/boost-ai-catalog-extensible-filters branch from 1c6fba1 to 3da5eaa Compare July 14, 2026 17:46
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 14, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 5:47 PM UTC · Ended 5:53 PM UTC
Commit: a04cf01 · View workflow run →

@sonarqubecloud

Copy link
Copy Markdown

const tagOptions = useMemo(
() => uniqueOptions(entities.flatMap(e => e.metadata.tags ?? [])),
[entities],
return (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] performance

The onChange handler vals => onFilterChange(filter.urlParam, vals) creates a new closure for each filter on every FilterSidebar render, defeating FilterSelect's internal useCallback. With 4-10 filters this is negligible but could become relevant as filter count grows.

Suggested fix: Consider memoizing per-filter onChange callbacks or using a stable callback pattern if filter count exceeds ~10.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed ready-for-merge All reviewers approved — ready to merge labels Jul 14, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 5:47 PM UTC · Completed 5:53 PM UTC
Commit: a04cf01 · View workflow run →

@mareklibra mareklibra left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Having better test coverage for the AiCatalogFilterBlueprint would be great as this aims to be a generic functionality.

import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha';

import { navModule } from './modules/nav';
// import { sampleFilterModule } from './modules/sampleFilter';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since this is a dev instance only, can we enable the sampleFilter by default?

filters: createExtensionInput([filterDefinitionDataRef]),
},
factory(originalFactory, { inputs }) {
const filterDefs = inputs.filters

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What about ensuring uniqueness? This PR is about third-party extensibility.

Two filters (e.g. a third-party one reusing type/owner/provider/tag) can register the same urlParam, causing duplicate React keys in FilterSidebar and silently shared/overwritten state in useUrlFilters's filterValues map.

Consider validating uniqueness across the resolved filter set (in plugin.tsx's factory or the blueprint factory) and throwing/logging a clear error on collision.

| Accessibility | WCAG 2.1 AA, keyboard navigation, screen reader support |
| Layer | Technology |
| ----------------- | ------------------------------------------------------------------------------------------------------------ |
| Component library | BUI (`@backstage/ui`) for new components, MUI v5 fallback where BUI lacks coverage, `@remixicon/react` icons |

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove the @mui/icons-material change, removed by #3736

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request ready-for-merge All reviewers approved — ready to merge Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants