feat(boost): extensible browse filters via NFS (RHIDP-15449)#3750
feat(boost): extensible browse filters via NFS (RHIDP-15449)#3750rohitkrai03 wants to merge 3 commits into
Conversation
|
Important This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior. Changed Packages
|
|
🤖 Finished Review · ✅ Success · Started 1:39 PM UTC · Completed 1:44 PM UTC |
Review — approveThis PR introduces extensible browse filters for the AI Catalog via the Backstage New Frontend System (NFS). The architecture is clean: filters are data ( What was reviewed
FindingsLow: Missing unit tests for
|
c1377d2 to
e15e24b
Compare
|
🤖 Review · ❌ Terminated · Started 1:53 PM UTC · Ended 1:59 PM UTC |
PR Summary by Qodofeat(boost): Make AI Catalog browse filters NFS-extensible via FilterDefinition blueprint
AI Description
Diagram
High-Level Assessment
Files changed (25)
|
|
🤖 Finished Review · ❌ Failure · Started 1:53 PM UTC · Completed 1:59 PM UTC |
e15e24b to
a026c11
Compare
|
🤖 Review · ❌ Terminated · Started 2:02 PM UTC · Ended 2:08 PM UTC |
Codecov Report❌ Patch coverage is 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
*This pull request uses carry forward flags. Click here to find out more. Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
|
🤖 Finished Review · ✅ Success · Started 2:02 PM UTC · Completed 2:08 PM UTC |
a026c11 to
0910e44
Compare
|
🤖 Review · ❌ Terminated · Started 2:35 PM UTC · Ended 2:41 PM UTC |
|
🤖 Finished Review · ✅ Success · Started 2:35 PM UTC · Completed 2:41 PM UTC |
gabemontero
left a comment
There was a problem hiding this comment.
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
0910e44 to
1c6fba1
Compare
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>
1c6fba1 to
3da5eaa
Compare
|
🤖 Review · ❌ Terminated · Started 5:47 PM UTC · Ended 5:53 PM UTC |
|
| const tagOptions = useMemo( | ||
| () => uniqueOptions(entities.flatMap(e => e.metadata.tags ?? [])), | ||
| [entities], | ||
| return ( |
There was a problem hiding this comment.
[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.
|
🤖 Finished Review · ✅ Success · Started 5:47 PM UTC · Completed 5:53 PM UTC |
mareklibra
left a comment
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
Since this is a dev instance only, can we enable the sampleFilter by default?
| filters: createExtensionInput([filterDefinitionDataRef]), | ||
| }, | ||
| factory(originalFactory, { inputs }) { | ||
| const filterDefs = inputs.filters |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
Please remove the @mui/icons-material change, removed by #3736



FIxes
Summary
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 viaapp-config.yamlFilterSidebarwith a data-driven approach: each filter is a plainFilterDefinitionobject (urlParam, label/labelKey, getOptions, matchEntity, priority) rendered by a generic<Select>Changes
New files:
src/blueprints/AiCatalogFilterBlueprint.ts—FilterDefinition,filterDefinitionDataRef,AiCatalogFilterBlueprintsrc/filters/builtInFilterDefinitions.ts— 4 built-in definitions (category, provider, owner, tags)packages/app/src/modules/sampleFilter/— lifecycle filter as third-party contribution examplespecs/{filter-customization,translations,e2e-tests,dynamic-plugin-export}/spec.mdRefactored:
plugin.tsx— pagemakeWithOverrideswithname: 'ai-catalog'andfiltersinputFilterSidebar— generic<Select>perFilterDefinition,label/labelKeyi18nuseUrlFilters— dynamicfilterParams[], genericsetFilter(),clearFilterspreserves view/pageSizeapplyEntityFilters— AND loop overmatchEntity, no hardcoded field namesuseAiAssets— new signature(search, filters, filterValues)Public API:
AiCatalogFilterBlueprint,filterDefinitionDataRef,FilterDefinitionTest plan
yarn tsc --noEmit— 0 errorsyarn build:api-reports:only— report updated withpage:boost/ai-catalogand new exportsyarn test --no-watch— 51 suites, 526 tests passingminorfor@red-hat-developer-hub/backstage-plugin-boost)yarn start→ verify 4 built-in filters + lifecycle sample filter render at/ai-catalogai-catalog-filter:boost/owner: falsein app-config disables the owner filterScreenshot
Filter.Customization.mov
Made with Cursor