Issue 2049 : Use TokenPaginatedTable for Component Search#438
Issue 2049 : Use TokenPaginatedTable for Component Search#438sahibamittal wants to merge 6 commits intomainfrom
TokenPaginatedTable for Component Search#438Conversation
Replace the legacy bootstrap-table with TokenPaginatedTable and update table props/behaviour to use token-based pagination.
Up to standards ✅🟢 Issues
|
There was a problem hiding this comment.
Pull request overview
Migrates the Portfolio Component Search view from legacy bootstrap-table server pagination to the shared TokenPaginatedTable component, aligning the frontend with the backend’s token-based pagination API (Issue #2049 / hyades-apiserver#1867).
Changes:
- Replaced
<bootstrap-table>usage with<token-paginated-table>and switched to/api/v2/componentswith query-param based filtering + sorting. - Updated search UI/logic (notably HASH search now includes hash-type selection) and refactored query param construction to an object-based approach.
- Simplified
hashes_short_desctranslations across multiple locales.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/views/portfolio/components/ComponentSearch.vue | Migrates component search results to TokenPaginatedTable; updates filters, sorting, and HASH search UI. |
| src/i18n/locales/en.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/de.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/es.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/fr.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/hi.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/it.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/ja.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/pl.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/pt.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/pt-BR.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/ru.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/uk-UA.json | Simplifies hashes_short_desc string. |
| src/i18n/locales/zh.json | Simplifies hashes_short_desc string. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| return params; | ||
| }, | ||
| performSearch: function () { |
There was a problem hiding this comment.
performSearch() can be triggered via the Enter key handlers even when isSearchDisabled is true (e.g., HASH search without hashType), which will apply incomplete filters and potentially issue invalid/ineffective requests. Add a guard in performSearch() to no-op when isSearchDisabled is true (or remove the Enter handlers / validate required fields inside createQueryParams).
| performSearch: function () { | |
| performSearch: function () { | |
| if (this.isSearchDisabled) { | |
| return; | |
| } |
| if (this.coordinatesGroup) | ||
| params.group = common.trimToNull(this.coordinatesGroup); | ||
| if (this.coordinatesName) | ||
| params.name = common.trimToNull(this.coordinatesName); | ||
| if (this.coordinatesVersion) | ||
| params.version = common.trimToNull(this.coordinatesVersion); |
There was a problem hiding this comment.
In the COORDINATES branch, you check if (this.coordinatesGroup) before trimming. Whitespace-only input is truthy, so common.trimToNull() can yield null, and common.setQueryParams() will serialize it as the literal string "null" (e.g., group=null). Trim first and only add the param if the trimmed value is non-null.
| if (this.coordinatesGroup) | |
| params.group = common.trimToNull(this.coordinatesGroup); | |
| if (this.coordinatesName) | |
| params.name = common.trimToNull(this.coordinatesName); | |
| if (this.coordinatesVersion) | |
| params.version = common.trimToNull(this.coordinatesVersion); | |
| let group = common.trimToNull(this.coordinatesGroup); | |
| if (group) params.group = group; | |
| let name = common.trimToNull(this.coordinatesName); | |
| if (name) params.name = name; | |
| let version = common.trimToNull(this.coordinatesVersion); | |
| if (version) params.version = version; |
| `<a href="${url}">${xssFilters.inHTMLData(value)}</a>` | ||
| : `<a href="${url}">${xssFilters.inHTMLData(value)}</a>`; | ||
| return ( | ||
| `<a href="${dependencyGraphUrl}"<i class="fa fa-sitemap" aria-hidden="true" style="float:right; padding-top: 4px; cursor:pointer" data-toggle="tooltip" data-placement="bottom" title="Show in dependency graph"></i></a> ` + |
There was a problem hiding this comment.
The component name column formatter generates invalid HTML: the dependency graph link is missing a > after the href attribute (<a href="..."<i ...>). This will break rendering/click behavior and may interfere with tooltips/events. Fix the anchor tag markup so it is valid HTML.
| `<a href="${dependencyGraphUrl}"<i class="fa fa-sitemap" aria-hidden="true" style="float:right; padding-top: 4px; cursor:pointer" data-toggle="tooltip" data-placement="bottom" title="Show in dependency graph"></i></a> ` + | |
| `<a href="${dependencyGraphUrl}"><i class="fa fa-sitemap" aria-hidden="true" style="float:right; padding-top: 4px; cursor:pointer" data-toggle="tooltip" data-placement="bottom" title="Show in dependency graph"></i></a> ` + |
| placeholder="Select hash type" | ||
| v-model="hashType" | ||
| :options="hashTypes" | ||
| class="mr-2" | ||
| ></b-form-select> |
There was a problem hiding this comment.
<b-form-select> does not support a placeholder prop (BootstrapVue expects a null option like { value: null, text: '…', disabled: true } to emulate a placeholder). As written, the placeholder will be ignored and hashType may remain null while the UI shows the first option, keeping the Search button disabled. Add an explicit placeholder option (or initialize hashType to a valid default) so the model and UI state are consistent.
| placeholder="Select hash type" | |
| v-model="hashType" | |
| :options="hashTypes" | |
| class="mr-2" | |
| ></b-form-select> | |
| v-model="hashType" | |
| :options="hashTypes" | |
| class="mr-2" | |
| > | |
| <b-form-select-option :value="null" disabled>Select hash type</b-form-select-option> | |
| </b-form-select> |
| tableOptions: { | ||
| showColumns: true, | ||
| showRefresh: true, | ||
| pagination: true, | ||
| silentSort: false, | ||
| toolbar: '#componentSearchToolbar', | ||
| sidePagination: 'server', | ||
| queryParamsType: 'pageSize', | ||
| pageList: '[10, 25, 50, 100]', | ||
| pageSize: | ||
| localStorage && | ||
| localStorage.getItem('ComponentSearchPageSize') !== null | ||
| ? Number(localStorage.getItem('ComponentSearchPageSize')) | ||
| : 10, | ||
| sortName: | ||
| localStorage && | ||
| localStorage.getItem('ComponentSearchSortName') !== null | ||
| ? localStorage.getItem('ComponentSearchSortName') | ||
| : undefined, | ||
| sortOrder: | ||
| localStorage && | ||
| localStorage.getItem('ComponentSearchSortOrder') !== null | ||
| ? localStorage.getItem('ComponentSearchSortOrder') | ||
| : undefined, | ||
| icons: { | ||
| refresh: 'fa-refresh', | ||
| }, | ||
| //toolbar: '#componentSearchToolbar', | ||
| responseHandler: function (res, xhr) { | ||
| res.total = xhr.getResponseHeader('X-Total-Count'); | ||
| return res; | ||
| }, | ||
| url: `${this.$api.BASE_URL}/${this.$api.URL_COMPONENT}/identity`, | ||
| onPageChange: (number, size) => { | ||
| if (localStorage) { | ||
| localStorage.setItem('ComponentSearchPageSize', size.toString()); | ||
| } | ||
| }, | ||
| onColumnSwitch: (field, checked) => { | ||
| if (localStorage) { | ||
| localStorage.setItem( | ||
| 'ComponentSearchShow' + common.capitalize(field), | ||
| checked.toString(), | ||
| ); | ||
| } | ||
| }, | ||
| sortName: 'name', | ||
| sortOrder: 'asc', | ||
| customSort: () => {}, | ||
| onSort: (name, order) => { | ||
| if (localStorage) { | ||
| localStorage.setItem('ComponentSearchSortName', name); | ||
| localStorage.setItem('ComponentSearchSortOrder', order); | ||
| } | ||
| this.sortBy = name; | ||
| this.sortDirection = order; | ||
| }, | ||
| }, |
There was a problem hiding this comment.
This migration drops column visibility / sort preference persistence for the ComponentSearch table: the previous onColumnSwitch localStorage writes and loadUserPreferencesForBootstrapTable hook are no longer connected, so user column toggles won't be restored (see loadUserPreferencesForBootstrapTable in src/shared/utils.js:102-129). If TokenPaginatedTable doesn't handle this internally, consider wiring the same persistence behavior via bootstrap-table events (or enhancing TokenPaginatedTable to apply preferences).
| mounted() { | ||
| this.changeSearchUrl = true; | ||
| }, |
There was a problem hiding this comment.
changeSearchUrl is now only assigned (beforeMount/mounted) but no longer read anywhere, and tableBaseUrl is declared but unused. Similarly, onPreBody() is no longer wired to any table event after switching to TokenPaginatedTable. Removing these dead fields/methods will avoid confusion and make it clearer how initial searches and URL state are intended to work.
| tableDataBaseUrl() { | ||
| if (!this.appliedFilters) return null; | ||
| const url = `${this.$api.BASE_URL}/api/v2/components`; | ||
| const queryParams = { ...this.appliedFilters }; | ||
| const sortBy = this.sortBy || 'name'; | ||
| const sortDirection = this.sortDirection || 'asc'; | ||
| queryParams.sort_by = sortBy; | ||
| queryParams.sort_direction = sortDirection.toUpperCase(); | ||
| return common.setQueryParams(url, queryParams); |
There was a problem hiding this comment.
TokenPaginatedTable calls loadPage(this.baseUrl) on mount and whenever baseUrl changes, but tableDataBaseUrl() returns null until appliedFilters is set. This will result in runtime errors (e.g., url.startsWith in common.setQueryParams) and the table never loading for deep-linked searches (since performSearch() is no longer invoked during initial route-hash parsing). Consider rendering the table only when a non-empty base URL exists (e.g., v-if), and/or initializing appliedFilters during beforeMount/mounted when the route hash provides initial search values.

Description
Replace the legacy bootstrap-table with TokenPaginatedTable and update table props/behaviour to use token-based pagination.
Addressed Issue
Issue: DependencyTrack/hyades#2049
Backend: DependencyTrack/hyades-apiserver#1867
Additional Details
Checklist