Skip to content

Conversation

@carlosthe19916
Copy link
Collaborator

@carlosthe19916 carlosthe19916 commented Sep 30, 2025

  • This PR adds filtering by license in the SBOM and Package List pages
  • As we expect thousands of Licenses then the License Filter dropdown is fetched asynchronously so it populates the dropdown with GET /api/v1/license.
  • The user can select one or more licenses to apply filterting.
image

Summary by Sourcery

Enable filtering SBOM and package lists by license through an asynchronous multi-select control and new license API endpoint.

New Features:

  • Add SBOM and package list pages filter by license using an async multi-select filter.
  • Introduce a new /api/v2/license endpoint and corresponding OpenAPI schemas for listing licenses.

Enhancements:

  • Bump OpenAPI client version to 0.4.0-beta.1.
  • Add useFetchLicenses React query hook to fetch licenses from the backend.
  • Extend table control logic to support a new multiselectAsync filter type.
  • Implement MultiSelect, AsyncMultiselectFilterControl, SearchInputComponent, and useMultiSelectHandlers for async dropdown filtering.
Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Sep 30, 2025

Reviewer's Guide

This PR adds license-based filtering to SBOM and package list pages by extending the OpenAPI spec with a new license endpoint, introducing a useFetchLicenses query hook, extending the filter framework with an async multiselect filter type and UI components, and integrating this new filter into the SBOM and package contexts.

Class diagram for new and updated filter components

classDiagram class MultiSelect { +id: string +onChange(selections: MultiSelectOptionProps[]): void +options: MultiSelectOptionProps[] +selections: MultiSelectOptionProps[] +placeholderText: string +searchString: string +searchInputAriaLabel: string +labelColor +noResultsMessage: string +showChips: boolean +onSearchChange(value: string): void +isDisabled: boolean +isScrollable: boolean } class SearchInputComponent { +id: string +placeholder: string +ariaLabel: string +onSearchChange(value: string): void +onClear(): void +onKeyHandling(event): void +onClick(): void +inputValue: string +inputRef +selections: MultiSelectOptionProps[] +isDropdownOpen: boolean +activeItem: MultiSelectOptionProps } class useAutocompleteHandlers { +options: MultiSelectOptionProps[] +searchString: string +selections: MultiSelectOptionProps[] +onChange(selections: MultiSelectOptionProps[]): void +menuRef +searchInputRef +onSearchChange(value: string): void } class AsyncMultiselectFilterControl { +category: IMultiselectFilterCategory +filterValue +setFilterValue +showToolbarItem +isDisabled: boolean } class MultiSelectOptionProps { +id: string +name: string | () => string +labelName: string | () => string +tooltip: string | () => string +optionProps } MultiSelect --> SearchInputComponent MultiSelect --> useAutocompleteHandlers AsyncMultiselectFilterControl --> MultiSelect MultiSelectOptionProps <.. MultiSelect MultiSelectOptionProps <.. AsyncMultiselectFilterControl 
Loading

Class diagram for useFetchLicenses query hook

classDiagram class useFetchLicenses { +params: HubRequestParams +disableQuery: boolean +result: { data: LicenseText[], total: number, params } +isFetching: boolean +fetchError: AxiosError +refetch } class LicenseText { +license: string } useFetchLicenses --> LicenseText 
Loading

File-Level Changes

Change Details Files
Extend OpenAPI spec with license listing endpoint and schemas
  • Bump trustd.yaml version to 0.4.0-beta.1
  • Add /api/v2/license GET path with query parameters for filtering, sorting, pagination
  • Define LicenseText and PaginatedResults_LicenseText response schemas
client/openapi/trustd.yaml
Introduce useFetchLicenses hook for querying licenses
  • Create useFetchLicenses in app/queries/licenses.ts using react-query
  • Define LicensesQueryKey and return typed result with data, total, params
client/src/app/queries/licenses.ts
Extend filter framework to support async multiselect
  • Add FilterType.multiselectAsync enum value
  • Handle multiselectAsync in FilterControl and getFilterHubRequestParams
  • Import and use async control in FilterToolbar
client/src/app/components/FilterToolbar/FilterToolbar.tsx
client/src/app/components/FilterToolbar/FilterControl.tsx
client/src/app/hooks/table-controls/filtering/getFilterHubRequestParams.ts
Implement reusable Async Multiselect UI components
  • Add MultiSelect component with typeahead, chips and selection logic
  • Create useAutocompleteHandlers hook for keyboard and selection management
  • Build AsyncMultiselectFilterControl and SearchInput components
  • Define MultiSelectOptionProps type
client/src/app/components/MultiSelect/MultiSelect.tsx
client/src/app/components/MultiSelect/useMultiSelectHandlers.ts
client/src/app/components/MultiSelect/SearchInput.tsx
client/src/app/components/MultiSelect/type-utils.ts
client/src/app/components/FilterToolbar/AsyncMultiselectFilterControl.tsx
Integrate license filter into SBOM and package contexts
  • Import useFetchLicenses and useDebounceValue
  • Add state and debounced input for license search
  • Fetch licenses and map to selectOptions
  • Append new license multiselectAsync category to filterDefinitions
