Skip to content

User preferences to dismiss todos upon merge or closure of associated Merge Request or Issue

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

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:

# 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:

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
Edited by Kai Armstrong