GraphQL blockingCount keeps counting work items of a deleted group

Summary

Deleting a group leaves issues.blocking_issues_count too high on every issue outside the group that blocked one of the group's own open work items (its epics and other group-level work items). 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 work items.

!265408 (merged) fixed this for issue deletion (#636333 (closed)) with a before_destroy callback on the issue model, which also covers the issues of the group's projects, since project deletion destroys them one by one. The group's own work items are removed by group.destroy (destroy_service.rb#L61) through the ON DELETE CASCADE foreign key on issues.namespace_id (fk_c78fbacd64), and their issue_links rows through the cascades on the link columns, so no callback runs. !265408 (merged) left this case for a follow-up, and #636694 (closed) is about repairing the counts that are already wrong.

Steps to reproduce

  1. On Ultimate, create groups G and H, with an epic in each.
  2. On H's epic, add G's epic to its linked items as blocks.
  3. Query { namespace(fullPath: "<H>") { workItem(iid: "<iid>") { features { linkedItems { blockingCount } } } } }. It answers 1.
  4. Delete G permanently.
  5. Query again.

Example Project

None on GitLab.com. I reproduced it with a service spec on master.

What is the current bug behavior?

After G is deleted, blockingCount still answers 1 for H's epic, which no longer blocks anything.

What is the expected correct behavior?

blockingCount answers 0.

Relevant logs and/or screenshots

The spec on master

A spec of the group deletion service, run with the application code of master at 9fc1d8a7: the group's work items are blocked by an open issue, a closed issue and an epic outside the group, and a subgroup's work item by another issue outside it. None of the four blockers is recounted.

1) Groups::DestroyService when work items of the group are blocked by issues outside it refreshes the blocking issues count of the issues outside the group that blocked its work items
     expected `blocking_issue.reload.blocking_issues_count` to have changed from 2 to 1, but did not change
     expected `closed_blocking_issue.reload.blocking_issues_count` to have changed from 1 to 0, but did not change
     expected `blocking_epic.reload.blocking_issues_count` to have changed from 1 to 0, but did not change
     expected `subgroup_blocker.reload.blocking_issues_count` to have changed from 1 to 0, but did not change

Output of checks

Not applicable: not reproduced on GitLab.com.

Possible fixes

Before the group is deleted, read in batches the work items outside it that block one of its open work items, and recount them after the commit with the recount !265408 (merged) runs. EE::Groups::DestroyService#before_group_destroy (destroy_service.rb#L53) runs for the group and for each subgroup it deletes, right before group.destroy. I am opening a merge request that does this.

Edited by 🤖 GitLab Bot 🤖