client/src/app/pages/sbom-list/sbom-context.tsx
client/src/app/pages/package-list/package-context.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review: ## Individual Comments ### Comment 1 <location> `client/src/app/components/FilterToolbar/AsyncMultiselectFilterControl.tsx:30-31` </location> <code_context> +}: React.PropsWithChildren< + IMultiselectFilterControlProps<TItem> +>): React.JSX.Element | null => { + const optionMap = React.useRef( + new Map<string, FilterSelectOptionProps | null>(), + ); + </code_context> <issue_to_address> **issue:** The 'optionMap' is initialized but never populated. Since 'optionMap' remains empty, 'getOptionFromOptionValue' cannot perform lookups. Consider initializing 'optionMap' with values from 'selectOptions' if label lookup is required. </issue_to_address> ### Comment 2 <location> `client/src/app/components/MultiSelect/MultiSelect.tsx:87-88` </location> <code_context> + onSearchChange, + }); + + const createItemId = (value: string) => + `select-typeahead-${value.replace(" ", "-")}`; + + const inputGroup = ( </code_context> <issue_to_address> **suggestion:** The 'replace' only replaces the first space in the value. Use 'replaceAll' or a regular expression to ensure all spaces are replaced for consistent ID formatting. Suggested implementation: ```typescript const createItemId = (value: string) => `select-typeahead-${value.replaceAll(" ", "-")}`; ``` If you need to support environments where `replaceAll` is not available, you can use: `value.replace(/\s/g, "-")` instead of `value.replaceAll(" ", "-")`. </issue_to_address> ### Comment 3 <location> `client/src/app/components/MultiSelect/useMultiSelectHandlers.ts:104-113` </location> <code_context> + } + }; + + const handleMenuArrowKeys = (key: string) => { + let indexToFocus = 0; + if (isDropdownOpen) { +  if (key === "ArrowUp") { +  if (focusedItemIndex === null || focusedItemIndex === 0) { +  indexToFocus = options.length - 1; +  } else { +  indexToFocus = focusedItemIndex - 1; +  } +  } +  if (key === "ArrowDown") { +  if ( +  focusedItemIndex === null || +  focusedItemIndex === options.length - 1 +  ) { +  indexToFocus = 0; +  } else { +  indexToFocus = focusedItemIndex + 1; +  } +  } + } + setFocusedItemIndex(indexToFocus); + const focusedItem = options.filter( +  ({ optionProps }) => !optionProps?.isDisabled, + )[indexToFocus]; + setActiveItem(focusedItem); + }; + </code_context> <issue_to_address> **issue (bug_risk):** Filtering out disabled options may cause index mismatch. Index calculations should be based on the filtered array to ensure 'focusedItem' corresponds to the correct option. </issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
# Conflicts: #	client/src/app/pages/package-list/package-context.tsx
Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
@codecov
Copy link

codecov bot commented Sep 30, 2025

Codecov Report

❌ Patch coverage is 46.15385% with 91 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.26%. Comparing base (5d5b7a2) to head (b217139).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...ponents/AsyncMultiSelect/useMultiSelectHandlers.ts 22.72% 51 Missing ⚠️
...ts/FilterToolbar/AsyncMultiselectFilterControl.tsx 40.00% 18 Missing and 3 partials ⚠️
...ents/FilterPanel/AsyncMultiselectFilterControl.tsx 52.17% 8 Missing and 3 partials ⚠️
...p/components/AsyncMultiSelect/AsyncMultiSelect.tsx 71.42% 2 Missing and 2 partials ⚠️
...rc/app/components/AsyncMultiSelect/SearchInput.tsx 33.33% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@ Coverage Diff @@ ## main #759 +/- ## ========================================== - Coverage 59.91% 58.26% -1.65%  ========================================== Files 157 163 +6 Lines 2699 2868 +169 Branches 612 652 +40 ========================================== + Hits 1617 1671 +54  - Misses 849 956 +107  - Partials 233 241 +8 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
@carlosthe19916 carlosthe19916 added the backport release/0.4.z This PR should be backported to release/0.4.z branch. label Sep 30, 2025
@carlosthe19916 carlosthe19916 changed the title [WIP] feat: SBOM|Package list pages -> filtering by license feat: SBOM|Package list pages -> filtering by license Sep 30, 2025
@gildub gildub mentioned this pull request Oct 1, 2025
Copy link
Contributor

@gildub gildub left a comment

Choose a reason for hiding this comment

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

It works just fine.
I can't believe how many components are needed to get the Licence filter.
Besides cosmetics my only concern is the issue about refetching licenses systematically. Please see inline.

Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
@gildub gildub self-requested a review October 1, 2025 11:43
@gildub gildub enabled auto-merge October 1, 2025 11:44
@gildub gildub added this pull request to the merge queue Oct 1, 2025
Merged via the queue into guacsec:main with commit 307eabd Oct 1, 2025
11 checks passed
@carlosthe19916
Copy link
Collaborator Author

/backport

1 similar comment
@carlosthe19916
Copy link
Collaborator Author

/backport

github-actions bot pushed a commit that referenced this pull request Oct 1, 2025
Signed-off-by: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com> (cherry picked from commit 307eabd)
@trustify-ci-bot
Copy link
Contributor

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

Labels

backport release/0.4.z This PR should be backported to release/0.4.z branch.

2 participants