PageRenderTime 33ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/app/services/todo_service.rb

https://gitlab.com/alexsanford/gitlab-ce
Ruby | 342 lines | 193 code | 52 blank | 97 comment | 16 complexity | fe13fd7bb2d188bd772f163fbc8f8409 MD5 | raw file
  1. # TodoService class
  2. #
  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. # When create an issue we should:
  10. #
  11. # * create a todo for assignee if issue is assigned
  12. # * create a todo for each mentioned user on issue
  13. #
  14. def new_issue(issue, current_user)
  15. new_issuable(issue, current_user)
  16. end
  17. # When update an issue we should:
  18. #
  19. # * mark all pending todos related to the issue for the current user as done
  20. #
  21. def update_issue(issue, current_user, skip_users = [])
  22. update_issuable(issue, current_user, skip_users)
  23. end
  24. # When close an issue we should:
  25. #
  26. # * mark all pending todos related to the target for the current user as done
  27. #
  28. def close_issue(issue, current_user)
  29. mark_pending_todos_as_done(issue, current_user)
  30. end
  31. # When we destroy an issue we should:
  32. #
  33. # * refresh the todos count cache for the current user
  34. #
  35. def destroy_issue(issue, current_user)
  36. destroy_issuable(issue, current_user)
  37. end
  38. # When we reassign an issue we should:
  39. #
  40. # * create a pending todo for new assignee if issue is assigned
  41. #
  42. def reassigned_issue(issue, current_user)
  43. create_assignment_todo(issue, current_user)
  44. end
  45. # When create a merge request we should:
  46. #
  47. # * creates a pending todo for assignee if merge request is assigned
  48. # * create a todo for each mentioned user on merge request
  49. #
  50. def new_merge_request(merge_request, current_user)
  51. new_issuable(merge_request, current_user)
  52. end
  53. # When update a merge request we should:
  54. #
  55. # * create a todo for each mentioned user on merge request
  56. #
  57. def update_merge_request(merge_request, current_user, skip_users = [])
  58. update_issuable(merge_request, current_user, skip_users)
  59. end
  60. # When close a merge request we should:
  61. #
  62. # * mark all pending todos related to the target for the current user as done
  63. #
  64. def close_merge_request(merge_request, current_user)
  65. mark_pending_todos_as_done(merge_request, current_user)
  66. end
  67. # When we destroy a merge request we should:
  68. #
  69. # * refresh the todos count cache for the current user
  70. #
  71. def destroy_merge_request(merge_request, current_user)
  72. destroy_issuable(merge_request, current_user)
  73. end
  74. # When we reassign a merge request we should:
  75. #
  76. # * creates a pending todo for new assignee if merge request is assigned
  77. #
  78. def reassigned_merge_request(merge_request, current_user)
  79. create_assignment_todo(merge_request, current_user)
  80. end
  81. # When merge a merge request we should:
  82. #
  83. # * mark all pending todos related to the target for the current user as done
  84. #
  85. def merge_merge_request(merge_request, current_user)
  86. mark_pending_todos_as_done(merge_request, current_user)
  87. end
  88. # When a build fails on the HEAD of a merge request we should:
  89. #
  90. # * create a todo for author of MR to fix it
  91. # * create a todo for merge_user to keep an eye on it
  92. #
  93. def merge_request_build_failed(merge_request)
  94. create_build_failed_todo(merge_request, merge_request.author)
  95. create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds?
  96. end
  97. # When a new commit is pushed to a merge request we should:
  98. #
  99. # * mark all pending todos related to the merge request for that user as done
  100. #
  101. def merge_request_push(merge_request, current_user)
  102. mark_pending_todos_as_done(merge_request, current_user)
  103. end
  104. # When a build is retried to a merge request we should:
  105. #
  106. # * mark all pending todos related to the merge request for the author as done
  107. # * mark all pending todos related to the merge request for the merge_user as done
  108. #
  109. def merge_request_build_retried(merge_request)
  110. mark_pending_todos_as_done(merge_request, merge_request.author)
  111. mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds?
  112. end
  113. # When a merge request could not be automatically merged due to its unmergeable state we should:
  114. #
  115. # * create a todo for a merge_user
  116. #
  117. def merge_request_became_unmergeable(merge_request)
  118. create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds?
  119. end
  120. # When create a note we should:
  121. #
  122. # * mark all pending todos related to the noteable for the note author as done
  123. # * create a todo for each mentioned user on note
  124. #
  125. def new_note(note, current_user)
  126. handle_note(note, current_user)
  127. end
  128. # When update a note we should:
  129. #
  130. # * mark all pending todos related to the noteable for the current user as done
  131. # * create a todo for each new user mentioned on note
  132. #
  133. def update_note(note, current_user, skip_users = [])
  134. handle_note(note, current_user, skip_users)
  135. end
  136. # When an emoji is awarded we should:
  137. #
  138. # * mark all pending todos related to the awardable for the current user as done
  139. #
  140. def new_award_emoji(awardable, current_user)
  141. mark_pending_todos_as_done(awardable, current_user)
  142. end
  143. # When marking pending todos as done we should:
  144. #
  145. # * mark all pending todos related to the target for the current user as done
  146. #
  147. def mark_pending_todos_as_done(target, user)
  148. attributes = attributes_for_target(target)
  149. pending_todos(user, attributes).update_all(state: :done)
  150. user.update_todos_count_cache
  151. end
  152. # When user marks some todos as done
  153. def mark_todos_as_done(todos, current_user)
  154. update_todos_state_by_ids(todos.select(&:id), current_user, :done)
  155. end
  156. def mark_todos_as_done_by_ids(ids, current_user)
  157. update_todos_state_by_ids(ids, current_user, :done)
  158. end
  159. # When user marks some todos as pending
  160. def mark_todos_as_pending(todos, current_user)
  161. update_todos_state_by_ids(todos.select(&:id), current_user, :pending)
  162. end
  163. def mark_todos_as_pending_by_ids(ids, current_user)
  164. update_todos_state_by_ids(ids, current_user, :pending)
  165. end
  166. # When user marks an issue as todo
  167. def mark_todo(issuable, current_user)
  168. attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
  169. create_todos(current_user, attributes)
  170. end
  171. def todo_exist?(issuable, current_user)
  172. TodosFinder.new(current_user).execute.exists?(target: issuable)
  173. end
  174. private
  175. def update_todos_state_by_ids(ids, current_user, state)
  176. todos = current_user.todos.where(id: ids)
  177. # Only update those that are not really on that state
  178. todos = todos.where.not(state: state)
  179. todos_ids = todos.pluck(:id)
  180. todos.unscope(:order).update_all(state: state)
  181. current_user.update_todos_count_cache
  182. todos_ids
  183. end
  184. def create_todos(users, attributes)
  185. Array(users).map do |user|
  186. next if pending_todos(user, attributes).exists?
  187. todo = Todo.create(attributes.merge(user_id: user.id))
  188. user.update_todos_count_cache
  189. todo
  190. end
  191. end
  192. def new_issuable(issuable, author)
  193. create_assignment_todo(issuable, author)
  194. create_mention_todos(issuable.project, issuable, author)
  195. end
  196. def update_issuable(issuable, author, skip_users = [])
  197. # Skip toggling a task list item in a description
  198. return if toggling_tasks?(issuable)
  199. create_mention_todos(issuable.project, issuable, author, nil, skip_users)
  200. end
  201. def destroy_issuable(issuable, user)
  202. user.update_todos_count_cache
  203. end
  204. def toggling_tasks?(issuable)
  205. issuable.previous_changes.include?('description') &&
  206. issuable.tasks? && issuable.updated_tasks.any?
  207. end
  208. def handle_note(note, author, skip_users = [])
  209. # Skip system notes, and notes on project snippet
  210. return if note.system? || note.for_snippet?
  211. project = note.project
  212. target = note.noteable
  213. mark_pending_todos_as_done(target, author)
  214. create_mention_todos(project, target, author, note, skip_users)
  215. end
  216. def create_assignment_todo(issuable, author)
  217. if issuable.assignee
  218. attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
  219. create_todos(issuable.assignee, attributes)
  220. end
  221. end
  222. def create_mention_todos(project, target, author, note = nil, skip_users = [])
  223. # Create Todos for directly addressed users
  224. directly_addressed_users = filter_directly_addressed_users(project, note || target, author, skip_users)
  225. attributes = attributes_for_todo(project, target, author, Todo::DIRECTLY_ADDRESSED, note)
  226. create_todos(directly_addressed_users, attributes)
  227. # Create Todos for mentioned users
  228. mentioned_users = filter_mentioned_users(project, note || target, author, skip_users)
  229. attributes = attributes_for_todo(project, target, author, Todo::MENTIONED, note)
  230. create_todos(mentioned_users, attributes)
  231. end
  232. def create_build_failed_todo(merge_request, todo_author)
  233. attributes = attributes_for_todo(merge_request.project, merge_request, todo_author, Todo::BUILD_FAILED)
  234. create_todos(todo_author, attributes)
  235. end
  236. def create_unmergeable_todo(merge_request, merge_user)
  237. attributes = attributes_for_todo(merge_request.project, merge_request, merge_user, Todo::UNMERGEABLE)
  238. create_todos(merge_user, attributes)
  239. end
  240. def attributes_for_target(target)
  241. attributes = {
  242. project_id: target.project.id,
  243. target_id: target.id,
  244. target_type: target.class.name,
  245. commit_id: nil
  246. }
  247. if target.is_a?(Commit)
  248. attributes.merge!(target_id: nil, commit_id: target.id)
  249. end
  250. attributes
  251. end
  252. def attributes_for_todo(project, target, author, action, note = nil)
  253. attributes_for_target(target).merge!(
  254. project_id: project.id,
  255. author_id: author.id,
  256. action: action,
  257. note: note
  258. )
  259. end
  260. def filter_todo_users(users, project, target)
  261. reject_users_without_access(users, project, target).uniq
  262. end
  263. def filter_mentioned_users(project, target, author, skip_users = [])
  264. mentioned_users = target.mentioned_users(author) - skip_users
  265. filter_todo_users(mentioned_users, project, target)
  266. end
  267. def filter_directly_addressed_users(project, target, author, skip_users = [])
  268. directly_addressed_users = target.directly_addressed_users(author) - skip_users
  269. filter_todo_users(directly_addressed_users, project, target)
  270. end
  271. def reject_users_without_access(users, project, target)
  272. if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?)
  273. target = target.noteable
  274. end
  275. if target.is_a?(Issuable)
  276. select_users(users, :"read_#{target.to_ability_name}", target)
  277. else
  278. select_users(users, :read_project, project)
  279. end
  280. end
  281. def select_users(users, ability, subject)
  282. users.select do |user|
  283. user.can?(ability.to_sym, subject)
  284. end
  285. end
  286. def pending_todos(user, criteria = {})
  287. valid_keys = [:project_id, :target_id, :target_type, :commit_id]
  288. user.todos.pending.where(criteria.slice(*valid_keys))
  289. end
  290. end