2018-07-05 06:18:17 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-02-20 08:59:59 -05:00
|
|
|
# TodoService class
|
2016-02-15 13:13:52 -05:00
|
|
|
#
|
2016-06-02 09:46:58 -04:00
|
|
|
# Used for creating/updating todos after certain user actions
|
2016-02-15 13:13:52 -05:00
|
|
|
#
|
|
|
|
# Ex.
|
2016-02-20 08:59:59 -05:00
|
|
|
# TodoService.new.new_issue(issue, current_user)
|
2016-02-15 13:13:52 -05:00
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
class TodoService
|
2016-02-15 13:13:52 -05:00
|
|
|
# When create an issue we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * create a todo for assignee if issue is assigned
|
|
|
|
# * create a todo for each mentioned user on issue
|
2016-02-15 13:13:52 -05:00
|
|
|
#
|
|
|
|
def new_issue(issue, current_user)
|
2016-02-16 18:01:14 -05:00
|
|
|
new_issuable(issue, current_user)
|
2016-02-15 13:13:52 -05:00
|
|
|
end
|
|
|
|
|
2016-02-17 17:04:14 -05:00
|
|
|
# When update an issue we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * mark all pending todos related to the issue for the current user as done
|
2016-02-17 17:04:14 -05:00
|
|
|
#
|
2017-03-28 02:25:43 -04:00
|
|
|
def update_issue(issue, current_user, skip_users = [])
|
|
|
|
update_issuable(issue, current_user, skip_users)
|
2016-02-17 17:04:14 -05:00
|
|
|
end
|
|
|
|
|
2016-02-16 15:31:40 -05:00
|
|
|
# When close an issue we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * mark all pending todos related to the target for the current user as done
|
2016-02-16 15:31:40 -05:00
|
|
|
#
|
|
|
|
def close_issue(issue, current_user)
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(issue, current_user)
|
2016-02-16 15:31:40 -05:00
|
|
|
end
|
|
|
|
|
2017-12-08 07:17:22 -05:00
|
|
|
# When we destroy a todo target we should:
|
2016-09-01 18:12:05 -04:00
|
|
|
#
|
2017-12-08 07:17:22 -05:00
|
|
|
# * refresh the todos count cache for all users with todos on the target
|
2016-09-01 18:12:05 -04:00
|
|
|
#
|
2017-12-08 07:17:22 -05:00
|
|
|
# This needs to yield back to the caller to destroy the target, because it
|
|
|
|
# collects the todo users before the todos themselves are deleted, then
|
|
|
|
# updates the todo counts for those users.
|
|
|
|
#
|
|
|
|
def destroy_target(target)
|
2018-09-20 11:05:26 -04:00
|
|
|
todo_users = UsersWithPendingTodosFinder.new(target).execute.to_a
|
2017-12-08 07:17:22 -05:00
|
|
|
|
|
|
|
yield target
|
|
|
|
|
|
|
|
todo_users.each(&:update_todos_count_cache)
|
2016-09-01 18:12:05 -04:00
|
|
|
end
|
|
|
|
|
2019-04-07 14:35:16 -04:00
|
|
|
# When we reassign an issuable we should:
|
2016-02-15 13:13:52 -05:00
|
|
|
#
|
2019-04-07 14:35:16 -04:00
|
|
|
# * create a pending todo for new assignee if issuable is assigned
|
2016-02-15 13:13:52 -05:00
|
|
|
#
|
2019-04-07 14:35:16 -04:00
|
|
|
def reassigned_issuable(issuable, current_user, old_assignees = [])
|
|
|
|
create_assignment_todo(issuable, current_user, old_assignees)
|
2016-02-16 18:01:14 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# When create a merge request we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * creates a pending todo for assignee if merge request is assigned
|
|
|
|
# * create a todo for each mentioned user on merge request
|
2016-02-16 18:01:14 -05:00
|
|
|
#
|
|
|
|
def new_merge_request(merge_request, current_user)
|
|
|
|
new_issuable(merge_request, current_user)
|
|
|
|
end
|
|
|
|
|
2016-02-17 17:04:14 -05:00
|
|
|
# When update a merge request we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * create a todo for each mentioned user on merge request
|
2016-02-17 17:04:14 -05:00
|
|
|
#
|
2017-03-28 02:25:43 -04:00
|
|
|
def update_merge_request(merge_request, current_user, skip_users = [])
|
|
|
|
update_issuable(merge_request, current_user, skip_users)
|
2016-02-17 17:04:14 -05:00
|
|
|
end
|
|
|
|
|
2016-02-16 18:47:33 -05:00
|
|
|
# When close a merge request we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * mark all pending todos related to the target for the current user as done
|
2016-02-16 18:47:33 -05:00
|
|
|
#
|
|
|
|
def close_merge_request(merge_request, current_user)
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(merge_request, current_user)
|
2016-02-16 18:47:33 -05:00
|
|
|
end
|
|
|
|
|
2016-02-16 20:54:10 -05:00
|
|
|
# When merge a merge request we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * mark all pending todos related to the target for the current user as done
|
2016-02-16 20:54:10 -05:00
|
|
|
#
|
|
|
|
def merge_merge_request(merge_request, current_user)
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(merge_request, current_user)
|
2016-02-16 20:54:10 -05:00
|
|
|
end
|
|
|
|
|
2016-03-08 13:22:50 -05:00
|
|
|
# When a build fails on the HEAD of a merge request we should:
|
|
|
|
#
|
2018-05-09 05:55:00 -04:00
|
|
|
# * create a todo for each merge participant
|
2016-03-08 13:22:50 -05:00
|
|
|
#
|
|
|
|
def merge_request_build_failed(merge_request)
|
2018-05-09 05:55:00 -04:00
|
|
|
merge_request.merge_participants.each do |user|
|
|
|
|
create_build_failed_todo(merge_request, user)
|
|
|
|
end
|
2016-03-08 13:22:50 -05:00
|
|
|
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)
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(merge_request, current_user)
|
2016-03-08 13:22:50 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# When a build is retried to a merge request we should:
|
|
|
|
#
|
2018-05-09 05:55:00 -04:00
|
|
|
# * mark all pending todos related to the merge request as done for each merge participant
|
2016-03-08 13:22:50 -05:00
|
|
|
#
|
|
|
|
def merge_request_build_retried(merge_request)
|
2018-05-09 05:55:00 -04:00
|
|
|
merge_request.merge_participants.each do |user|
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(merge_request, user)
|
2018-05-09 05:55:00 -04:00
|
|
|
end
|
2016-03-08 13:22:50 -05:00
|
|
|
end
|
2017-03-28 02:25:43 -04:00
|
|
|
|
2018-05-09 05:55:00 -04:00
|
|
|
# When a merge request could not be merged due to its unmergeable state we should:
|
2016-12-14 09:37:31 -05:00
|
|
|
#
|
2018-05-09 05:55:00 -04:00
|
|
|
# * create a todo for each merge participant
|
2016-12-14 09:37:31 -05:00
|
|
|
#
|
|
|
|
def merge_request_became_unmergeable(merge_request)
|
2018-05-09 05:55:00 -04:00
|
|
|
merge_request.merge_participants.each do |user|
|
|
|
|
create_unmergeable_todo(merge_request, user)
|
|
|
|
end
|
2016-12-14 09:37:31 -05:00
|
|
|
end
|
2017-03-28 02:25:43 -04:00
|
|
|
|
2016-02-16 16:29:11 -05:00
|
|
|
# When create a note we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * mark all pending todos related to the noteable for the note author as done
|
|
|
|
# * create a todo for each mentioned user on note
|
2016-02-16 16:29:11 -05:00
|
|
|
#
|
2016-02-18 16:12:52 -05:00
|
|
|
def new_note(note, current_user)
|
|
|
|
handle_note(note, current_user)
|
2016-02-16 16:29:11 -05:00
|
|
|
end
|
|
|
|
|
2016-02-16 17:21:24 -05:00
|
|
|
# When update a note we should:
|
|
|
|
#
|
2016-02-20 08:59:59 -05:00
|
|
|
# * mark all pending todos related to the noteable for the current user as done
|
|
|
|
# * create a todo for each new user mentioned on note
|
2016-02-16 17:21:24 -05:00
|
|
|
#
|
2017-03-28 02:25:43 -04:00
|
|
|
def update_note(note, current_user, skip_users = [])
|
|
|
|
handle_note(note, current_user, skip_users)
|
2016-02-18 16:12:52 -05:00
|
|
|
end
|
|
|
|
|
2016-04-16 15:09:08 -04:00
|
|
|
# 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)
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(awardable, current_user)
|
2016-04-16 15:09:08 -04:00
|
|
|
end
|
|
|
|
|
2020-06-08 14:08:27 -04:00
|
|
|
# When assigning an alert we should:
|
|
|
|
#
|
|
|
|
# * create a pending todo for new assignee if alert is assigned
|
|
|
|
#
|
|
|
|
def assign_alert(alert, current_user)
|
|
|
|
create_assignment_todo(alert, current_user, [])
|
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
# 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)
|
2016-06-02 09:46:58 -04:00
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
def todo_exist?(issuable, current_user)
|
|
|
|
TodosFinder.new(current_user).any_for_target?(issuable, :pending)
|
2016-08-16 16:10:37 -04:00
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
# Resolves all todos related to target
|
|
|
|
def resolve_todos_for_target(target, current_user)
|
|
|
|
attributes = attributes_for_target(target)
|
2016-06-02 09:46:58 -04:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos(pending_todos(current_user, attributes), current_user)
|
2019-11-21 13:06:26 -05:00
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
def resolve_todos(todos, current_user, resolution: :done, resolved_by_action: :system_done)
|
|
|
|
todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)
|
2019-12-03 13:06:49 -05:00
|
|
|
|
|
|
|
current_user.update_todos_count_cache
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
todos_ids
|
2017-01-16 08:11:08 -05:00
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
def resolve_todo(todo, current_user, resolution: :done, resolved_by_action: :system_done)
|
|
|
|
return if todo.done?
|
2016-02-18 16:12:52 -05:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
todo.update(state: resolution, resolved_by_action: resolved_by_action)
|
2016-06-07 04:44:01 -04:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
current_user.update_todos_count_cache
|
2016-08-11 13:00:06 -04:00
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
def restore_todos(todos, current_user)
|
|
|
|
todos_ids = todos.batch_update(state: :pending)
|
2016-02-18 16:12:52 -05:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
current_user.update_todos_count_cache
|
|
|
|
|
|
|
|
todos_ids
|
2017-04-21 05:36:34 -04:00
|
|
|
end
|
2017-01-16 08:11:08 -05:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
def restore_todo(todo, current_user)
|
|
|
|
return if todo.pending?
|
2018-09-20 11:05:26 -04:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
todo.update(state: :pending)
|
2018-09-20 11:05:26 -04:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
current_user.update_todos_count_cache
|
2017-01-16 08:11:08 -05:00
|
|
|
end
|
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
private
|
|
|
|
|
2016-03-16 19:31:30 -04:00
|
|
|
def create_todos(users, attributes)
|
2016-06-09 11:12:59 -04:00
|
|
|
Array(users).map do |user|
|
2016-03-16 19:31:30 -04:00
|
|
|
next if pending_todos(user, attributes).exists?
|
2017-11-14 04:02:39 -05:00
|
|
|
|
2016-06-27 09:08:57 -04:00
|
|
|
todo = Todo.create(attributes.merge(user_id: user.id))
|
2016-06-02 09:46:58 -04:00
|
|
|
user.update_todos_count_cache
|
2016-06-27 09:08:57 -04:00
|
|
|
todo
|
2016-02-18 16:12:52 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def new_issuable(issuable, author)
|
2016-02-20 08:59:59 -05:00
|
|
|
create_assignment_todo(issuable, author)
|
|
|
|
create_mention_todos(issuable.project, issuable, author)
|
2016-02-18 16:12:52 -05:00
|
|
|
end
|
|
|
|
|
2017-03-28 02:25:43 -04:00
|
|
|
def update_issuable(issuable, author, skip_users = [])
|
2016-06-09 13:07:58 -04:00
|
|
|
# Skip toggling a task list item in a description
|
2016-06-17 10:34:11 -04:00
|
|
|
return if toggling_tasks?(issuable)
|
2016-06-09 13:07:58 -04:00
|
|
|
|
2017-03-28 02:25:43 -04:00
|
|
|
create_mention_todos(issuable.project, issuable, author, nil, skip_users)
|
2016-06-09 13:07:58 -04:00
|
|
|
end
|
|
|
|
|
2016-06-17 10:34:11 -04:00
|
|
|
def toggling_tasks?(issuable)
|
|
|
|
issuable.previous_changes.include?('description') &&
|
|
|
|
issuable.tasks? && issuable.updated_tasks.any?
|
|
|
|
end
|
|
|
|
|
2017-03-28 02:25:43 -04:00
|
|
|
def handle_note(note, author, skip_users = [])
|
2018-02-28 02:48:23 -05:00
|
|
|
return unless note.can_create_todo?
|
2016-02-17 17:04:14 -05:00
|
|
|
|
2016-02-18 14:26:52 -05:00
|
|
|
project = note.project
|
2020-05-28 14:08:37 -04:00
|
|
|
target = note.noteable
|
2016-02-17 17:04:14 -05:00
|
|
|
|
2020-05-28 14:08:37 -04:00
|
|
|
resolve_todos_for_target(target, author)
|
2017-03-28 02:25:43 -04:00
|
|
|
create_mention_todos(project, target, author, note, skip_users)
|
2016-02-18 16:12:52 -05:00
|
|
|
end
|
2016-02-17 17:04:14 -05:00
|
|
|
|
2020-06-08 14:08:27 -04:00
|
|
|
def create_assignment_todo(target, author, old_assignees = [])
|
|
|
|
if target.assignees.any?
|
|
|
|
assignees = target.assignees - old_assignees
|
|
|
|
attributes = attributes_for_todo(target.project, target, author, Todo::ASSIGNED)
|
2017-10-04 12:24:23 -04:00
|
|
|
create_todos(assignees, attributes)
|
2016-02-16 17:21:24 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-07-16 09:35:19 -04:00
|
|
|
def create_mention_todos(parent, target, author, note = nil, skip_users = [])
|
2016-12-05 07:12:22 -05:00
|
|
|
# Create Todos for directly addressed users
|
2018-07-16 09:35:19 -04:00
|
|
|
directly_addressed_users = filter_directly_addressed_users(parent, note || target, author, skip_users)
|
|
|
|
attributes = attributes_for_todo(parent, target, author, Todo::DIRECTLY_ADDRESSED, note)
|
2016-12-05 07:12:22 -05:00
|
|
|
create_todos(directly_addressed_users, attributes)
|
|
|
|
|
|
|
|
# Create Todos for mentioned users
|
2018-07-16 09:35:19 -04:00
|
|
|
mentioned_users = filter_mentioned_users(parent, note || target, author, skip_users)
|
|
|
|
attributes = attributes_for_todo(parent, target, author, Todo::MENTIONED, note)
|
2016-03-16 19:31:30 -04:00
|
|
|
create_todos(mentioned_users, attributes)
|
|
|
|
end
|
|
|
|
|
2016-12-12 16:55:33 -05:00
|
|
|
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)
|
2016-03-08 13:22:50 -05:00
|
|
|
end
|
|
|
|
|
2018-05-09 05:55:00 -04:00
|
|
|
def create_unmergeable_todo(merge_request, todo_author)
|
|
|
|
attributes = attributes_for_todo(merge_request.project, merge_request, todo_author, Todo::UNMERGEABLE)
|
|
|
|
create_todos(todo_author, attributes)
|
2016-12-14 09:37:31 -05:00
|
|
|
end
|
|
|
|
|
2016-03-16 19:31:30 -04:00
|
|
|
def attributes_for_target(target)
|
|
|
|
attributes = {
|
2017-04-27 06:41:26 -04:00
|
|
|
project_id: target&.project&.id,
|
2016-03-16 19:31:30 -04:00
|
|
|
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!(
|
2018-07-16 09:35:19 -04:00
|
|
|
project_id: project&.id,
|
2016-03-16 19:31:30 -04:00
|
|
|
author_id: author.id,
|
|
|
|
action: action,
|
|
|
|
note: note
|
|
|
|
)
|
2016-02-15 13:13:52 -05:00
|
|
|
end
|
2016-02-15 21:26:33 -05:00
|
|
|
|
2018-07-16 09:35:19 -04:00
|
|
|
def filter_todo_users(users, parent, target)
|
|
|
|
reject_users_without_access(users, parent, target).uniq
|
2016-12-05 07:12:22 -05:00
|
|
|
end
|
|
|
|
|
2018-07-16 09:35:19 -04:00
|
|
|
def filter_mentioned_users(parent, target, author, skip_users = [])
|
2017-03-28 02:25:43 -04:00
|
|
|
mentioned_users = target.mentioned_users(author) - skip_users
|
2018-07-16 09:35:19 -04:00
|
|
|
filter_todo_users(mentioned_users, parent, target)
|
2016-12-05 07:12:22 -05:00
|
|
|
end
|
|
|
|
|
2018-07-16 09:35:19 -04:00
|
|
|
def filter_directly_addressed_users(parent, target, author, skip_users = [])
|
2017-03-28 02:25:43 -04:00
|
|
|
directly_addressed_users = target.directly_addressed_users(author) - skip_users
|
2018-07-16 09:35:19 -04:00
|
|
|
filter_todo_users(directly_addressed_users, parent, target)
|
2016-02-17 12:46:26 -05:00
|
|
|
end
|
|
|
|
|
2018-07-16 09:35:19 -04:00
|
|
|
def reject_users_without_access(users, parent, target)
|
2019-08-22 11:05:07 -04:00
|
|
|
target = target.noteable if target.is_a?(Note)
|
2016-03-30 17:41:21 -04:00
|
|
|
|
2019-08-22 11:05:07 -04:00
|
|
|
if target.respond_to?(:to_ability_name)
|
2016-10-04 08:52:08 -04:00
|
|
|
select_users(users, :"read_#{target.to_ability_name}", target)
|
2016-03-30 17:41:21 -04:00
|
|
|
else
|
2018-07-16 09:35:19 -04:00
|
|
|
select_users(users, :read_project, parent)
|
2016-03-30 17:41:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def select_users(users, ability, subject)
|
|
|
|
users.select do |user|
|
|
|
|
user.can?(ability.to_sym, subject)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-16 19:31:30 -04:00
|
|
|
def pending_todos(user, criteria = {})
|
2018-09-20 11:05:26 -04:00
|
|
|
PendingTodosFinder.new(user, criteria).execute
|
2016-02-16 18:01:14 -05:00
|
|
|
end
|
2016-02-15 13:13:52 -05:00
|
|
|
end
|
2019-09-13 09:26:31 -04:00
|
|
|
|
|
|
|
TodoService.prepend_if_ee('EE::TodoService')
|