gitlab-org--gitlab-foss/app/services/todo_service.rb
Sean McGivern 6b834f2cbc Create a todo on failing MR build
When a build fails for a commit, create a todo for the author of the
merge request that commit is the HEAD of. If the commit isn't the HEAD
commit of any MR, don't do anything. If there already is a todo for that
user and MR, don't do anything.

Current limitations:
- This isn't configurable by project.
- The author of a merge request might not be the person who pushed the
  breaking commit.
2016-05-17 10:17:45 +01:00

231 lines
6.6 KiB
Ruby

# TodoService class
#
# Used for creating todos after certain user actions
#
# Ex.
# TodoService.new.new_issue(issue, current_user)
#
class TodoService
# When create an issue we should:
#
# * create a todo for assignee if issue is assigned
# * create a todo for each mentioned user on issue
#
def new_issue(issue, current_user)
new_issuable(issue, current_user)
end
# When update an issue we should:
#
# * mark all pending todos related to the issue for the current user as done
#
def update_issue(issue, current_user)
create_mention_todos(issue.project, issue, current_user)
end
# When close an issue we should:
#
# * mark all pending todos related to the target for the current user as done
#
def close_issue(issue, current_user)
mark_pending_todos_as_done(issue, current_user)
end
# When we reassign an issue we should:
#
# * create a pending todo for new assignee if issue is assigned
#
def reassigned_issue(issue, current_user)
create_assignment_todo(issue, current_user)
end
# When create a merge request we should:
#
# * creates a pending todo for assignee if merge request is assigned
# * create a todo for each mentioned user on merge request
#
def new_merge_request(merge_request, current_user)
new_issuable(merge_request, current_user)
end
# When update a merge request we should:
#
# * create a todo for each mentioned user on merge request
#
def update_merge_request(merge_request, current_user)
create_mention_todos(merge_request.project, merge_request, current_user)
end
# When close a merge request we should:
#
# * mark all pending todos related to the target for the current user as done
#
def close_merge_request(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user)
end
# When we reassign a merge request we should:
#
# * creates a pending todo for new assignee if merge request is assigned
#
def reassigned_merge_request(merge_request, current_user)
create_assignment_todo(merge_request, current_user)
end
# When merge a merge request we should:
#
# * mark all pending todos related to the target for the current user as done
#
def merge_merge_request(merge_request, current_user)
mark_pending_todos_as_done(merge_request, current_user)
end
# 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
# When create a note we should:
#
# * mark all pending todos related to the noteable for the note author as done
# * create a todo for each mentioned user on note
#
def new_note(note, current_user)
handle_note(note, current_user)
end
# When update a note we should:
#
# * mark all pending todos related to the noteable for the current user as done
# * create a todo for each new user mentioned on note
#
def update_note(note, current_user)
handle_note(note, current_user)
end
# When marking pending todos as done we should:
#
# * mark all pending todos related to the target for the current user as done
#
def mark_pending_todos_as_done(target, user)
attributes = attributes_for_target(target)
pending_todos(user, attributes).update_all(state: :done)
end
private
def create_todos(users, attributes)
Array(users).each do |user|
next if pending_todos(user, attributes).exists?
Todo.create(attributes.merge(user_id: user.id))
end
end
def new_issuable(issuable, author)
create_assignment_todo(issuable, author)
create_mention_todos(issuable.project, issuable, author)
end
def handle_note(note, author)
# Skip system notes, and notes on project snippet
return if note.system? || note.for_snippet?
project = note.project
target = note.noteable
mark_pending_todos_as_done(target, author)
create_mention_todos(project, target, author, note)
end
def create_assignment_todo(issuable, author)
if issuable.assignee && issuable.assignee != author
attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
create_todos(issuable.assignee, attributes)
end
end
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
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
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
)
end
def filter_mentioned_users(project, target, author)
mentioned_users = target.mentioned_users
mentioned_users = reject_users_without_access(mentioned_users, project, target)
mentioned_users.delete(author)
mentioned_users.uniq
end
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
def pending_todos(user, criteria = {})
valid_keys = [:project_id, :target_id, :target_type, :commit_id]
user.todos.pending.where(criteria.slice(*valid_keys))
end
end