Add work_item_type_names filter to the Work Items REST API
Summary
Add a work_item_type_names filter to the Work Items REST API list endpoint, so clients can filter by work item type NAME (including custom types) rather than only by integer type id.
This is a sibling to #605888 (closed) (formalizing work_item_type_ids). That one gives clients an id-based filter; this one adds the convenient name-based filter on top.
Why this is useful
Today the only way to filter by a specific (custom) work item type is work_item_type_ids, which takes integer ids. That forces every client to first resolve a human-readable type name (e.g. "Activated by default") to its numeric id before it can filter, which means an extra lookup round-trip and client-side plumbing.
Type names are what humans and automation configs actually speak. A concrete driver: the gitlab-triage gem is adding a work_items policy type on this REST API (epic gitlab-org#22402). Its triage rules are written in YAML by humans, e.g. filter for work items of type "Bug". Without a name filter, the gem has to build and maintain a name-to-id resolver and do a preliminary lookup on every run. A name filter removes that entirely, and the same convenience applies to any external integration or script.
Names are safe to filter on here: work item types are managed at the root namespace level (organization / top-level group), so type names cannot collide within a namespace tree. There is no ambiguity to resolve.
Scope
- REST API only for now. GraphQL can wire this up later cheaply (see below); it is intentionally out of scope for this issue.
- Additive. Does not change or replace
work_item_type_ids(which the frontend depends on and #605888 (closed) is formalizing).
Background: how type filtering works today (important, read before implementing)
work_item_type_idsis a pure passthrough: the finder doeswhere(work_item_type_id: ids). Nothing resolves specific type identity. GraphQL even pre-resolves its GID input to integers before the finder (prepare:inshared_filter_arguments.rb).- The base-type
types/issue_typesfilter resolves names, but only for the six system base types, not specific/custom types. - So resolving a specific type NAME to its id does not exist anywhere yet. This issue adds it, in the shared finder, so all current and future consumers can reuse it.
Implementation checklist
1. Resolution in the shared finder
- In
app/finders/issues/issue_types_filter.rb, add aby_work_item_type_namesfilter method and call it from#filter(the method that currently chainsby_work_item_type_idsthenby_issue_types). Adding the method without adding it to thefilterchain is a no-op, so wire it in. - Resolve names to ids using the type Provider, scoped to the finder's parent:
WorkItems::TypesFramework::Provider.new(parent).parentis available on the filter viaIssuables::BaseFilter(it isparams.parent, i.e.project || group). The Provider walks to the root ancestor, so a Project or Group parent both resolve custom types correctly. - Resolve all requested names in a single pass (not one
findper name).Provider#find_by_nameexists but is exact-match and O(n) per call; prefer resolving against the Provider's full type list once, matching case-insensitively (the intended vocabulary is case-insensitive, e.g. "bug" matches "Bug"). Map matched names to their ids, then feed the existingwith_work_item_type_idsscope (app/models/issue.rb). - Unknown name behavior: an unknown name matches nothing (consistent with how an unknown id behaves today). Do NOT raise. If every requested name is unknown, the result set is empty.
- Negated path: add
by_negated_work_item_type_namesinapp/finders/issues_finder.rbalongside the existingby_negated_work_item_type_ids(around lines 164, 255-260), resolving names the same way and feedingwithout_work_item_type_ids.
2. REST exposure (the only API surface in this issue)
- Declare
work_item_type_names,type: Array[String], inlib/api/helpers/work_items/list_params.rbin the top-level filter block AND thenotblock (mirror wherelabel_name/ the base-typetypesare declared). - Confirm the param reaches the finder.
API::Helpers::WorkItemsFilterParams#transformbuilds finder params from the raw params hash and carriesresource_parent, so a declaredwork_item_type_nameskey flows through. Verify the finder readsparams[:work_item_type_names](andnot_params[:work_item_type_names]for negation) - add the reader if needed. Do not assume it auto-wires; trace it end to end. - Combining
work_item_type_nameswithwork_item_type_idsor base-typetypesis an AND. This is acceptable; no mutual-exclusivity needed. (Contrast:types+work_item_type_idsis a known silent-AND footgun tracked in #605888 (closed), but that is out of scope here.)
3. Explicitly OUT of scope (follow-up)
- The GraphQL
argument :work_item_type_namesinapp/graphql/resolvers/concerns/work_items/shared_filter_arguments.rb. The resolution branch will already exist in the finder, so exposing it on GraphQL later is roughly an argument declaration plus a spec. Do not add it in this issue. - No changes to
work_item_type_ids(frontend depends on it; formalization is #605888 (closed)).
Acceptance criteria
-
GET /projects/:id/-/work_items?work_item_type_names[]=Bugreturns only work items of type "Bug", including when "Bug" is a namespace-defined custom type. - Case-insensitive:
work_item_type_names[]=bugmatches type "Bug". - Multiple names OR together:
work_item_type_names[]=Bug&work_item_type_names[]=Incidentreturns both. -
not[work_item_type_names][]=Bugexcludes work items of type "Bug". - An unknown type name matches nothing (no error); all-unknown yields an empty result set.
- Works on both project and group list endpoints.
Testing
- Finder specs (
ee/spec/finders/issues_finder_spec.rb, since custom types are EE): coverwork_item_type_namesand the negated variant, using a custom type created with the:work_item_custom_typefactory (create(:work_item_custom_type, namespace: root_namespace)), asserting a custom-type name resolves and filters correctly. Test through the finder's public interface, not private methods. - REST request specs: add a
work_item_type_namescontext to the shared examplespec/support/shared_examples/api/work_items_filter_shared_examples.rb(where thetypescontext lives, around lines 58-65). It is already exercised by both the project and group list specs inspec/requests/api/work_items_spec.rb(viait_behaves_like 'work item listing filters'), so covering it there gives both surfaces for free. Include thenotblock and a custom type.
Size
Small. Roughly 40-70 lines of production code (finder branches plus REST param declarations) and 60-80 lines of tests. No database migration, no model change. Add a changelog trailer.