User preferences to dismiss todos upon merge or closure of associated Merge Request or Issue
## Problem to solve
A **To-Do** raised automatically by a Merge Request being assigned, should be automatically marked 'done' by MR being merged/closed. This would avoid them accumulating.
Improving this is desirable for people using GitLab primarily as a repo & for merge requests (not as a task management system), to not find the Todo feature filled with outdated litter. If they ever do look at the Todos, finding it full of outdated items raised automatically but no longer relevant will be very effective in putting people off using this feature.
## Proposed Solution: Two-Phase Approach
### Phase 1: Prospective Auto-Resolution 🚀 **HIGH IMPACT**
**Objective**: Prevent new stale todos by auto-resolving them when MRs close/merge
**Implementation**:
1. **Hook into MR state change events** (close/merge)
2. **Query for pending todos** on that specific MR with stale-prone action types
3. **Bulk update todos to 'done'** with `resolved_by_action: :system_done`
**Code Changes**:
```ruby
# In MergeRequest state change callback or service
def auto_resolve_stale_todos_on_mr_completion
return unless merged? || closed?
stale_action_types = [
Todo::ASSIGNED, # 1
Todo::APPROVAL_REQUIRED, # 5
Todo::REVIEW_REQUESTED, # 9
Todo::ADDED_APPROVER # 13
]
Todo.where(
target: self,
state: :pending,
action: stale_action_types
).update_all(
state: :done,
resolved_by_action: Todo.resolved_by_actions[:system_done],
updated_at: Time.current
)
end
```
**Impact**: Eliminates 100% of future stale todos for these action types
**Risk**: Low - only affects todos that are definitively useless
---
### Phase 2: Historical Data Cleanup 🧹 **DEBT REDUCTION**
**Objective**: Clean up the existing 6.3M stale todos to improve user experience immediately
**Implementation**:
```ruby
class CleanupStaleTodosBackgroundMigration < Gitlab::BackgroundMigration::BatchedMigrationJob
STALE_ACTION_TYPES = [1, 5, 9, 13].freeze # assigned, approval_required, review_requested, added_approver
BATCH_SIZE = 1000
def perform
stale_todos = Todo.joins(
"JOIN merge_requests ON todos.target_id = merge_requests.id"
).where(
target_type: 'MergeRequest',
state: :pending,
action: STALE_ACTION_TYPES
).where(
"merge_requests.state IN ('merged', 'closed')"
).limit(BATCH_SIZE)
stale_todos.update_all(
state: :done,
resolved_by_action: Todo.resolved_by_actions[:system_done],
updated_at: Time.current
)
# Update user todo count caches
user_ids = stale_todos.distinct.pluck(:user_id)
Users::UpdateTodoCountCacheService.new(user_ids).execute
end
end
```
**Rollout Strategy**:
1. **Test on staging** with a subset of data
2. **Gradual rollout** - start with older stale todos (6+ months old)
3. **Monitor performance** impact on database
4. **Measure user satisfaction** - track todo dashboard engagement
**Impact**:
- Immediate UX improvement for 279,000+ affected users
- Reduces todo table size and improves query performance
- Clean slate for todo dashboard relevancy
---
## Success Metrics
### Phase 1 (Prospective):
- **New stale todo creation**: Should drop to 0 for target action types
- **User todo completion rates**: Should increase as todos become more actionable
### Phase 2 (Historical):
- **Stale todo reduction**: 6.3M → 0 for historical data
- **Todo dashboard engagement**: Increased time spent on actual actionable todos
- **User satisfaction scores**: Improved ratings for todo functionality
## Risk Mitigation
1. **Feature Flag**: Phase 1 behind feature flag for easy rollback
2. **Monitoring**: Track todo resolution rates and user complaints
3. **Granular Rollout**: Start Phase 2 with oldest/safest data first
4. **Performance Testing**: Ensure migration doesn't impact production queries
5. **User Communication**: Changelog entry explaining todo cleanup
issue