Refresh milestone counts when an issue or merge request is deleted
What does this MR do and why?
A milestone's issue and merge request counts come from Milestones::IssuesCountService, ClosedIssuesCountService and MergeRequestsCountService, which keep them in Rails.cache for the cache's default TTL, 8 hours on Redis (cache.rb#L23-29). Those caches are deleted when an issuable changes milestone, the closed count when an issue is closed or reopened, and all three when a milestone is transferred. Deleting an issue or merge request deletes none of them: Issuable::DestroyService#after_destroy clears only the project and assignee counters (destroy_service.rb#L38-42).
So for up to 8 hours after a delete, the milestone reports the old counts: totalIssuesCount and closedIssuesCount in GraphQL, the issue_stats of a release's milestones in the Releases API, the counts and progress on the milestone pages, and the merge request count in the milestone header.
This MR deletes the milestone's count caches after the destroy, as the milestone callback does when an issuable leaves a milestone: both issue counts for an issue or work item, the merge request count for a merge request. Epics go through the same service and have no milestone, so nothing changes for them. Like the project counters next to them, the keys are deleted right after the destroy, not after commit; I can move them to run_after_commit_or_now if you prefer.
No database changes: a delete adds at most one milestone lookup by primary key.
The new examples fail on master and pass with this change.
Spec runs
Locally on Ruby 3.3.11 and PostgreSQL 17, on master at b617afc2:
- With master's
app/services/issuable/destroy_service.rb, the two destroy service specs and the request spec below: 21 examples, 2 failures, the two new examples. After deleting a closed issue the milestone still counts 2 issues and 1 closed instead of 1 and 0, and after deleting a merge request 1 merge request instead of 0. - With the change, the same plus
ee/spec/services/ee/issuable/destroy_service_spec.rb,spec/services/work_items/delete_service_spec.rband the three milestone count service specs: 89 examples, 0 failures. WithFOSS_ONLY=1, the two service specs and the work items one: 48 examples, 0 failures. - A throwaway request spec with caching on (
:use_clean_rails_memory_store_caching): three issues in a milestone, the third closed, thenDELETE /projects/:id/issues/:issue_iidfor the first and the third, then the GraphQL stats. On master:
== before (3 issues, 1 closed)
{"project":{"milestones":{"nodes":[{"title":"gql2-ms","stats":{"totalIssuesCount":3,"closedIssuesCount":1}}]},"issues":{"count":3}}}
DELETE opened issue #1 -> 204
DELETE closed issue #3 -> 204
== after deleting 2 of 3 (expect total 1, closed 0)
{"project":{"milestones":{"nodes":[{"title":"gql2-ms","stats":{"totalIssuesCount":3,"closedIssuesCount":1}}]},"issues":{"count":1}}}With this change the last line reads:
{"project":{"milestones":{"nodes":[{"title":"gql2-ms","stats":{"totalIssuesCount":1,"closedIssuesCount":0}}]},"issues":{"count":1}}}- RuboCop on the changed files and the commit linter: no offenses.
Left to the pipeline: Ruby 3.4, the API and GraphQL delete specs, and the milestone feature and view specs.
References
Closes #350852 (closed)
The issue was closed in August 2025 by the inactive bugs policy, without a fix. The assignees' counts had the same gap and were fixed in !236965 (merged).
I found this while working on https://github.com/jmrplens/gitlab-mcp-server.
Screenshots or screen recordings
None: no UI code changes. After a delete, the milestone pages now show the remaining counts.
How to set up and validate locally
- In a project, create a milestone with three issues in it, and close the third.
- Open the project's milestones list: the milestone reads
1/3 complete. - Delete the first and the third issue from their issue pages.
- Open the milestones list again. On master it still reads
1/3 complete; with this MR it reads0/1 complete.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.