Add related_merge_requests endpoint to WorkItem REST API

Adds the related_merge_requests paginated sub-endpoint to the work item REST API (namespaces / projects / groups), listing the merge requests that mention a work item. This backs the development widget's related_merge_requests field for the REST API.

Part of #601071 (closed).

Implementation

  • New API::WorkItems::RelatedMergeRequests class, structured after the existing award_emoji work item sub-endpoint.
  • Reuses the existing API::Entities::MergeRequestBasic entity.
  • Sources its data the same way as the GraphQL development widget's related_merge_requests field: it returns none for group-level work items and otherwise delegates to Issues::ReferencedMergeRequestsService (the same source Resolvers::MergeRequests::WorkItemRelatedResolver uses). It does not reimplement the derivation, so REST tracks GraphQL if that source migrates behind explicit_mr_work_item_relations.
  • Guarded by the work_item_rest_api feature flag and read_work_item authorization, consistent with the other work item sub-endpoints.

Query plan

The endpoint re-loads the merge requests returned by Issues::ReferencedMergeRequestsService as a relation so with_api_entity_associations can preload what MergeRequestBasic renders. Querying by id is safe because the service has already filtered them through Ability.merge_requests_readable_by_user.

Results are ordered by iid (via the new MergeRequest.order_iid_asc scope) to match GET /issues/:iid/related_merge_requests and to keep LIMIT/OFFSET pagination deterministic.

SELECT "merge_requests".* FROM "merge_requests"
WHERE "merge_requests"."id" IN (39, 40, 41)
ORDER BY "merge_requests"."iid" ASC
LIMIT 20 OFFSET 0
 Limit  (cost=3.46..3.47 rows=3 width=838)
   ->  Sort  (cost=3.46..3.47 rows=3 width=838)
         Sort Key: iid
         ->  Index Scan using merge_requests_pkey on merge_requests  (cost=0.14..3.43 rows=3 width=838)
               Index Cond: (id = ANY ('{39,40,41}'::bigint[]))

Primary-key index scan bounded by the id set (the merge requests referenced from a single work item's description and notes). The sort is an in-memory ordering of that small result set, not a table sort.

New scope

This MR adds scope :order_iid_asc, -> { reorder(iid: :asc) } to MergeRequest. It adds no joins or conditions — it only replaces the ORDER BY shown above.


Set up and validate locally

Prerequisites: a running GDK with the work_item_rest_api feature flag enabled:

# rails console
Feature.enable(:work_item_rest_api)

Seed data — reference a few merge requests from a work item. Reference them in descending iid order so the response demonstrates the ORDER BY iid this MR adds:

user      = User.find_by_username('root')
project   = Project.find_by_full_path('your-group/your-project')
work_item = project.issues.find_by(iid: <WORK_ITEM_IID>)

project.merge_requests.order(:iid).limit(3).to_a.reverse.each do |mr|
  Note.create!(project: project, noteable: work_item, author: user, system: true,
               note: mr.to_reference(full: true))
end

Create a personal access token with the api scope and export TOKEN=<token>.

Call the endpoint (project, namespace, and group scopes):

# project scope
curl --header "PRIVATE-TOKEN: $TOKEN" \
  "http://gdk.test:3000/api/v4/projects/<PROJECT_ID>/-/work_items/<IID>/related_merge_requests"

# namespace scope (URL-encode the full path)
curl --header "PRIVATE-TOKEN: $TOKEN" \
  "http://gdk.test:3000/api/v4/namespaces/<group%2Fproject>/-/work_items/<IID>/related_merge_requests"

# group scope — a group-level work item has no project to search, so returns []
curl --header "PRIVATE-TOKEN: $TOKEN" \
  "http://gdk.test:3000/api/v4/groups/<GROUP_ID>/-/work_items/<IID>/related_merge_requests"

Each row is a MergeRequestBasic payload. Results are ordered by iid so LIMIT/OFFSET pagination stays stable across pages, matching GET /issues/:iid/related_merge_requests.

Edge cases: unknown work item → 404; no token → 401; work_item_rest_api disabled → 403.

Screenshots

Project-scope response — the work item references the merge requests as !3, !2, !1, but they come back ordered by iid:

wi-rest-related-mrs-response

Pagination across two pages plus edge cases — pages neither overlap nor skip rows:

wi-rest-related-mrs-pagination

Edited by Daniyal Arshad

Merge request reports

Loading
Loading