summaryrefslogtreecommitdiff
path: root/app/finders/autocomplete
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2018-07-30 17:45:49 +0200
committerYorick Peterse <yorickpeterse@gmail.com>2018-08-20 13:53:00 +0200
commit6f3c490107f5fa7dd00bce0bbd89e4a0fa4d6389 (patch)
tree574fb9d604b8269846876430b9761f397e391d2b /app/finders/autocomplete
parent0a73c1c5833ac5a86289418e93b5d50aa8e89cbd (diff)
downloadgitlab-ce-6f3c490107f5fa7dd00bce0bbd89e4a0fa4d6389.tar.gz
Refactor AutocompleteControllerdefine-abstraction-levels
This refactors the AutocompleteController according to the guidelines and boundaries discussed in https://gitlab.com/gitlab-org/gitlab-ce/issues/49653. Specifically, ActiveRecord logic is moved to different finders, which are then used in the controller. View logic in turn is moved to presenters, instead of directly using ActiveRecord's "to_json" method. The finder MoveToProjectFinder is also adjusted according to the abstraction guidelines and boundaries, resulting in a much more simple finder. By using finders (and other abstractions) more actively, we can push a lot of logic out of the controller. We also remove the need for various "before_action" hooks, though this could be achieved without using finders as well. The various finders related to AutcompleteController have also been moved into a namespace. This removes the need for calling everything "AutocompleteSmurfFinder", instead you can use "Autocomplete::SmurfFinder".
Diffstat (limited to 'app/finders/autocomplete')
-rw-r--r--app/finders/autocomplete/group_finder.rb37
-rw-r--r--app/finders/autocomplete/move_to_project_finder.rb35
-rw-r--r--app/finders/autocomplete/project_finder.rb35
-rw-r--r--app/finders/autocomplete/users_finder.rb85
4 files changed, 192 insertions, 0 deletions
diff --git a/app/finders/autocomplete/group_finder.rb b/app/finders/autocomplete/group_finder.rb
new file mode 100644
index 00000000000..dd97ac4c817
--- /dev/null
+++ b/app/finders/autocomplete/group_finder.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ # Finder for retrieving a group to use for autocomplete data sources.
+ class GroupFinder
+ attr_reader :current_user, :project, :group_id
+
+ # current_user - The currently logged in user, if any.
+ # project - The Project (if any) to use for the autocomplete data sources.
+ # params - A Hash containing parameters to use for finding the project.
+ #
+ # The following parameters are supported:
+ #
+ # * group_id: The ID of the group to find.
+ def initialize(current_user = nil, project = nil, params = {})
+ @current_user = current_user
+ @project = project
+ @group_id = params[:group_id]
+ end
+
+ # Attempts to find a Group based on the current group ID.
+ def execute
+ return unless project.blank? && group_id.present?
+
+ group = Group.find(group_id)
+
+ # This removes the need for using `return render_404` and similar patterns
+ # in controllers that use this finder.
+ unless Ability.allowed?(current_user, :read_group, group)
+ raise ActiveRecord::RecordNotFound
+ .new("Could not find a Group with ID #{group_id}")
+ end
+
+ group
+ end
+ end
+end
diff --git a/app/finders/autocomplete/move_to_project_finder.rb b/app/finders/autocomplete/move_to_project_finder.rb
new file mode 100644
index 00000000000..edaf74c5f92
--- /dev/null
+++ b/app/finders/autocomplete/move_to_project_finder.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ # Finder that retrieves a list of projects that an issue can be moved to.
+ class MoveToProjectFinder
+ attr_reader :current_user, :search, :project_id, :offset_id
+
+ # current_user - The User object of the user that wants to view the list of
+ # projects.
+ #
+ # params - A Hash containing additional parameters to set.
+ #
+ # The following parameters can be set (as Symbols):
+ #
+ # * search: An optional search query to apply to the list of projects.
+ # * project_id: The ID of a project to exclude from the returned relation.
+ # * offset_id: The ID of a project to use for pagination. When given, only
+ # projects with a lower ID are included in the list.
+ def initialize(current_user, params = {})
+ @current_user = current_user
+ @search = params[:search]
+ @project_id = params[:project_id]
+ @offset_id = params[:offset_id]
+ end
+
+ def execute
+ current_user
+ .projects_where_can_admin_issues
+ .optionally_search(search)
+ .excluding_project(project_id)
+ .paginate_in_descending_order_using_id(before: offset_id)
+ .eager_load_namespace_and_owner
+ end
+ end
+end
diff --git a/app/finders/autocomplete/project_finder.rb b/app/finders/autocomplete/project_finder.rb
new file mode 100644
index 00000000000..3a4696f4c2e
--- /dev/null
+++ b/app/finders/autocomplete/project_finder.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ # Finder for retrieving a project to use for autocomplete data sources.
+ class ProjectFinder
+ attr_reader :current_user, :project_id
+
+ # current_user - The currently logged in user, if any.
+ # params - A Hash containing parameters to use for finding the project.
+ #
+ # The following parameters are supported:
+ #
+ # * project_id: The ID of the project to find.
+ def initialize(current_user = nil, params = {})
+ @current_user = current_user
+ @project_id = params[:project_id]
+ end
+
+ # Attempts to find a Project based on the current project ID.
+ def execute
+ return if project_id.blank?
+
+ project = Project.find(project_id)
+
+ # This removes the need for using `return render_404` and similar patterns
+ # in controllers that use this finder.
+ unless Ability.allowed?(current_user, :read_project, project)
+ raise ActiveRecord::RecordNotFound
+ .new("Could not find a Project with ID #{project_id}")
+ end
+
+ project
+ end
+ end
+end
diff --git a/app/finders/autocomplete/users_finder.rb b/app/finders/autocomplete/users_finder.rb
new file mode 100644
index 00000000000..b2557469079
--- /dev/null
+++ b/app/finders/autocomplete/users_finder.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Autocomplete
+ class UsersFinder
+ # The number of users to display in the results is hardcoded to 20, and
+ # pagination is not supported. This ensures that performance remains
+ # consistent and removes the need for implementing keyset pagination to
+ # ensure good performance.
+ LIMIT = 20
+
+ attr_reader :current_user, :project, :group, :search, :skip_users,
+ :author_id, :todo_filter, :todo_state_filter,
+ :filter_by_current_user
+
+ def initialize(params:, current_user:, project:, group:)
+ @current_user = current_user
+ @project = project
+ @group = group
+ @search = params[:search]
+ @skip_users = params[:skip_users]
+ @author_id = params[:author_id]
+ @todo_filter = params[:todo_filter]
+ @todo_state_filter = params[:todo_state_filter]
+ @filter_by_current_user = params[:current_user]
+ end
+
+ def execute
+ items = limited_users
+
+ if search.blank?
+ # Include current user if available to filter by "Me"
+ items.unshift(current_user) if prepend_current_user?
+
+ if prepend_author? && (author = User.find_by_id(author_id))
+ items.unshift(author)
+ end
+ end
+
+ items.uniq
+ end
+
+ private
+
+ # Returns the users based on the input parameters, as an Array.
+ #
+ # This method is separate so it is easier to extend in EE.
+ def limited_users
+ # When changing the order of these method calls, make sure that
+ # reorder_by_name() is called _before_ optionally_search(), otherwise
+ # reorder_by_name will break the ORDER BY applied in optionally_search().
+ find_users
+ .active
+ .reorder_by_name
+ .optionally_search(search)
+ .where_not_in(skip_users)
+ .limit_to_todo_authors(
+ user: current_user,
+ with_todos: todo_filter,
+ todo_state: todo_state_filter
+ )
+ .limit(LIMIT)
+ .to_a
+ end
+
+ def prepend_current_user?
+ filter_by_current_user.present? && current_user
+ end
+
+ def prepend_author?
+ author_id.present? && current_user
+ end
+
+ def find_users
+ if project
+ project.authorized_users.union_with_user(author_id)
+ elsif group
+ group.users_with_parents
+ elsif current_user
+ User.all
+ else
+ User.none
+ end
+ end
+ end
+end