2018-09-14 05:42:05 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-07-23 23:28:12 +00:00
|
|
|
module IssuableCollections
|
|
|
|
extend ActiveSupport::Concern
|
2018-08-30 20:39:56 +00:00
|
|
|
include CookiesHelper
|
2016-07-23 23:28:12 +00:00
|
|
|
include SortingHelper
|
2017-07-08 06:21:09 +00:00
|
|
|
include Gitlab::IssuableMetadata
|
2017-11-17 12:27:16 +00:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
2016-07-23 23:28:12 +00:00
|
|
|
|
|
|
|
included do
|
2017-11-07 13:34:12 +00:00
|
|
|
helper_method :finder
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2017-11-22 07:50:36 +00:00
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
2017-11-07 13:34:12 +00:00
|
|
|
def set_issuables_index
|
2018-01-29 15:22:58 +00:00
|
|
|
@issuables = issuables_collection
|
2017-09-05 09:22:37 +00:00
|
|
|
|
2018-01-29 15:22:58 +00:00
|
|
|
set_pagination
|
2017-11-07 13:34:12 +00:00
|
|
|
return if redirect_out_of_range(@total_pages)
|
2017-09-05 09:22:37 +00:00
|
|
|
|
2018-02-28 21:58:36 +00:00
|
|
|
if params[:label_name].present? && @project
|
2017-11-07 13:34:12 +00:00
|
|
|
labels_params = { project_id: @project.id, title: params[:label_name] }
|
|
|
|
@labels = LabelsFinder.new(current_user, labels_params).execute
|
2017-09-05 09:22:37 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
@users = []
|
2017-11-07 13:34:12 +00:00
|
|
|
if params[:assignee_id].present?
|
|
|
|
assignee = User.find_by_id(params[:assignee_id])
|
|
|
|
@users.push(assignee) if assignee
|
|
|
|
end
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2017-11-07 13:34:12 +00:00
|
|
|
if params[:author_id].present?
|
|
|
|
author = User.find_by_id(params[:author_id])
|
|
|
|
@users.push(author) if author
|
|
|
|
end
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
2018-01-29 15:22:58 +00:00
|
|
|
|
|
|
|
def set_pagination
|
|
|
|
return if pagination_disabled?
|
|
|
|
|
|
|
|
@issuables = @issuables.page(params[:page])
|
2019-05-21 22:46:12 +00:00
|
|
|
@issuables = per_page_for_relative_position if params[:sort] == 'relative_position'
|
2019-06-12 16:28:25 +00:00
|
|
|
@issuable_meta_data = issuable_meta_data(@issuables, collection_type, current_user)
|
2018-01-29 15:22:58 +00:00
|
|
|
@total_pages = issuable_page_count
|
|
|
|
end
|
2017-11-22 07:50:36 +00:00
|
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2018-01-29 15:22:58 +00:00
|
|
|
def pagination_disabled?
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-11-07 13:34:12 +00:00
|
|
|
def issuables_collection
|
|
|
|
finder.execute.preload(preload_for_collection)
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
2018-08-27 15:31:01 +00:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2017-11-07 13:34:12 +00:00
|
|
|
def redirect_out_of_range(total_pages)
|
2018-01-29 15:22:58 +00:00
|
|
|
return false if total_pages.nil? || total_pages.zero?
|
2017-08-24 16:17:04 +00:00
|
|
|
|
2017-11-22 07:50:36 +00:00
|
|
|
out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
2017-08-24 16:17:04 +00:00
|
|
|
|
|
|
|
if out_of_range
|
2018-04-28 10:35:16 +00:00
|
|
|
redirect_to(url_for(safe_params.merge(page: total_pages, only_path: true)))
|
2017-08-24 16:17:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
out_of_range
|
|
|
|
end
|
|
|
|
|
2017-11-07 13:34:12 +00:00
|
|
|
def issuable_page_count
|
2017-11-22 07:50:36 +00:00
|
|
|
page_count_for_relation(@issuables, finder.row_count) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
2017-08-24 16:17:04 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def page_count_for_relation(relation, row_count)
|
|
|
|
limit = relation.limit_value.to_f
|
|
|
|
|
|
|
|
return 1 if limit.zero?
|
|
|
|
|
|
|
|
(row_count.to_f / limit).ceil
|
|
|
|
end
|
|
|
|
|
2019-05-21 22:46:12 +00:00
|
|
|
# manual / relative_position sorting allows for 100 items on the page
|
|
|
|
def per_page_for_relative_position
|
|
|
|
@issuables.per(100) # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
|
|
|
end
|
|
|
|
|
2016-07-23 23:28:12 +00:00
|
|
|
def issuable_finder_for(finder_class)
|
2018-11-09 14:29:45 +00:00
|
|
|
finder_class.new(current_user, finder_options)
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
|
|
|
|
2017-11-22 07:50:36 +00:00
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
2018-11-09 14:29:45 +00:00
|
|
|
def finder_options
|
|
|
|
params[:state] = default_state if params[:state].blank?
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2018-11-09 14:29:45 +00:00
|
|
|
options = {
|
|
|
|
scope: params[:scope],
|
|
|
|
state: params[:state],
|
2019-02-25 11:00:24 +00:00
|
|
|
confidential: Gitlab::Utils.to_boolean(params[:confidential]),
|
2018-12-03 12:46:47 +00:00
|
|
|
sort: set_sort_order
|
2018-11-09 14:29:45 +00:00
|
|
|
}
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2018-11-09 14:29:45 +00:00
|
|
|
# Used by view to highlight active option
|
|
|
|
@sort = options[:sort]
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2019-06-18 07:45:47 +00:00
|
|
|
# When a user looks for an exact iid, we do not filter by search but only by iid
|
|
|
|
if params[:search] =~ /^#(?<iid>\d+)\z/
|
|
|
|
options[:iids] = Regexp.last_match[:iid]
|
|
|
|
params[:search] = nil
|
|
|
|
end
|
|
|
|
|
2016-07-23 23:28:12 +00:00
|
|
|
if @project
|
2018-11-09 14:29:45 +00:00
|
|
|
options[:project_id] = @project.id
|
Extend CTE search optimisation to projects
When we use the `search` param on an `IssuableFinder`, we can run into
issues. We have trigram indexes to support these searches. On
GitLab.com, we often see Postgres's optimiser prioritise the (global)
trigram indexes over the index on `project_id`. For group and project
searches, we know that it will be quicker to filter by `project_id`
first, as it returns fewer rows in most cases.
For group issues search, we ran into this issue previously, and went
through the following iterations:
1. Use a CTE on the project IDs as an optimisation fence. This prevents
the planner from disregarding the index on `project_id`.
Unfortunately it breaks some types of sorting, like priority and
popularity, as they sort on a joined table.
2. Use a subquery for listing issues, and a CTE for counts. The subquery
- in the case of group lists - didn't help as much as the CTE, but
was faster than not including it. We can safely use a CTE for counts
as they don't have sorting.
Now, however, we're seeing the same issue in a project context. The
subquery doesn't help at all there (it would only return one row, after
all). In an attempt to keep total code complexity under control, this
commit removes the subquery optimisation and applies the CTE
optimisation only for sorts we know that are safe.
This means that for more complicated sorts (like priority and
popularity), the search will continue to be very slow. If this is a
high-priority issue, we can consider introducing further optimisations,
but this finder is already very complicated and additional complexity
has a cost.
The group CTE optimisation is controlled by the same feature flag as
before, `attempt_group_search_optimizations`, which is enabled by
default. The new project CTE optimisation is controlled by a new feature
flag, `attempt_project_search_optimizations`, which is disabled by
default.
2019-04-03 09:46:13 +00:00
|
|
|
options[:attempt_project_search_optimizations] = true
|
2016-07-23 23:28:12 +00:00
|
|
|
elsif @group
|
2018-11-09 14:29:45 +00:00
|
|
|
options[:group_id] = @group.id
|
|
|
|
options[:include_subgroups] = true
|
2018-11-29 12:52:48 +00:00
|
|
|
options[:attempt_group_search_optimizations] = true
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
|
|
|
|
2018-11-09 14:29:45 +00:00
|
|
|
params.permit(finder_type.valid_params).merge(options)
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
2017-11-22 07:50:36 +00:00
|
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2018-11-09 14:29:45 +00:00
|
|
|
def default_state
|
|
|
|
'opened'
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
|
|
|
|
2018-12-03 12:46:47 +00:00
|
|
|
def set_sort_order
|
|
|
|
set_sort_order_from_user_preference || set_sort_order_from_cookie || default_sort_order
|
|
|
|
end
|
|
|
|
|
|
|
|
def set_sort_order_from_user_preference
|
|
|
|
return unless current_user
|
|
|
|
return unless issuable_sorting_field
|
|
|
|
|
|
|
|
user_preference = current_user.user_preference
|
|
|
|
|
|
|
|
sort_param = params[:sort]
|
|
|
|
sort_param ||= user_preference[issuable_sorting_field]
|
|
|
|
|
2018-12-12 16:15:58 +00:00
|
|
|
return sort_param if Gitlab::Database.read_only?
|
|
|
|
|
2018-12-03 12:46:47 +00:00
|
|
|
if user_preference[issuable_sorting_field] != sort_param
|
2019-01-07 00:00:48 +00:00
|
|
|
user_preference.update(issuable_sorting_field => sort_param)
|
2018-12-03 12:46:47 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
sort_param
|
|
|
|
end
|
|
|
|
|
2019-01-07 00:00:48 +00:00
|
|
|
# Implement issuable_sorting_field method on controllers
|
2018-12-03 12:46:47 +00:00
|
|
|
# to choose which column to store the sorting parameter.
|
|
|
|
def issuable_sorting_field
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
|
2016-07-23 23:28:12 +00:00
|
|
|
def set_sort_order_from_cookie
|
2018-08-30 20:39:56 +00:00
|
|
|
sort_param = params[:sort] if params[:sort].present?
|
2018-08-21 09:15:58 +00:00
|
|
|
# fallback to legacy cookie value for backward compatibility
|
2018-08-30 20:39:56 +00:00
|
|
|
sort_param ||= cookies['issuable_sort']
|
|
|
|
sort_param ||= cookies[remember_sorting_key]
|
|
|
|
|
|
|
|
sort_value = update_cookie_value(sort_param)
|
|
|
|
set_secure_cookie(remember_sorting_key, sort_value)
|
2018-11-09 14:29:45 +00:00
|
|
|
sort_value
|
2018-08-21 09:15:58 +00:00
|
|
|
end
|
2016-07-23 23:28:12 +00:00
|
|
|
|
2018-08-21 09:15:58 +00:00
|
|
|
def remember_sorting_key
|
|
|
|
@remember_sorting_key ||= "#{collection_type.downcase}_sort"
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def default_sort_order
|
|
|
|
case params[:state]
|
2017-09-23 00:46:53 +00:00
|
|
|
when 'opened', 'all' then sort_value_created_date
|
2016-07-23 23:28:12 +00:00
|
|
|
when 'merged', 'closed' then sort_value_recently_updated
|
2017-09-23 00:46:53 +00:00
|
|
|
else sort_value_created_date
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Update old values to the actual ones.
|
|
|
|
def update_cookie_value(value)
|
|
|
|
case value
|
|
|
|
when 'id_asc' then sort_value_oldest_created
|
|
|
|
when 'id_desc' then sort_value_recently_created
|
|
|
|
when 'downvotes_asc' then sort_value_popularity
|
|
|
|
when 'downvotes_desc' then sort_value_popularity
|
|
|
|
else value
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|
|
|
|
end
|
2017-11-07 13:34:12 +00:00
|
|
|
|
|
|
|
def finder
|
2018-08-21 09:15:58 +00:00
|
|
|
@finder ||= issuable_finder_for(finder_type)
|
2017-11-07 13:34:12 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def collection_type
|
2019-07-30 10:04:10 +00:00
|
|
|
@collection_type ||= if finder_type <= IssuesFinder
|
2017-11-07 13:34:12 +00:00
|
|
|
'Issue'
|
2019-07-30 10:04:10 +00:00
|
|
|
elsif finder_type <= MergeRequestsFinder
|
2017-11-07 13:34:12 +00:00
|
|
|
'MergeRequest'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-04-07 18:35:16 +00:00
|
|
|
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
2017-11-07 13:34:12 +00:00
|
|
|
def preload_for_collection
|
2019-04-07 18:35:16 +00:00
|
|
|
common_attributes = [:author, :assignees, :labels, :milestone]
|
2017-11-07 13:34:12 +00:00
|
|
|
@preload_for_collection ||= case collection_type
|
|
|
|
when 'Issue'
|
2019-04-07 18:35:16 +00:00
|
|
|
common_attributes + [:project, project: :namespace]
|
2017-11-07 13:34:12 +00:00
|
|
|
when 'MergeRequest'
|
2019-04-07 18:35:16 +00:00
|
|
|
common_attributes + [:target_project, source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits]
|
2017-11-07 13:34:12 +00:00
|
|
|
end
|
|
|
|
end
|
2019-04-07 18:35:16 +00:00
|
|
|
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
2016-07-23 23:28:12 +00:00
|
|
|
end
|