debian-mirror-gitlab/app/services/todo_service.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

465 lines
14 KiB
Ruby
Raw Normal View History

2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2016-04-02 18:10:28 +05:30
# TodoService class
#
2016-06-22 15:30:34 +05:30
# Used for creating/updating todos after certain user actions
2016-04-02 18:10:28 +05:30
#
# Ex.
# TodoService.new.new_issue(issue, current_user)
#
class TodoService
2020-11-24 15:15:51 +05:30
include Gitlab::Utils::UsageData
2022-05-07 20:08:51 +05:30
2016-04-02 18:10:28 +05:30
# 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
#
2017-08-17 22:00:37 +05:30
def update_issue(issue, current_user, skip_users = [])
update_issuable(issue, current_user, skip_users)
2016-04-02 18:10:28 +05:30
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)
2020-06-23 00:09:42 +05:30
resolve_todos_for_target(issue, current_user)
2016-04-02 18:10:28 +05:30
end
2018-03-17 18:26:18 +05:30
# When we destroy a todo target we should:
2016-09-29 09:46:39 +05:30
#
2018-03-17 18:26:18 +05:30
# * refresh the todos count cache for all users with todos on the target
2016-09-29 09:46:39 +05:30
#
2018-03-17 18:26:18 +05:30
# 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)
2021-06-08 01:23:25 +05:30
todo_user_ids = target.todos.distinct_user_ids
2018-03-17 18:26:18 +05:30
yield target
2021-06-08 01:23:25 +05:30
Users::UpdateTodoCountCacheService.new(todo_user_ids).execute if todo_user_ids.present?
2016-09-29 09:46:39 +05:30
end
2020-10-24 23:57:45 +05:30
# When we reassign an assignable object (issuable, alert) we should:
2016-04-02 18:10:28 +05:30
#
2020-10-24 23:57:45 +05:30
# * create a pending todo for new assignee if object is assigned
2016-04-02 18:10:28 +05:30
#
2020-10-24 23:57:45 +05:30
def reassigned_assignable(issuable, current_user, old_assignees = [])
2019-07-31 22:56:46 +05:30
create_assignment_todo(issuable, current_user, old_assignees)
2016-04-02 18:10:28 +05:30
end
2020-11-24 15:15:51 +05:30
# When we reassign an reviewable object (merge request) we should:
#
# * create a pending todo for new reviewer if object is assigned
#
def reassigned_reviewable(issuable, current_user, old_reviewers = [])
create_reviewer_todo(issuable, current_user, old_reviewers)
end
2016-04-02 18:10:28 +05:30
# 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
#
2017-08-17 22:00:37 +05:30
def update_merge_request(merge_request, current_user, skip_users = [])
update_issuable(merge_request, current_user, skip_users)
2016-04-02 18:10:28 +05:30
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)
2020-06-23 00:09:42 +05:30
resolve_todos_for_target(merge_request, current_user)
2016-04-02 18:10:28 +05:30
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)
2020-06-23 00:09:42 +05:30
resolve_todos_for_target(merge_request, current_user)
2016-04-02 18:10:28 +05:30
end
2016-06-02 11:05:42 +05:30
# When a build fails on the HEAD of a merge request we should:
#
2018-11-08 19:23:39 +05:30
# * create a todo for each merge participant
2016-06-02 11:05:42 +05:30
#
def merge_request_build_failed(merge_request)
2018-11-08 19:23:39 +05:30
merge_request.merge_participants.each do |user|
create_build_failed_todo(merge_request, user)
end
2016-06-02 11:05:42 +05:30
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-06-23 00:09:42 +05:30
resolve_todos_for_target(merge_request, current_user)
2016-06-02 11:05:42 +05:30
end
# When a build is retried to a merge request we should:
#
2018-11-08 19:23:39 +05:30
# * mark all pending todos related to the merge request as done for each merge participant
2016-06-02 11:05:42 +05:30
#
def merge_request_build_retried(merge_request)
2018-11-08 19:23:39 +05:30
merge_request.merge_participants.each do |user|
2020-06-23 00:09:42 +05:30
resolve_todos_for_target(merge_request, user)
2018-11-08 19:23:39 +05:30
end
2017-08-17 22:00:37 +05:30
end
2018-11-08 19:23:39 +05:30
# When a merge request could not be merged due to its unmergeable state we should:
2017-08-17 22:00:37 +05:30
#
2018-11-08 19:23:39 +05:30
# * create a todo for each merge participant
2017-08-17 22:00:37 +05:30
#
def merge_request_became_unmergeable(merge_request)
2018-11-08 19:23:39 +05:30
merge_request.merge_participants.each do |user|
create_unmergeable_todo(merge_request, user)
end
2016-06-02 11:05:42 +05:30
end
2016-04-02 18:10:28 +05:30
# 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
#
2017-08-17 22:00:37 +05:30
def update_note(note, current_user, skip_users = [])
handle_note(note, current_user, skip_users)
2016-04-02 18:10:28 +05:30
end
# 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-06-23 00:09:42 +05:30
resolve_todos_for_target(awardable, current_user)
end
2020-07-28 23:09:34 +05:30
# When user marks a target as todo
def mark_todo(target, current_user)
2023-03-04 22:38:38 +05:30
project = target.project
attributes = attributes_for_todo(project, target, current_user, Todo::MARKED)
create_todos(current_user, attributes, project&.namespace, project)
2016-09-13 17:45:13 +05:30
end
2020-06-23 00:09:42 +05:30
def todo_exist?(issuable, current_user)
TodosFinder.new(current_user).any_for_target?(issuable, :pending)
2017-08-17 22:00:37 +05:30
end
2016-06-22 15:30:34 +05:30
2020-06-23 00:09:42 +05:30
# Resolves all todos related to target
def resolve_todos_for_target(target, current_user)
attributes = attributes_for_target(target)
2020-01-01 13:55:28 +05:30
2021-04-29 21:17:54 +05:30
resolve_todos(pending_todos([current_user], attributes), current_user)
2020-06-23 00:09:42 +05:30
end
2020-01-01 13:55:28 +05:30
2020-06-23 00:09:42 +05:30
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)
2020-01-01 13:55:28 +05:30
current_user.update_todos_count_cache
2020-06-23 00:09:42 +05:30
todos_ids
2017-08-17 22:00:37 +05:30
end
2020-06-23 00:09:42 +05:30
def resolve_todo(todo, current_user, resolution: :done, resolved_by_action: :system_done)
return if todo.done?
2016-04-02 18:10:28 +05:30
2020-06-23 00:09:42 +05:30
todo.update(state: resolution, resolved_by_action: resolved_by_action)
2020-06-23 00:09:42 +05:30
current_user.update_todos_count_cache
2016-09-13 17:45:13 +05:30
end
2023-04-23 21:23:45 +05:30
def resolve_access_request_todos(current_user, member)
return if current_user.nil? || member.nil?
target = member.source
finder_params = {
state: :pending,
author_id: member.user_id,
action_id: ::Todo::MEMBER_ACCESS_REQUESTED,
type: target.class.polymorphic_name,
target: target.id
}
todos = TodosFinder.new(current_user, finder_params).execute
resolve_todos(todos, current_user)
end
2020-06-23 00:09:42 +05:30
def restore_todos(todos, current_user)
todos_ids = todos.batch_update(state: :pending)
2016-04-02 18:10:28 +05:30
2020-06-23 00:09:42 +05:30
current_user.update_todos_count_cache
todos_ids
2017-09-10 17:25:29 +05:30
end
2017-08-17 22:00:37 +05:30
2020-06-23 00:09:42 +05:30
def restore_todo(todo, current_user)
return if todo.pending?
2018-12-13 13:39:08 +05:30
2020-06-23 00:09:42 +05:30
todo.update(state: :pending)
2018-12-13 13:39:08 +05:30
2020-06-23 00:09:42 +05:30
current_user.update_todos_count_cache
2017-08-17 22:00:37 +05:30
end
2021-03-11 19:13:27 +05:30
def create_request_review_todo(target, author, reviewers)
2023-03-04 22:38:38 +05:30
project = target.project
attributes = attributes_for_todo(project, target, author, Todo::REVIEW_REQUESTED)
create_todos(reviewers, attributes, project.namespace, project)
end
2023-03-17 16:20:25 +05:30
def create_member_access_request_todos(member)
2023-03-04 22:38:38 +05:30
source = member.source
attributes = attributes_for_access_request_todos(source, member.user, Todo::MEMBER_ACCESS_REQUESTED)
approvers = source.access_request_approvers_to_be_notified.map(&:user)
return true if approvers.empty?
if source.instance_of? Project
project = source
namespace = project.namespace
else
project = nil
namespace = source
end
create_todos(approvers, attributes, namespace, project)
2021-03-11 19:13:27 +05:30
end
2020-06-23 00:09:42 +05:30
private
2023-03-04 22:38:38 +05:30
def create_todos(users, attributes, namespace, project)
2021-04-29 21:17:54 +05:30
users = Array(users)
2018-03-17 18:26:18 +05:30
2021-04-29 21:17:54 +05:30
return if users.empty?
2022-05-07 20:08:51 +05:30
users_single_todos, users_multiple_todos = users.partition { |u| Feature.disabled?(:multiple_todos, u) }
excluded_user_ids = []
if users_single_todos.present?
excluded_user_ids += pending_todos(
users_single_todos,
attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion)
).distinct_user_ids
end
if users_multiple_todos.present? && !Todo::ACTIONS_MULTIPLE_ALLOWED.include?(attributes.fetch(:action))
excluded_user_ids += pending_todos(
users_multiple_todos,
attributes.slice(:project_id, :target_id, :target_type, :commit_id, :discussion, :action)
).distinct_user_ids
end
users.reject! { |user| excluded_user_ids.include?(user.id) }
2021-04-29 21:17:54 +05:30
todos = users.map do |user|
2020-11-24 15:15:51 +05:30
issue_type = attributes.delete(:issue_type)
2023-03-04 22:38:38 +05:30
track_todo_creation(user, issue_type, namespace, project)
2020-11-24 15:15:51 +05:30
2021-04-29 21:17:54 +05:30
Todo.create(attributes.merge(user_id: user.id))
2016-04-02 18:10:28 +05:30
end
2021-04-29 21:17:54 +05:30
2021-06-08 01:23:25 +05:30
Users::UpdateTodoCountCacheService.new(users.map(&:id)).execute
2021-04-29 21:17:54 +05:30
todos
2016-04-02 18:10:28 +05:30
end
def new_issuable(issuable, author)
create_assignment_todo(issuable, author)
2020-11-24 15:15:51 +05:30
create_reviewer_todo(issuable, author) if issuable.allows_reviewers?
2016-04-02 18:10:28 +05:30
create_mention_todos(issuable.project, issuable, author)
end
2017-08-17 22:00:37 +05:30
def update_issuable(issuable, author, skip_users = [])
# Skip toggling a task list item in a description
2016-06-22 15:30:34 +05:30
return if toggling_tasks?(issuable)
2017-08-17 22:00:37 +05:30
create_mention_todos(issuable.project, issuable, author, nil, skip_users)
end
2016-06-22 15:30:34 +05:30
def toggling_tasks?(issuable)
issuable.previous_changes.include?('description') &&
issuable.tasks? && issuable.updated_tasks.any?
end
2017-08-17 22:00:37 +05:30
def handle_note(note, author, skip_users = [])
2018-03-27 19:54:05 +05:30
return unless note.can_create_todo?
2016-04-02 18:10:28 +05:30
project = note.project
2020-06-23 00:09:42 +05:30
target = note.noteable
2016-04-02 18:10:28 +05:30
2020-06-23 00:09:42 +05:30
resolve_todos_for_target(target, author)
2017-08-17 22:00:37 +05:30
create_mention_todos(project, target, author, note, skip_users)
2016-04-02 18:10:28 +05:30
end
2020-06-23 00:09:42 +05:30
def create_assignment_todo(target, author, old_assignees = [])
if target.assignees.any?
2023-03-04 22:38:38 +05:30
project = target.project
2020-06-23 00:09:42 +05:30
assignees = target.assignees - old_assignees
2023-03-04 22:38:38 +05:30
attributes = attributes_for_todo(project, target, author, Todo::ASSIGNED)
create_todos(assignees, attributes, project.namespace, project)
2016-04-02 18:10:28 +05:30
end
end
2020-11-24 15:15:51 +05:30
def create_reviewer_todo(target, author, old_reviewers = [])
if target.reviewers.any?
reviewers = target.reviewers - old_reviewers
2021-03-11 19:13:27 +05:30
create_request_review_todo(target, author, reviewers)
2020-11-24 15:15:51 +05:30
end
end
2018-11-18 11:00:15 +05:30
def create_mention_todos(parent, target, author, note = nil, skip_users = [])
2017-08-17 22:00:37 +05:30
# Create Todos for directly addressed users
2018-11-18 11:00:15 +05:30
directly_addressed_users = filter_directly_addressed_users(parent, note || target, author, skip_users)
attributes = attributes_for_todo(parent, target, author, Todo::DIRECTLY_ADDRESSED, note)
2023-03-04 22:38:38 +05:30
create_todos(directly_addressed_users, attributes, parent&.namespace, parent)
2017-08-17 22:00:37 +05:30
# Create Todos for mentioned users
2021-03-08 18:12:59 +05:30
mentioned_users = filter_mentioned_users(parent, note || target, author, skip_users + directly_addressed_users)
2018-11-18 11:00:15 +05:30
attributes = attributes_for_todo(parent, target, author, Todo::MENTIONED, note)
2023-03-04 22:38:38 +05:30
create_todos(mentioned_users, attributes, parent&.namespace, parent)
2016-04-02 18:10:28 +05:30
end
2017-08-17 22:00:37 +05:30
def create_build_failed_todo(merge_request, todo_author)
2023-03-04 22:38:38 +05:30
project = merge_request.project
attributes = attributes_for_todo(project, merge_request, todo_author, Todo::BUILD_FAILED)
create_todos(todo_author, attributes, project.namespace, project)
2017-08-17 22:00:37 +05:30
end
2018-11-08 19:23:39 +05:30
def create_unmergeable_todo(merge_request, todo_author)
2023-03-04 22:38:38 +05:30
project = merge_request.project
attributes = attributes_for_todo(project, merge_request, todo_author, Todo::UNMERGEABLE)
create_todos(todo_author, attributes, project.namespace, project)
2016-06-02 11:05:42 +05:30
end
def attributes_for_target(target)
attributes = {
2017-08-17 22:00:37 +05:30
project_id: target&.project&.id,
2016-06-02 11:05:42 +05:30
target_id: target.id,
target_type: target.class.name,
commit_id: nil
}
2023-01-13 00:05:48 +05:30
case target
when Commit
2016-06-02 11:05:42 +05:30
attributes.merge!(target_id: nil, commit_id: target.id)
2023-01-13 00:05:48 +05:30
when Issue
2020-11-24 15:15:51 +05:30
attributes[:issue_type] = target.issue_type
2023-01-13 00:05:48 +05:30
when Discussion
2021-09-04 01:27:46 +05:30
attributes.merge!(target_type: nil, target_id: nil, discussion: target)
2016-04-02 18:10:28 +05:30
end
2016-06-02 11:05:42 +05:30
attributes
2016-04-02 18:10:28 +05:30
end
2016-06-02 11:05:42 +05:30
def attributes_for_todo(project, target, author, action, note = nil)
attributes_for_target(target).merge!(
2018-11-18 11:00:15 +05:30
project_id: project&.id,
2016-06-02 11:05:42 +05:30
author_id: author.id,
action: action,
note: note
2016-04-02 18:10:28 +05:30
)
end
2016-06-02 11:05:42 +05:30
2018-11-18 11:00:15 +05:30
def filter_todo_users(users, parent, target)
reject_users_without_access(users, parent, target).uniq
2017-08-17 22:00:37 +05:30
end
2018-11-18 11:00:15 +05:30
def filter_mentioned_users(parent, target, author, skip_users = [])
2017-08-17 22:00:37 +05:30
mentioned_users = target.mentioned_users(author) - skip_users
2018-11-18 11:00:15 +05:30
filter_todo_users(mentioned_users, parent, target)
2017-08-17 22:00:37 +05:30
end
2018-11-18 11:00:15 +05:30
def filter_directly_addressed_users(parent, target, author, skip_users = [])
2017-08-17 22:00:37 +05:30
directly_addressed_users = target.directly_addressed_users(author) - skip_users
2018-11-18 11:00:15 +05:30
filter_todo_users(directly_addressed_users, parent, target)
2016-06-02 11:05:42 +05:30
end
2018-11-18 11:00:15 +05:30
def reject_users_without_access(users, parent, target)
2019-09-04 21:01:54 +05:30
if target.respond_to?(:to_ability_name)
2016-11-03 12:29:30 +05:30
select_users(users, :"read_#{target.to_ability_name}", target)
2016-06-02 11:05:42 +05:30
else
2018-11-18 11:00:15 +05:30
select_users(users, :read_project, parent)
2016-06-02 11:05:42 +05:30
end
end
def select_users(users, ability, subject)
users.select do |user|
user.can?(ability.to_sym, subject)
end
end
2021-04-29 21:17:54 +05:30
def pending_todos(users, criteria = {})
PendingTodosFinder.new(users, criteria).execute
2016-06-02 11:05:42 +05:30
end
2020-11-24 15:15:51 +05:30
2023-03-04 22:38:38 +05:30
def track_todo_creation(user, issue_type, namespace, project)
2020-11-24 15:15:51 +05:30
return unless issue_type == 'incident'
2023-03-04 22:38:38 +05:30
event = "incident_management_incident_todo"
track_usage_event(event, user.id)
return unless Feature.enabled?(:route_hll_to_snowplow_phase2, namespace)
Gitlab::Tracking.event(
self.class.to_s,
event,
project: project,
namespace: namespace,
user: user,
label: 'redis_hll_counters.incident_management.incident_management_total_unique_counts_monthly',
context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event).to_context]
)
end
def attributes_for_access_request_todos(source, author, action, note = nil)
attributes = {
target_id: source.id,
target_type: source.class.polymorphic_name,
author_id: author.id,
action: action,
note: note
}
2023-03-17 16:20:25 +05:30
if source.instance_of? Project
attributes[:project_id] = source.id
attributes[:group_id] = source.group.id if source.group.present?
else
attributes[:group_id] = source.id
end
2023-03-04 22:38:38 +05:30
attributes
2020-11-24 15:15:51 +05:30
end
2016-04-02 18:10:28 +05:30
end
2019-12-04 20:38:33 +05:30
2021-06-08 01:23:25 +05:30
TodoService.prepend_mod_with('TodoService')