Apply hooks search filter before pagination by nitishagar · Pull Request #8368 · taskcluster/taskcluster · GitHub
Skip to content

Apply hooks search filter before pagination#8368

Open
nitishagar wants to merge 1 commit into
taskcluster:mainfrom
nitishagar:fix/7972-hooks-search
Open

Apply hooks search filter before pagination#8368
nitishagar wants to merge 1 commit into
taskcluster:mainfrom
nitishagar:fix/7972-hooks-search

Conversation

@nitishagar

@nitishagar nitishagar commented Mar 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #7972.

Refactors the hooks search to use a dedicated searchHooks API endpoint instead of
overloading listHookGroups. This was done per reviewer feedback in
#8368 (comment).

Root cause: The previous approach added a search query param to listHookGroups,
which didn't match its natural contract (it returns groups but was filtering by hook IDs).
The initial page load was also slow due to N+1 requests when fetching hook IDs per group.

Fix:

  1. New db/versions/0123.yml: adds search_hooks(search_term_in, page_size, page_offset)
    a single ILIKE query across hook_group_id and hook_id, returns full hook rows
  2. New GET /hooks/search?q=<term> API endpoint (searchHooks) — returns matching
    hook definitions across all groups (same shape as listHooks)
  3. listHookGroups reverted to its clean contract (no search param, uses get_hook_groups())
  4. GraphQL: hookGroups no longer takes search; new searchHooks(query: String!): [Hook] added
  5. UI: when a ?search= URL param is present, fetches from searchHooks and renders a flat
    HooksListTable; otherwise renders the normal HookGroupsTable

Initial page load is fast (group IDs only). Searches trigger a single efficient DB query.

Changes

DB:

  • db/versions/0123.yml — new search_hooks function (replaces get_hook_groups_2)
  • db/test/fns/hooks_test.js — tests for search_hooks
  • db/test/versions/0123_test.js — version smoke test

API (services/hooks):

  • services/hooks/schemas/v1/search-hooks-response.yml — new schema (same shape as listHooks)
  • services/hooks/src/api.js — reverted listHookGroups, added searchHooks at /hooks/search
  • services/hooks/test/api_test.js — tests for searchHooks

GraphQL/Web-server:

  • services/web-server/src/graphql/Hooks.graphql — removed search from hookGroups, added searchHooks
  • services/web-server/src/loaders/hooks.js — new hookSearch DataLoader
  • services/web-server/src/resolvers/Hooks.js — new searchHooks resolver

UI:

  • ui/src/views/Hooks/ListHookGroups/hookGroups.graphql — simplified (no $search)
  • ui/src/views/Hooks/ListHookGroups/searchHooks.graphql — new query
  • ui/src/views/Hooks/ListHookGroups/index.jsx — two-mode rendering

Generated (from yarn generate):

  • generated/references.json, generated/db-schema.json
  • libraries/postgres/@types/fns.d.ts, db/fns.md, db/schema.md, db/versions/README.md
  • All client libraries updated with searchHooks method

Test Plan

  • cd db && yarn test — DB function tests pass
  • cd services/hooks && yarn test — API tests pass including new searchHooks suite
  • Navigate to /hooks — groups table loads normally (no regression)
  • Type a search term and submit — flat list of matching hooks appears
  • Search matches hook IDs inside groups whose name doesn't match
  • Search is case-insensitive (e.g. PROJECT matches project-releng)
  • Clear search — groups table reappears

@nitishagar nitishagar requested a review from a team as a code owner March 13, 2026 05:57
@nitishagar nitishagar requested review from lotas, matt-boris and petemoore and removed request for a team March 13, 2026 05:57
@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch from 52be92b to aef4cd2 Compare March 13, 2026 06:16

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.

This feels like the right way to fix the given search issue, but the reason why we have separate HookGroups query is because it is fast.
We had to create a new HookGroups view specifically to make first page load fast - original query takes <300ms to respond with all groups.

Now, when we include hooks inside that is suddenly becomes much slower - takes ~6s to load

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

We cannot accept current changes as it would be a regression.
Plus having client-side only search doesn't help here.

We should probably think about implementing a server-side filtering, or only include nested hooks when search is present.

Another idea on top of my head would be to use different view for the search purposes

@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch 5 times, most recently from 4191d78 to d6f5b5e Compare March 15, 2026 09:09
@nitishagar

Copy link
Copy Markdown
Contributor Author

Thanks for the review @lotas.

Reworked this with server-side filtering:

  • Added a new DB function get_hook_groups_2(search_term_in text) (db/versions/0123.yml) — uses a single DISTINCT ... WHERE hook_group_id ILIKE OR hook_id ILIKE query. The fast path is preserved when the search term is empty, so the initial page load remains unchanged.
  • Threaded the search query param from listHookGroups → DataLoader → GraphQL → UI. The UI now passes search as a variable, allowing Apollo to re-query on URL changes. This removes the need for client-side filtering and avoids including hooks { hookId } in the initial query.

@lotas

lotas commented Mar 16, 2026

Copy link
Copy Markdown
Contributor

hey @nitishagar thanks for the refactor, I think this a step in the right direction, since we remove the expensive nested hook loading.

That said, I think we are still overloading listHookGroups with search behavior that does not match its natural ocntract. We filter for both hook group and hook id, but return only hook group. That shape existed mostly because current UI was optimized that way.

I think a cleaner approach would be to introduce a dedicated hook search endpoint and add new UI element that would display filtered hooks.

searchHooks - GET /hooks/search?q=xx that will return same as listHooks is currently reporting but without single hook group constraint.

@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch 2 times, most recently from b0fb28a to c844c3f Compare March 17, 2026 12:54
@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch from c844c3f to e696ea6 Compare April 9, 2026 00:48
@nitishagar

