todo_service.rb 9.4 KB
Newer Older
1
# TodoService class
2
#
P
Paco Guzman 已提交
3
# Used for creating/updating todos after certain user actions
4 5
#
# Ex.
6
#   TodoService.new.new_issue(issue, current_user)
7
#
8
class TodoService
9 10
  # When create an issue we should:
  #
11 12
  #  * create a todo for assignee if issue is assigned
  #  * create a todo for each mentioned user on issue
13 14
  #
  def new_issue(issue, current_user)
15
    new_issuable(issue, current_user)
16 17
  end

18 19
  # When update an issue we should:
  #
20
  #  * mark all pending todos related to the issue for the current user as done
21 22
  #
  def update_issue(issue, current_user)
23
    update_issuable(issue, current_user)
24 25
  end

26 27
  # When close an issue we should:
  #
28
  #  * mark all pending todos related to the target for the current user as done
29 30
  #
  def close_issue(issue, current_user)
31
    mark_pending_todos_as_done(issue, current_user)
32 33
  end

34 35 36 37 38 39 40 41
  # When we destroy an issue we should:
  #
  #  * refresh the todos count cache for the current user
  #
  def destroy_issue(issue, current_user)
    destroy_issuable(issue, current_user)
  end

42 43
  # When we reassign an issue we should:
  #
44
  #  * create a pending todo for new assignee if issue is assigned
45 46
  #
  def reassigned_issue(issue, current_user)
47
    create_assignment_todo(issue, current_user)
48 49 50 51
  end

  # When create a merge request we should:
  #
52 53
  #  * creates a pending todo for assignee if merge request is assigned
  #  * create a todo for each mentioned user on merge request
54 55 56 57 58
  #
  def new_merge_request(merge_request, current_user)
    new_issuable(merge_request, current_user)
  end

59 60
  # When update a merge request we should:
  #
61
  #  * create a todo for each mentioned user on merge request
62 63
  #
  def update_merge_request(merge_request, current_user)
64
    update_issuable(merge_request, current_user)
65 66
  end

67 68
  # When close a merge request we should:
  #
69
  #  * mark all pending todos related to the target for the current user as done
70 71
  #
  def close_merge_request(merge_request, current_user)
72
    mark_pending_todos_as_done(merge_request, current_user)
73 74
  end

75 76 77 78 79 80 81 82
  # When we destroy a merge request we should:
  #
  #  * refresh the todos count cache for the current user
  #
  def destroy_merge_request(merge_request, current_user)
    destroy_issuable(merge_request, current_user)
  end

83
  # When we reassign a merge request we should:
84
  #
85
  #  * creates a pending todo for new assignee if merge request is assigned
86 87
  #
  def reassigned_merge_request(merge_request, current_user)
88
    create_assignment_todo(merge_request, current_user)
89 90
  end

91 92
  # When merge a merge request we should:
  #
93
  #  * mark all pending todos related to the target for the current user as done
94 95
  #
  def merge_merge_request(merge_request, current_user)
96
    mark_pending_todos_as_done(merge_request, current_user)
97 98
  end

99 100
  # When a build fails on the HEAD of a merge request we should:
  #
101 102
  #  * create a todo for author of MR to fix it
  #  * create a todo for merge_user to keep an eye on it
103 104
  #
  def merge_request_build_failed(merge_request)
105 106
    create_build_failed_todo(merge_request, merge_request.author)
    create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds?
107 108 109 110 111 112 113 114 115 116 117 118 119
  end

  # When a new commit is pushed to a merge request we should:
  #
  #  * mark all pending todos related to the merge request for that user as done
  #
  def merge_request_push(merge_request, current_user)
    mark_pending_todos_as_done(merge_request, current_user)
  end

  # When a build is retried to a merge request we should:
  #
  #  * mark all pending todos related to the merge request for the author as done
120
  #  * mark all pending todos related to the merge request for the merge_user as done
121 122 123
  #
  def merge_request_build_retried(merge_request)
    mark_pending_todos_as_done(merge_request, merge_request.author)
124
    mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds?
125
  end
126 127 128 129 130 131 132 133 134
  
  # When a merge request could not be automatically merged due to its unmergeable state we should:
  #
  #  * create a todo for a merge_user
  #
  def merge_request_became_unmergeable(merge_request)
    create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_build_succeeds?
  end
  
135 136
  # When create a note we should:
  #
137 138
  #  * mark all pending todos related to the noteable for the note author as done
  #  * create a todo for each mentioned user on note
139
  #
140 141
  def new_note(note, current_user)
    handle_note(note, current_user)
142 143
  end

144 145
  # When update a note we should:
  #
146 147
  #  * mark all pending todos related to the noteable for the current user as done
  #  * create a todo for each new user mentioned on note
148 149
  #
  def update_note(note, current_user)
150 151 152
    handle_note(note, current_user)
  end

153 154 155 156 157 158 159 160
  # When an emoji is awarded we should:
  #
  #  * mark all pending todos related to the awardable for the current user as done
  #
  def new_award_emoji(awardable, current_user)
    mark_pending_todos_as_done(awardable, current_user)
  end

