2020-03-30 17:08:01 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
class IssuableFinder
|
|
|
|
class Params < SimpleDelegator
|
|
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
|
|
|
|
# This is used as a common filter for None / Any
|
|
|
|
FILTER_NONE = 'none'
|
|
|
|
FILTER_ANY = 'any'
|
|
|
|
|
|
|
|
# This is used in unassigning users
|
|
|
|
NONE = '0'
|
|
|
|
|
|
|
|
alias_method :params, :__getobj__
|
|
|
|
|
|
|
|
attr_accessor :current_user, :klass
|
|
|
|
|
|
|
|
def initialize(params, current_user, klass)
|
|
|
|
@current_user = current_user
|
|
|
|
@klass = klass
|
|
|
|
# We turn the params into a HashWithIndifferentAccess. We must use #to_h first because sometimes
|
|
|
|
# we get ActionController::Params and IssuableFinder::Params objects here.
|
|
|
|
super(params.to_h.with_indifferent_access)
|
|
|
|
end
|
|
|
|
|
|
|
|
def present?
|
|
|
|
params.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def author_id?
|
|
|
|
params[:author_id].present? && params[:author_id] != NONE
|
|
|
|
end
|
|
|
|
|
|
|
|
def author_username?
|
|
|
|
params[:author_username].present? && params[:author_username] != NONE
|
|
|
|
end
|
|
|
|
|
|
|
|
def no_author?
|
|
|
|
# author_id takes precedence over author_username
|
|
|
|
params[:author_id] == NONE || params[:author_username] == NONE
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_no_assignee?
|
|
|
|
params[:assignee_id].to_s.downcase == FILTER_NONE
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_any_assignee?
|
|
|
|
params[:assignee_id].to_s.downcase == FILTER_ANY
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_no_label?
|
|
|
|
downcased = label_names.map(&:downcase)
|
|
|
|
|
|
|
|
downcased.include?(FILTER_NONE)
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_any_label?
|
|
|
|
label_names.map(&:downcase).include?(FILTER_ANY)
|
|
|
|
end
|
|
|
|
|
|
|
|
def labels?
|
|
|
|
params[:label_name].present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def milestones?
|
|
|
|
params[:milestone_title].present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_no_milestone?
|
|
|
|
# Accepts `No Milestone` for compatibility
|
|
|
|
params[:milestone_title].to_s.downcase == FILTER_NONE || params[:milestone_title] == Milestone::None.title
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_any_milestone?
|
|
|
|
# Accepts `Any Milestone` for compatibility
|
|
|
|
params[:milestone_title].to_s.downcase == FILTER_ANY || params[:milestone_title] == Milestone::Any.title
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_upcoming_milestone?
|
|
|
|
params[:milestone_title] == Milestone::Upcoming.name
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_started_milestone?
|
|
|
|
params[:milestone_title] == Milestone::Started.name
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_no_release?
|
|
|
|
params[:release_tag].to_s.downcase == FILTER_NONE
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_any_release?
|
|
|
|
params[:release_tag].to_s.downcase == FILTER_ANY
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_no_reaction?
|
|
|
|
params[:my_reaction_emoji].to_s.downcase == FILTER_NONE
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_by_any_reaction?
|
|
|
|
params[:my_reaction_emoji].to_s.downcase == FILTER_ANY
|
|
|
|
end
|
|
|
|
|
|
|
|
def releases?
|
|
|
|
params[:release_tag].present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def project?
|
2020-06-02 11:08:24 -04:00
|
|
|
project_id.present?
|
2020-03-30 17:08:01 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def group
|
|
|
|
strong_memoize(:group) do
|
2020-06-29 08:09:20 -04:00
|
|
|
if params[:group_id].is_a?(Group)
|
|
|
|
params[:group_id]
|
|
|
|
elsif params[:group_id].present?
|
2020-03-30 17:08:01 -04:00
|
|
|
Group.find(params[:group_id])
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def related_groups
|
|
|
|
if project? && project&.group && Ability.allowed?(current_user, :read_group, project.group)
|
|
|
|
project.group.self_and_ancestors
|
|
|
|
elsif group
|
|
|
|
[group]
|
|
|
|
elsif current_user
|
|
|
|
Gitlab::ObjectHierarchy.new(current_user.authorized_groups, current_user.groups).all_objects
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def project
|
|
|
|
strong_memoize(:project) do
|
2020-06-02 11:08:24 -04:00
|
|
|
next nil unless project?
|
2020-05-06 23:09:46 -04:00
|
|
|
|
2020-06-02 11:08:24 -04:00
|
|
|
project = project_id.is_a?(Project) ? project_id : Project.find(project_id)
|
2020-03-30 17:08:01 -04:00
|
|
|
project = nil unless Ability.allowed?(current_user, :"read_#{klass.to_ability_name}", project)
|
|
|
|
|
|
|
|
project
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-02 11:08:24 -04:00
|
|
|
def project_id
|
|
|
|
params[:project_id]
|
|
|
|
end
|
|
|
|
|
2020-03-30 17:08:01 -04:00
|
|
|
def projects
|
|
|
|
strong_memoize(:projects) do
|
|
|
|
next [project] if project?
|
|
|
|
|
|
|
|
projects =
|
|
|
|
if current_user && params[:authorized_only].presence && !current_user_related?
|
|
|
|
current_user.authorized_projects(min_access_level)
|
|
|
|
else
|
|
|
|
projects_public_or_visible_to_user
|
|
|
|
end
|
|
|
|
|
|
|
|
projects.with_feature_available_for_user(klass, current_user).reorder(nil) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
def author
|
|
|
|
strong_memoize(:author) do
|
|
|
|
if author_id?
|
|
|
|
User.find_by(id: params[:author_id])
|
|
|
|
elsif author_username?
|
|
|
|
User.find_by_username(params[:author_username])
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
|
|
|
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
def assignees
|
|
|
|
strong_memoize(:assignees) do
|
|
|
|
if assignee_id?
|
|
|
|
User.where(id: params[:assignee_id])
|
|
|
|
elsif assignee_username?
|
|
|
|
User.where(username: params[:assignee_username])
|
|
|
|
else
|
|
|
|
User.none
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
|
|
|
|
|
|
def assignee
|
|
|
|
assignees.first
|
|
|
|
end
|
|
|
|
|
|
|
|
def label_names
|
|
|
|
if labels?
|
|
|
|
params[:label_name].is_a?(String) ? params[:label_name].split(',') : params[:label_name]
|
|
|
|
else
|
|
|
|
[]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def labels
|
|
|
|
strong_memoize(:labels) do
|
|
|
|
if labels? && !filter_by_no_label?
|
|
|
|
LabelsFinder.new(current_user, project_ids: projects, title: label_names).execute(skip_authorization: true) # rubocop: disable CodeReuse/Finder
|
|
|
|
else
|
|
|
|
Label.none
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def milestones
|
|
|
|
strong_memoize(:milestones) do
|
|
|
|
if milestones?
|
|
|
|
if project?
|
|
|
|
group_id = project.group&.id
|
|
|
|
project_id = project.id
|
|
|
|
end
|
|
|
|
|
|
|
|
group_id = group.id if group
|
|
|
|
|
|
|
|
search_params =
|
|
|
|
{ title: params[:milestone_title], project_ids: project_id, group_ids: group_id }
|
|
|
|
|
|
|
|
MilestonesFinder.new(search_params).execute # rubocop: disable CodeReuse/Finder
|
|
|
|
else
|
|
|
|
Milestone.none
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def current_user_related?
|
|
|
|
scope = params[:scope]
|
|
|
|
scope == 'created_by_me' || scope == 'authored' || scope == 'assigned_to_me'
|
|
|
|
end
|
|
|
|
|
|
|
|
def find_group_projects
|
|
|
|
return Project.none unless group
|
|
|
|
|
|
|
|
if params[:include_subgroups]
|
|
|
|
Project.where(namespace_id: group.self_and_descendants) # rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
else
|
|
|
|
group.projects
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# We use Hash#merge in a few places, so let's support it
|
|
|
|
def merge(other)
|
|
|
|
self.class.new(params.merge(other), current_user, klass)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Just for symmetry, and in case someone tries to use it
|
|
|
|
def merge!(other)
|
|
|
|
params.merge!(other)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def projects_public_or_visible_to_user
|
|
|
|
projects =
|
|
|
|
if group
|
|
|
|
if params[:projects]
|
|
|
|
find_group_projects.id_in(params[:projects])
|
|
|
|
else
|
|
|
|
find_group_projects
|
|
|
|
end
|
|
|
|
elsif params[:projects]
|
|
|
|
Project.id_in(params[:projects])
|
|
|
|
else
|
|
|
|
Project
|
|
|
|
end
|
|
|
|
|
|
|
|
projects.public_or_visible_to_user(current_user, min_access_level)
|
|
|
|
end
|
|
|
|
|
|
|
|
def min_access_level
|
|
|
|
ProjectFeature.required_minimum_access_level(klass)
|
|
|
|
end
|
|
|
|
|
|
|
|
def method_missing(method_name, *args, &block)
|
|
|
|
if method_name[-1] == '?'
|
|
|
|
params[method_name[0..-2].to_sym].present?
|
|
|
|
else
|
|
|
|
super
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def respond_to_missing?(method_name, include_private = false)
|
|
|
|
method_name[-1] == '?'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|