todo_service.rb 7.2 KB
Newer Older
1
# TodoService class
2
#
3
# Used for creating 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
  # When we reassign an issue we should:
  #
36
  #  * create a pending todo for new assignee if issue is assigned
37 38
  #
  def reassigned_issue(issue, current_user)
39
    create_assignment_todo(issue, current_user)
40 41 42 43
  end

  # When create a merge request we should:
  #
44 45
  #  * creates a pending todo for assignee if merge request is assigned
  #  * create a todo for each mentioned user on merge request
46 47 48 49 50
  #
  def new_merge_request(merge_request, current_user)
    new_issuable(merge_request, current_user)
  end

51 52
  # When update a merge request we should:
  #
53
  #  * create a todo for each mentioned user on merge request
54 55
  #
  def update_merge_request(merge_request, current_user)
56
    update_issuable(merge_request, current_user)
57 58
  end

59 60
  # When close a merge request we should:
  #
61
  #  * mark all pending todos related to the target for the current user as done
62 63
  #
  def close_merge_request(merge_request, current_user)
64
    mark_pending_todos_as_done(merge_request, current_user)
65 66 67
  end

  # When we reassign a merge request we should:
68
  #
69
  #  * creates a pending todo for new assignee if merge request is assigned
70 71
  #
  def reassigned_merge_request(merge_request, current_user)
72
    create_assignment_todo(merge_request, current_user)
73 74
  end

75 76
  # When merge a merge request we should:
  #
77
  #  * mark all pending todos related to the target for the current user as done
78 79
  #
  def merge_merge_request(merge_request, current_user)
80
    mark_pending_todos_as_done(merge_request, current_user)
81 82
  end

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  # When a build fails on the HEAD of a merge request we should:
  #
  #  * create a todo for that user to fix it
  #
  def merge_request_build_failed(merge_request)
    create_build_failed_todo(merge_request)
  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
  #
  def merge_request_build_retried(merge_request)
    mark_pending_todos_as_done(merge_request, merge_request.author)
  end

107 108
  # When create a note we should:
  #
109 110
  #  * mark all pending todos related to the noteable for the note author as done
  #  * create a todo for each mentioned user on note
111
  #
112 113
  def new_note(note, current_user)
    handle_note(note, current_user)
114 115
  end

116 117
  # When update a note we should:
  #
118 119
  #  * mark all pending todos related to the noteable for the current user as done
  #  * create a todo for each new user mentioned on note
120 121
  #
  def update_note(note, current_user)
122 123 124
    handle_note(note, current_user)
  end

125 126 127 128 129 130 131 132
  # 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

133
  # When marking pending todos as done we should:
134
  #
135
  #  * mark all pending todos related to the target for the current user as done
136
  #
137
  def mark_pending_todos_as_done(target, user)
138 139
    attributes = attributes_for_target(target)
    pending_todos(user, attributes).update_all(state: :done)
140 141
  end

P
Phil Hughes 已提交
142 143 144 145 146 147
  # 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

148 149
  private

150
  def create_todos(users, attributes)
151
    Array(users).map do |user|
152 153
      next if pending_todos(user, attributes).exists?
      Todo.create(attributes.merge(user_id: user.id))
154 155 156 157
    end
  end

  def new_issuable(issuable, author)
158 159
    create_assignment_todo(issuable, author)
    create_mention_todos(issuable.project, issuable, author)
160 161
  end

162 163 164 165 166 167 168
  def update_issuable(issuable, author)
    # Skip toggling a task list item in a description
    return if issuable.tasks? && issuable.updated_tasks.any?

    create_mention_todos(issuable.project, issuable, author)
  end

169
  def handle_note(note, author)
170
    # Skip system notes, and notes on project snippet
171
    return if note.system? || note.for_snippet?
172

173 174
    project = note.project
    target  = note.noteable
175

176 177
    mark_pending_todos_as_done(target, author)
    create_mention_todos(project, target, author, note)
178
  end
179

180
  def create_assignment_todo(issuable, author)
181
    if issuable.assignee && issuable.assignee != author
182 183
      attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
      create_todos(issuable.assignee, attributes)
184 185 186
    end
  end

187 188 189 190 191 192
  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

193 194 195 196 197 198
  def create_build_failed_todo(merge_request)
    author = merge_request.author
    attributes = attributes_for_todo(merge_request.project, merge_request, author, Todo::BUILD_FAILED)
    create_todos(author, attributes)
  end

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
  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
    )
221
  end
222

223
  def filter_mentioned_users(project, target, author)
224 225
    mentioned_users = target.mentioned_users
    mentioned_users = reject_users_without_access(mentioned_users, project, target)
226 227 228 229
    mentioned_users.delete(author)
    mentioned_users.uniq
  end

230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
  def reject_users_without_access(users, project, target)
    if target.is_a?(Note) && target.for_issue?
      target = target.noteable
    end

    if target.is_a?(Issue)
      select_users(users, :read_issue, target)
    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

248 249 250
  def pending_todos(user, criteria = {})
    valid_keys = [:project_id, :target_id, :target_type, :commit_id]
    user.todos.pending.where(criteria.slice(*valid_keys))
251
  end
252
end