summaryrefslogtreecommitdiff
path: root/app/finders
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
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')
-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
-rw-r--r--app/finders/autocomplete_users_finder.rb68
-rw-r--r--app/finders/awarded_emoji_finder.rb21
-rw-r--r--app/finders/move_to_project_finder.rb21
-rw-r--r--app/finders/user_finder.rb26
8 files changed, 239 insertions, 89 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
diff --git a/app/finders/autocomplete_users_finder.rb b/app/finders/autocomplete_users_finder.rb
deleted file mode 100644
index e8a03947f59..00000000000
--- a/app/finders/autocomplete_users_finder.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-class AutocompleteUsersFinder
- # 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, :params
-
- 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]
- @params = params
- end
-
- def execute
- items = find_users
- items = items.active
- items = items.reorder(:name)
- items = items.search(search) if search.present?
- items = items.where.not(id: skip_users) if skip_users.present?
- items = items.limit(LIMIT)
-
- if params[:todo_filter].present? && current_user
- items = items.todo_authors(current_user.id, params[:todo_state_filter])
- end
-
- if search.blank?
- # Include current user if available to filter by "Me"
- if params[:current_user].present? && current_user
- items = [current_user, *items].uniq
- end
-
- if author_id.present? && current_user
- author = User.find_by_id(author_id)
- items = [author, *items].uniq if author
- end
- end
-
- items
- end
-
- private
-
- def find_users
- return users_from_project if project
- return group.users_with_parents if group
- return User.all if current_user
-
- User.none
- end
-
- def users_from_project
- if author_id.present?
- union = Gitlab::SQL::Union
- .new([project.authorized_users, User.where(id: author_id)])
-
- User.from("(#{union.to_sql}) #{User.table_name}")
- else
- project.authorized_users
- end
- end
-end
diff --git a/app/finders/awarded_emoji_finder.rb b/app/finders/awarded_emoji_finder.rb
new file mode 100644
index 00000000000..f0cc17f3b26
--- /dev/null
+++ b/app/finders/awarded_emoji_finder.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Class for retrieving information about emoji awarded _by_ a particular user.
+class AwardedEmojiFinder
+ attr_reader :current_user
+
+ # current_user - The User to generate the data for.
+ def initialize(current_user = nil)
+ @current_user = current_user
+ end
+
+ def execute
+ return [] unless current_user
+
+ # We want the resulting data set to be an Array containing the emoji names
+ # in descending order, based on how often they were awarded.
+ AwardEmoji
+ .award_counts_for_user(current_user)
+ .map { |name, _| { name: name } }
+ end
+end
diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb
deleted file mode 100644
index 038d5565a1e..00000000000
--- a/app/finders/move_to_project_finder.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-class MoveToProjectFinder
- PAGE_SIZE = 50
-
- def initialize(user)
- @user = user
- end
-
- def execute(from_project, search: nil, offset_id: nil)
- projects = @user.projects_where_can_admin_issues
- projects = projects.search(search) if search.present?
- projects = projects.excluding_project(from_project)
- projects = projects.order_id_desc
-
- # infinite scroll using offset
- projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
- projects = projects.limit(PAGE_SIZE)
-
- # to ask for Project#name_with_namespace
- projects.includes(namespace: :owner)
- end
-end
diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb
new file mode 100644
index 00000000000..484a93c9873
--- /dev/null
+++ b/app/finders/user_finder.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# A simple finding for obtaining a single User.
+#
+# While using `User.find_by` directly is straightforward, it can lead to a lot
+# of code duplication. Sometimes we just want to find a user by an ID, other
+# times we may want to exclude blocked user. By using this finder (and extending
+# it whenever necessary) we can keep this logic in one place.
+class UserFinder
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ # Tries to find a User, returning nil if none could be found.
+ def execute
+ User.find_by(id: params[:id])
+ end
+
+ # Tries to find a User, raising a `ActiveRecord::RecordNotFound` if it could
+ # not be found.
+ def execute!
+ User.find(params[:id])
+ end
+end