GraphQL blockingCount keeps counting a blocked issue after it is deleted
Summary
Deleting an open issue that another issue blocks leaves the blocker's cached issues.blocking_issues_count one too high. GraphQL blockingCount (the issue field and the linked items widget), the work items REST API's blocking_count and the BLOCKING_ISSUES_ASC and BLOCKING_ISSUES_DESC sorts read that column, so they keep counting the deleted issue. The issues REST API counts live and answers the right number.
The link models refresh the column in their after_create and after_destroy callbacks (linkable_item.rb#L11-12), but deleting the blocked issue removes its issue_links rows through the ON DELETE CASCADE foreign key on target_id (fk_e71bb44f1f), which runs no callback. When the blocker blocks nothing else, as A in the steps below, the count stays wrong until it gains a blocking link; closing or reopening it does not correct it.
Where the column is read
Issue.blockingCount: issue_type.rb#L33-35- the linked items widget: linked_items_type.rb#L29-31
- the work items REST API: linked_items.rb#L11-15
- the sorts: issue.rb#L27-28
- the issues REST API, which counts live instead: issuable_metadata.rb#L54-57
Steps to reproduce
- On Premium or Ultimate, create issues A and B in a project, and mark A as blocking B.
- Query
{ project(fullPath: "<project_path>") { issue(iid: "<A>") { blockingCount } } }. It answers1. - Delete B, for example with
DELETE /api/v4/projects/<project_id>/issues/<B>. - Query again, and compare with
blocking_issues_countinGET /api/v4/projects/<project_id>/issues/<A>.
Example Project
None on GitLab.com. I reproduced it on a self-managed GitLab EE 17.6.1 instance and with a request spec on master.
What is the current bug behavior?
After B is deleted, GraphQL still answers blockingCount: 1 for A, and BLOCKING_ISSUES_DESC still lists A first. The issues REST API answers blocking_issues_count: 0.
What is the expected correct behavior?
GraphQL answers blockingCount: 0 for A, as the issues REST API does, and the sorts order A by that count.
Relevant logs and/or screenshots
Output on GitLab EE 17.6.1 and in a request spec on master
On GitLab Enterprise Edition 17.6.1, following the steps above:
== before delete (GraphQL)
{"data":{"project":{"issue":{"iid":"1","blockingCount":1}}}}
DELETE victim issue -> HTTP 204
== after delete (GraphQL)
{"data":{"project":{"issue":{"iid":"1","blockingCount":1}}}}
== after delete (REST /issues/:iid)
{"iid":1,"blocking_issues_count":0}
== remaining links on blocker (REST)
0The same steps in a request spec on master (the blocker is iid 1, the deleted issue iid 3, and iid 2 an unrelated issue):
before delete: {"issue":{"iid":"1","blockingCount":1},"workItems":{"nodes":[{"iid":"1","features":{"linkedItems":{"blockingCount":1}}}]},"issues":{"nodes":[{"iid":"1","blockingCount":1},{"iid":"3","blockingCount":0},{"iid":"2","blockingCount":0}]}} REST: {"iid":1,"blocking_issues_count":1}
DELETE /projects/:id/issues/3 -> HTTP 204
after delete: {"issue":{"iid":"1","blockingCount":1},"workItems":{"nodes":[{"iid":"1","features":{"linkedItems":{"blockingCount":1}}}]},"issues":{"nodes":[{"iid":"1","blockingCount":1},{"iid":"2","blockingCount":0}]}} REST: {"iid":1,"blocking_issues_count":0} links left on the blocker: 0Output of checks
Not applicable: not reproduced on GitLab.com.
Possible fixes
Read the blockers before the delete and recount them after the commit with the existing update_blocking_issues_count!, in a callback on the issue model, which covers the REST API, workItemDelete, the issue page and project deletion. Group deletion (another cascade, on issues.namespace_id) and the counts that are already wrong could follow separately. Computing the fields live, as EpicType#blocking_count does, would not fix the sorts.
I am opening a merge request that does this in EE::Issue. I found the bug while working on gitlab-mcp-server, which reads blocking_issues_count through the REST API.