Refactor #39333
openBookmark#controller has inconsistent semantics
Description
Summary
The controller column on the Bookmark model is named after Rails controllers, but in practice it stores a mix of at least three different kinds of values with no consistent semantics. This causes confusion, leaks implementation details into the database, and requires increasingly awkward workarounds as new pages are added.
Expected outcome
Rethink bookmark handling and storage, as well as its interaction with autocompletion. Settle on a common way of doing things and unify the existing approaches to use the new way.
Background
Bookmarks store a saved search query scoped to a particular "page" in the UI. The controller column serves two distinct runtime purposes:
- Filtering —
GET /api/v2/bookmarks?search=controller=<value>retrieves bookmarks for the current page. - Autocomplete URL — in server-rendered Foreman views,
<value>/auto_complete_searchis used as the autocomplete endpoint.
Because these two purposes happen to coincide for simple non-STI resources (where the Rails controller name, the DB table name, and the permission resource name are all the same string), the column was named after one of them. But the coincidence does not hold in general.
Three different value formats currently in the database
1. Permission resource names, tableized — the canonical format, used by server-rendered Foreman views and Katello's TableWrapper-based pages (like hosts, config_reports, provisioning_templates, ptables, report_templates, common_parameters, katello_content_views, katello_errata, katello_subscriptions, katello_alternate_content_sources, …)
These come from Permission.resources.map(&:tableize) and coincide with Rails controller_name for most resources. However they do not coincide with the DB table name for STI models: ProvisioningTemplate, Ptable, and ReportTemplate all live in the templates table, and CommonParameter lives in the parameters table — yet the stored values are provisioning_templates, ptables, report_templates, and common_parameters respectively.
2. Raw API URL paths — used by newer pages built on Foreman's TableIndexPage component, where the controller prop doubles as the autocomplete URL prefix:
/katello/api/v2/flatpak_remotes/katello/api/v2/flatpak_remote_repositories/katello/api/v2/host_bootc_images
These end up literally stored in the controller column. Because BookmarkControllerValidator cannot derive them from DB tables or permission resources, Katello has to enumerate them by hand in BookmarkControllerValidatorExtensions#valid_controllers_list.
3. Hardcoded special cases in the validator:
- dashboard, common_parameters - common_parameters is actually already covered by Permission.resources.map(&:tableize) making it redundant.
Additionally, the bookmark controller validator is overly permissive. It builds its allowlist from three sources - db tables, permissions and hardcoded values. The DB table names source admits values like templates and parameters that are never actually stored (their STI subclass permission names are stored instead). The validator cannot express "only permission resource names" cleanly, so it over-approximates with DB tables as a fallback, admitting a superset of the values that are legitimately used.
Frontend split that exposes the problem
Katello's TableWrapper component correctly separates the two concerns with distinct props:
autocompleteEndpoint — the API URL for autocomplete (e.g. /katello/api/v2/content_views)bookmarkController — the value stored/filtered on (e.g. katello_content_views)
Foreman's TableIndexPage (used by newer Katello pages such as FlatpakRemotes) conflates them into a single controller prop fed through getControllerSearchProps(controller), which uses the same value for both the autocomplete URL and the bookmark filter. For Katello API-namespaced pages, the only controller value that works for autocomplete is the full API path, which is then also stored verbatim in the DB.
No data to display