161
  # When marking pending todos as done we should:
162
  #
163
  #  * mark all pending todos related to the target for the current user as done
164
  #
165
  def mark_pending_todos_as_done(target, user)
166 167
    attributes = attributes_for_target(target)
    pending_todos(user, attributes).update_all(state: :done)
P
Paco Guzman 已提交
168 169 170 171 172
    user.update_todos_count_cache
  end

  # When user marks some todos as done
  def mark_todos_as_done(todos, current_user)
173
    mark_todos_as_done_by_ids(todos.select(&:id), current_user)
174 175
  end

176
  def mark_todos_as_done_by_ids(ids, current_user)
177
    todos = current_user.todos.where(id: ids)
P
Paco Guzman 已提交
178

179 180
    # Only return those that are not really on that state
    marked_todos = todos.where.not(state: :done).update_all(state: :done)
P
Paco Guzman 已提交
181
    current_user.update_todos_count_cache
182
    marked_todos
183 184
  end

P
Phil Hughes 已提交
185 186 187 188 189 190
  # When user marks an issue as todo
  def mark_todo(issuable, current_user)
    attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
    create_todos(current_user, attributes)
  end

191 192 193 194
  def todo_exist?(issuable, current_user)
    TodosFinder.new(current_user).execute.exists?(target: issuable)
  end

195 196
  private

197
  def create_todos(users, attributes)
198
    Array(users).map do |user|
199
      next if pending_todos(user, attributes).exists?
200
      todo = Todo.create(attributes.merge(user_id: user.id))
P
Paco Guzman 已提交
201
      user.update_todos_count_cache
202
      todo
203 204 205 206
    end
  end

  def new_issuable(issuable, author)
207 208
    create_assignment_todo(issuable, author)
    create_mention_todos(issuable.project, issuable, author)
209 210
  end

211 212
  def update_issuable(issuable, author)
    # Skip toggling a task list item in a description
213
    return if toggling_tasks?(issuable)
214 215 216 217

    create_mention_todos(issuable.project, issuable, author)
  end

218 219 220 221
  def destroy_issuable(issuable, user)
    user.update_todos_count_cache
  end

222 223 224 225 226
  def toggling_tasks?(issuable)
    issuable.previous_changes.include?('description') &&
      issuable.tasks? && issuable.updated_tasks.any?
  end

227
  def handle_note(note, author)
228
    # Skip system notes, and notes on project snippet
229
    return if note.system? || note.for_snippet?
230

231 232
    project = note.project
    target  = note.noteable
233

234 235
    mark_pending_todos_as_done(target, author)
    create_mention_todos(project, target, author, note)
236
  end
237

238
  def create_assignment_todo(issuable, author)
239
    if issuable.assignee
240 241
      attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
      create_todos(issuable.assignee, attributes)
242 243 244
    end
  end

245 246 247 248 249 250
  def create_mention_todos(project, target, author, note = nil)
    mentioned_users = filter_mentioned_users(project, note || target, author)
    attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
    create_todos(mentioned_users, attributes)
  end

251 252 253
  def create_build_failed_todo(merge_request, todo_author)
    attributes = attributes_for_todo(merge_request.project, merge_request, todo_author, Todo::BUILD_FAILED)
    create_todos(todo_author, attributes)
254 255
  end

256 257 258 259 260
  def create_unmergeable_todo(merge_request, merge_user)
    attributes = attributes_for_todo(merge_request.project, merge_request, merge_user, Todo::UNMERGEABLE)
    create_todos(merge_user, attributes)
  end

261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
  def attributes_for_target(target)
    attributes = {
      project_id: target.project.id,
      target_id: target.id,
      target_type: target.class.name,
      commit_id: nil
    }

    if target.is_a?(Commit)
      attributes.merge!(target_id: nil, commit_id: target.id)
    end

    attributes
  end

  def attributes_for_todo(project, target, author, action, note = nil)
    attributes_for_target(target).merge!(
      project_id: project.id,
      author_id: author.id,
      action: action,
      note: note
    )
283
  end
284

285
  def filter_mentioned_users(project, target, author)
286
    mentioned_users = target.mentioned_users(author)
287
    mentioned_users = reject_users_without_access(mentioned_users, project, target)
288 289 290
    mentioned_users.uniq
  end

291
  def reject_users_without_access(users, project, target)
V
Valery Sizov 已提交
292
    if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?)
293 294 295
      target = target.noteable
    end

V
Valery Sizov 已提交
296 297
    if target.is_a?(Issuable)
      select_users(users, :"read_#{target.to_ability_name}", target)
298 299 300 301 302 303 304 305 306 307 308
    else
      select_users(users, :read_project, project)
    end
  end

  def select_users(users, ability, subject)
    users.select do |user|
      user.can?(ability.to_sym, subject)
    end
  end

309 310 311
  def pending_todos(user, criteria = {})
    valid_keys = [:project_id, :target_id, :target_type, :commit_id]
    user.todos.pending.where(criteria.slice(*valid_keys))
312
  end
313
end