Copy link
Copy Markdown
Contributor Author

Hi @lotas, thanks for the detailed review. I've reworked the approach based on your feedback.

What changed:

Instead of including hooks { hookId } in the hookGroups query (which caused the ~6s regression), the fix now uses a dedicated server-side search endpoint:

  • Added GET /hooks/search?q=<term> REST endpoint (new searchHooks API method) backed by a search_hooks(term) PostgreSQL function that does a case-insensitive substring match (ILIKE) across both hook_group_id and hook_id
  • Added searchHooks(query: String!): [Hook] to the GraphQL schema with resolver and DataLoader in web-server
  • The UI now uses two separate queries: the existing fast hookGroups query (returns only group IDs, ~300ms) for the initial page load, and the new searchHooks query (full hook data) only when the user has typed a search term
  • The switch is URL-driven: ?search=<term> in the URL activates the search query; no search param uses the fast groups query
  • Clearing the search now removes the search param from the URL entirely

Performance: Initial page load is unchanged (still uses the fast groups-only query). Search triggers one efficient DB query instead of N+1 requests.

Tests: Added DB function tests (db/test/fns/hooks_test.js) and API tests (services/hooks/test/api_test.js) covering scope enforcement, case-insensitive matching, cross-group results, and empty query handling.

@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch 2 times, most recently from 771054b to 34a8353 Compare April 9, 2026 07:13
@nitishagar

Copy link
Copy Markdown
Contributor Author

Thanks @lotas for the detailed feedback! The PR has been updated to address all your concerns:

Server-side search implemented: Instead of client-side filtering, the PR now adds a dedicated searchHooks GraphQL resolver backed by the existing hooks.searchHooks({ q: query }) REST API endpoint. This keeps two distinct code paths:

  • No search: Uses the fast hookGroups query (only fetches hookGroupId, <300ms as before) — no regression
  • Search active: Uses a new searchHooks(query: String!) GraphQL query that calls the hooks service server-side and returns matching hooks rendered in a flat list via HooksListTable

The previous client-side filtering approach has been removed entirely. The hookGroups.graphql still only queries hookGroupId, so first page load performance is unchanged.

The CI failure (ui-browser-test) is due to worker claim-expired (infra timeout on the CI runners), not a test code issue.

@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch from 34a8353 to f98535d Compare April 16, 2026 09:46
Comment thread services/hooks/src/api.js Outdated
].join('\n'),
}, async function(req, res) {
const { q } = req.query;
const hookRows = await this.db.fns.search_hooks(q || '', 1000, 0);

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.

the new endpoint seems to silently cap at 1000 results, which feels like a footgun for future problems, even if this is documented. can we not use pagination here? we do that in other APIs... thanks!

@nitishagar

nitishagar commented Apr 24, 2026

Copy link
Copy Markdown
Contributor Author

Hey @petemoore

Before I push the fix, wanted to sanity-check the shape with you, @lotas, and @matt-boris since there are a few design choices worth a sec of your time.

Approach: use the existing paginateResults helper in @taskcluster/lib-api to replace the hardcoded 1000, 0. Handful of calls to make:

  1. Scope — full stack vs. REST-only. The endpoint is only consumed by ui/src/views/Hooks/ListHookGroups, and the GraphQL layer currently returns a flat [Hook]. Leaning toward threading pagination all the way through (REST → web-server GraphQL + DataLoader → UI load-more) so the UI isn't silently capped either. If you'd rather see that as a follow-up PR, happy to split.

  2. Default page size = 100, maxLimit = 1000. Nudges callers toward pagination without breaking anyone relying on the existing behavior. Alternative would be keeping 1000 as the default and making pagination purely opt-in — open to either.

  3. No new DB version. search_hooks (db/versions/0123.yml) already takes page_size_in / page_offset_in; the handler was just passing literals. Fix lives in the API layer and above.

  4. Token format. Standard offset-mode paginateResults (Hashids-wrapped integer), matching listLastFires, listTaskGroup, listWorkerPools.

  5. Schema/description. search-hooks-response.yml gets optional continuationToken (following the list-lastFires-response.yml pattern); description updated; yarn generate output committed.

Also worth asking while we're here — should listHooks / listHookGroups get the same treatment, or is that out of scope? Appreciate any thoughts.

Refactor hooks search per review feedback:
- New `search_hooks` DB function performs a single ILIKE query across
  hook_group_id and hook_id, returning full hook rows
- New `GET /hooks/search?q=<term>` API endpoint (`searchHooks`) with
  pagination (default 100, max 1000, continuationToken)
- `listHookGroups` keeps its fast contract (no search param)
- GraphQL: new `searchHooks(query, continuationToken, limit)` returns
  `HookSearchResult`; `hookGroups` no longer takes `search`
- UI: renders flat HooksListTable when `?search=` param is present
  with a Load More button; otherwise renders the normal HookGroupsTable

Fixes taskcluster#7972
@nitishagar nitishagar force-pushed the fix/7972-hooks-search branch from a608674 to b602232 Compare April 30, 2026 04:55
Comment thread services/hooks/src/api.js
method: 'get',
route: '/hooks/search',
name: 'searchHooks',
scopes: 'hooks:list-hooks:',

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.

Existing listHooks() call expects hooks:list-hooks:<hookGroupId> scope, which means that we could allow users without necessary scope to see those in search output

Maybe having a hooks:search-hooks could be a right approach, or doing those checks in code to see if user role allows us accessing those hook groups

@lotas

lotas commented May 18, 2026

Copy link
Copy Markdown
Contributor

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hooks ui search only searches what is currently visible

3 participants