2018-09-11 15:08:34 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-09-02 08:28:27 -04:00
|
|
|
# IssuableFinder
|
2014-01-15 09:16:23 -05:00
|
|
|
#
|
|
|
|
# Used to filter Issues and MergeRequests collections by set of params
|
|
|
|
#
|
2021-01-07 10:10:33 -05:00
|
|
|
# Note: This class is NOT meant to be instantiated. Instead you should
|
|
|
|
# look at IssuesFinder or EpicsFinder, which inherit from this.
|
|
|
|
#
|
2014-01-15 09:16:23 -05:00
|
|
|
# Arguments:
|
|
|
|
# klass - actual class like Issue or MergeRequest
|
|
|
|
# current_user - which user use
|
|
|
|
# params:
|
2018-05-13 22:07:53 -04:00
|
|
|
# scope: 'created_by_me' or 'assigned_to_me' or 'all'
|
2018-06-26 15:30:29 -04:00
|
|
|
# state: 'opened' or 'closed' or 'locked' or 'all'
|
2014-01-15 09:16:23 -05:00
|
|
|
# group_id: integer
|
|
|
|
# project_id: integer
|
2021-07-29 05:08:46 -04:00
|
|
|
# milestone_title: string (cannot be simultaneously used with milestone_wildcard_id)
|
|
|
|
# milestone_wildcard_id: 'none', 'any', 'upcoming', 'started' (cannot be simultaneously used with milestone_title)
|
2019-11-22 13:06:00 -05:00
|
|
|
# release_tag: string
|
2017-07-20 16:44:48 -04:00
|
|
|
# author_id: integer
|
2018-11-09 09:29:45 -05:00
|
|
|
# author_username: string
|
2018-10-25 03:02:28 -04:00
|
|
|
# assignee_id: integer or 'None' or 'Any'
|
2018-11-09 09:29:45 -05:00
|
|
|
# assignee_username: string
|
2014-01-15 09:16:23 -05:00
|
|
|
# search: string
|
2019-02-05 03:32:27 -05:00
|
|
|
# in: 'title', 'description', or a string joining them with comma
|
2014-01-15 09:16:23 -05:00
|
|
|
# label_name: string
|
|
|
|
# sort: string
|
2016-11-27 05:33:15 -05:00
|
|
|
# non_archived: boolean
|
2017-02-17 13:28:32 -05:00
|
|
|
# iids: integer[]
|
2017-08-30 03:48:55 -04:00
|
|
|
# my_reaction_emoji: string
|
2018-02-28 06:16:29 -05:00
|
|
|
# created_after: datetime
|
|
|
|
# created_before: datetime
|
|
|
|
# updated_after: datetime
|
|
|
|
# updated_before: datetime
|
2018-11-29 07:52:48 -05:00
|
|
|
# attempt_group_search_optimizations: boolean
|
2019-05-07 07:08:25 -04:00
|
|
|
# attempt_project_search_optimizations: boolean
|
2021-12-03 01:10:53 -05:00
|
|
|
# crm_contact_id: integer
|
2021-12-06 19:14:07 -05:00
|
|
|
# crm_organization_id: integer
|
2014-01-15 09:16:23 -05:00
|
|
|
#
|
2014-09-02 08:28:27 -04:00
|
|
|
class IssuableFinder
|
2017-12-11 09:21:06 -05:00
|
|
|
prepend FinderWithCrossProjectAccess
|
|
|
|
include FinderMethods
|
2017-07-07 12:31:50 -04:00
|
|
|
include CreatedAtFilter
|
2018-11-29 07:52:48 -05:00
|
|
|
include Gitlab::Utils::StrongMemoize
|
2017-07-11 08:19:43 -04:00
|
|
|
|
2020-03-30 17:08:01 -04:00
|
|
|
requires_cross_project_access unless: -> { params.project? }
|
2015-03-26 21:56:42 -04:00
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
NEGATABLE_PARAMS_HELPER_KEYS = %i[project_id scope status include_subgroups].freeze
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2014-02-25 12:15:08 -05:00
|
|
|
attr_accessor :current_user, :params
|
2021-02-22 10:10:48 -05:00
|
|
|
attr_reader :original_params
|
2020-09-18 08:09:50 -04:00
|
|
|
attr_writer :parent
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2021-05-26 17:10:49 -04:00
|
|
|
delegate(*%i[milestones], to: :params)
|
2020-03-30 17:08:01 -04:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
class << self
|
|
|
|
def scalar_params
|
|
|
|
@scalar_params ||= %i[
|
2018-02-20 07:33:49 -05:00
|
|
|
assignee_id
|
|
|
|
assignee_username
|
|
|
|
author_id
|
|
|
|
author_username
|
2021-12-03 01:10:53 -05:00
|
|
|
crm_contact_id
|
2021-12-06 19:14:07 -05:00
|
|
|
crm_organization_id
|
2019-04-19 02:50:54 -04:00
|
|
|
label_name
|
2018-02-20 07:33:49 -05:00
|
|
|
milestone_title
|
2019-11-22 13:06:00 -05:00
|
|
|
release_tag
|
2018-02-20 07:33:49 -05:00
|
|
|
my_reaction_emoji
|
|
|
|
search
|
2019-01-13 11:24:31 -05:00
|
|
|
in
|
2018-02-20 07:33:49 -05:00
|
|
|
]
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
2018-02-20 07:33:49 -05:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
def array_params
|
|
|
|
@array_params ||= { label_name: [], assignee_username: [] }
|
|
|
|
end
|
|
|
|
|
|
|
|
# This should not be used in controller strong params!
|
|
|
|
def negatable_scalar_params
|
2020-05-06 23:09:46 -04:00
|
|
|
@negatable_scalar_params ||= scalar_params - %i[search in]
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
# This should not be used in controller strong params!
|
|
|
|
def negatable_array_params
|
|
|
|
@negatable_array_params ||= array_params.keys.append(:iids)
|
|
|
|
end
|
2018-02-20 07:33:49 -05:00
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
# This should not be used in controller strong params!
|
|
|
|
def negatable_params
|
|
|
|
@negatable_params ||= negatable_scalar_params + negatable_array_params
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid_params
|
2021-02-22 10:10:48 -05:00
|
|
|
@valid_params ||= scalar_params + [array_params.merge(or: {}, not: {})]
|
2019-09-17 08:06:48 -04:00
|
|
|
end
|
2018-02-20 07:33:49 -05:00
|
|
|
end
|
|
|
|
|
2020-03-30 17:08:01 -04:00
|
|
|
def params_class
|
|
|
|
IssuableFinder::Params
|
|
|
|
end
|
|
|
|
|
2021-01-07 10:10:33 -05:00
|
|
|
def klass
|
|
|
|
raise NotImplementedError
|
|
|
|
end
|
|
|
|
|
2016-11-22 05:25:04 -05:00
|
|
|
def initialize(current_user, params = {})
|
2014-01-15 09:16:23 -05:00
|
|
|
@current_user = current_user
|
2021-02-22 10:10:48 -05:00
|
|
|
@original_params = params
|
2020-03-30 17:08:01 -04:00
|
|
|
@params = params_class.new(params, current_user, klass)
|
2015-05-25 07:36:28 -04:00
|
|
|
end
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2015-05-25 07:36:28 -04:00
|
|
|
def execute
|
2014-02-03 10:02:44 -05:00
|
|
|
items = init_collection
|
2018-02-20 07:33:49 -05:00
|
|
|
items = filter_items(items)
|
|
|
|
|
2019-09-17 08:06:48 -04:00
|
|
|
# Let's see if we have to negate anything
|
2020-10-14 08:08:58 -04:00
|
|
|
items = filter_negated_items(items) if should_filter_negated_args?
|
2019-09-17 08:06:48 -04:00
|
|
|
|
2019-02-19 15:51:11 -05:00
|
|
|
# This has to be last as we use a CTE as an optimization fence
|
2020-09-03 02:08:22 -04:00
|
|
|
# for counts by passing the force_cte param and passing the
|
|
|
|
# attempt_group_search_optimizations param
|
2018-06-05 13:31:32 -04:00
|
|
|
# https://www.postgresql.org/docs/current/static/queries-with.html
|
|
|
|
items = by_search(items)
|
2018-02-20 07:33:49 -05:00
|
|
|
|
2021-04-19 11:09:08 -04:00
|
|
|
sort(items)
|
2018-02-20 07:33:49 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def filter_items(items)
|
2021-03-23 05:09:17 -04:00
|
|
|
# Selection by group is already covered by `by_project` and `projects` for project-based issuables
|
|
|
|
# Group-based issuables have their own group filter methods
|
2018-06-05 13:31:32 -04:00
|
|
|
items = by_project(items)
|
2014-02-03 10:02:44 -05:00
|
|
|
items = by_scope(items)
|
2017-07-07 12:31:50 -04:00
|
|
|
items = by_created_at(items)
|
2018-02-28 06:16:29 -05:00
|
|
|
items = by_updated_at(items)
|
2019-01-17 12:49:07 -05:00
|
|
|
items = by_closed_at(items)
|
2014-01-15 09:16:23 -05:00
|
|
|
items = by_state(items)
|
|
|
|
items = by_assignee(items)
|
2014-12-05 10:13:07 -05:00
|
|
|
items = by_author(items)
|
2016-11-27 05:33:15 -05:00
|
|
|
items = by_non_archived(items)
|
2017-02-17 13:28:32 -05:00
|
|
|
items = by_iids(items)
|
2017-02-07 08:15:07 -05:00
|
|
|
items = by_milestone(items)
|
2019-11-22 13:06:00 -05:00
|
|
|
items = by_release(items)
|
2017-02-07 08:15:07 -05:00
|
|
|
items = by_label(items)
|
2021-12-03 01:10:53 -05:00
|
|
|
items = by_my_reaction_emoji(items)
|
2021-12-06 19:14:07 -05:00
|
|
|
items = by_crm_contact(items)
|
|
|
|
by_crm_organization(items)
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|
|
|
|
|
2020-10-14 08:08:58 -04:00
|
|
|
def should_filter_negated_args?
|
2020-05-06 23:09:46 -04:00
|
|
|
# API endpoints send in `nil` values so we test if there are any non-nil
|
2020-10-14 08:08:58 -04:00
|
|
|
not_params.present? && not_params.values.any?
|
|
|
|
end
|
2020-05-06 23:09:46 -04:00
|
|
|
|
2020-10-14 08:08:58 -04:00
|
|
|
# Negates all params found in `negatable_params`
|
|
|
|
def filter_negated_items(items)
|
2020-05-06 23:09:46 -04:00
|
|
|
items = by_negated_milestone(items)
|
|
|
|
items = by_negated_release(items)
|
|
|
|
items = by_negated_my_reaction_emoji(items)
|
|
|
|
by_negated_iids(items)
|
|
|
|
end
|
|
|
|
|
2017-08-24 12:17:04 -04:00
|
|
|
def row_count
|
2020-10-14 14:08:47 -04:00
|
|
|
Gitlab::IssuablesCountForState
|
2020-11-02 13:09:03 -05:00
|
|
|
.new(self, nil, fast_fail: true)
|
2020-10-14 14:08:47 -04:00
|
|
|
.for_state_or_opened(params[:state])
|
2017-08-24 12:17:04 -04:00
|
|
|
end
|
|
|
|
|
2016-11-25 08:57:56 -05:00
|
|
|
# We often get counts for each state by running a query per state, and
|
|
|
|
# counting those results. This is typically slower than running one query
|
|
|
|
# (even if that query is slower than any of the individual state queries) and
|
|
|
|
# grouping and counting within that query.
|
|
|
|
#
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2016-11-25 08:57:56 -05:00
|
|
|
def count_by_state
|
2019-02-19 15:51:11 -05:00
|
|
|
count_params = params.merge(state: nil, sort: nil, force_cte: true)
|
2016-11-25 08:57:56 -05:00
|
|
|
finder = self.class.new(current_user, count_params)
|
2019-02-19 15:51:11 -05:00
|
|
|
|
2021-09-16 14:11:32 -04:00
|
|
|
state_counts = finder
|
|
|
|
.execute
|
|
|
|
.reorder(nil)
|
|
|
|
.group(:state_id)
|
|
|
|
.count
|
|
|
|
|
2016-11-25 08:57:56 -05:00
|
|
|
counts = Hash.new(0)
|
|
|
|
|
2021-09-16 14:11:32 -04:00
|
|
|
state_counts.each do |key, value|
|
|
|
|
counts[count_key(key)] += value
|
2016-11-25 08:57:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
counts[:all] = counts.values.sum
|
2018-07-27 06:13:36 -04:00
|
|
|
counts.with_indifferent_access
|
2016-11-25 08:57:56 -05:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2016-11-25 08:57:56 -05:00
|
|
|
|
2015-05-25 07:36:28 -04:00
|
|
|
def search
|
|
|
|
params[:search].presence
|
|
|
|
end
|
|
|
|
|
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 05:46:13 -04:00
|
|
|
def use_cte_for_search?
|
|
|
|
strong_memoize(:use_cte_for_search) do
|
|
|
|
next false unless search
|
2021-09-29 11:11:47 -04:00
|
|
|
next false unless default_or_simple_sort?
|
2018-11-29 07:52:48 -05:00
|
|
|
|
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 05:46:13 -04:00
|
|
|
attempt_group_search_optimizations? || attempt_project_search_optimizations?
|
2018-11-29 07:52:48 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-18 08:09:50 -04:00
|
|
|
def parent_param=(obj)
|
|
|
|
@parent = obj
|
|
|
|
params[parent_param] = parent if parent
|
|
|
|
end
|
|
|
|
|
|
|
|
def parent_param
|
|
|
|
case parent
|
|
|
|
when Project
|
|
|
|
:project_id
|
|
|
|
when Group
|
|
|
|
:group_id
|
|
|
|
else
|
|
|
|
raise "Unexpected parent: #{parent.class}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-01-15 09:16:23 -05:00
|
|
|
private
|
|
|
|
|
2020-09-18 08:09:50 -04:00
|
|
|
attr_reader :parent
|
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
def not_params
|
|
|
|
strong_memoize(:not_params) do
|
|
|
|
params_class.new(params[:not].dup, current_user, klass).tap do |not_params|
|
|
|
|
next unless not_params.present?
|
|
|
|
|
|
|
|
# These are "helper" params that modify the results, like :in and :search. They usually come in at the top-level
|
|
|
|
# params, but if they do come in inside the `:not` params, the inner ones should take precedence.
|
2021-04-07 14:09:45 -04:00
|
|
|
not_helpers = params.slice(*NEGATABLE_PARAMS_HELPER_KEYS).merge(params[:not].to_h.slice(*NEGATABLE_PARAMS_HELPER_KEYS))
|
2020-05-06 23:09:46 -04:00
|
|
|
not_helpers.each do |key, value|
|
|
|
|
not_params[key] = value unless not_params[key].present?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-02-19 15:51:11 -05:00
|
|
|
def force_cte?
|
|
|
|
!!params[:force_cte]
|
|
|
|
end
|
|
|
|
|
2014-02-03 10:02:44 -05:00
|
|
|
def init_collection
|
2015-10-14 06:23:49 -04:00
|
|
|
klass.all
|
2014-02-03 10:02:44 -05:00
|
|
|
end
|
|
|
|
|
2021-09-29 11:11:47 -04:00
|
|
|
def default_or_simple_sort?
|
|
|
|
params[:sort].blank? || params[:sort].to_s.in?(klass.simple_sorts.keys)
|
|
|
|
end
|
|
|
|
|
2018-11-29 07:52:48 -05:00
|
|
|
def attempt_group_search_optimizations?
|
2020-09-03 02:08:22 -04:00
|
|
|
params[:attempt_group_search_optimizations]
|
2018-11-29 07:52:48 -05:00
|
|
|
end
|
|
|
|
|
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 05:46:13 -04:00
|
|
|
def attempt_project_search_optimizations?
|
2020-09-08 05:08:31 -04:00
|
|
|
params[:attempt_project_search_optimizations]
|
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 05:46:13 -04:00
|
|
|
end
|
|
|
|
|
2018-10-01 07:45:15 -04:00
|
|
|
def count_key(value)
|
2019-11-08 10:06:21 -05:00
|
|
|
# value may be an array if the finder used in `count_by_state` added an
|
|
|
|
# additional `group by`. Anyway we are sure that state will be always the
|
|
|
|
# last item because it's added as the last one to the query.
|
2019-10-18 07:11:44 -04:00
|
|
|
value = Array(value).last
|
|
|
|
klass.available_states.key(value)
|
2018-10-01 07:45:15 -04:00
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2014-02-03 10:02:44 -05:00
|
|
|
def by_scope(items)
|
2020-03-30 17:08:01 -04:00
|
|
|
return items.none if params.current_user_related? && !current_user
|
2017-09-18 13:29:17 -04:00
|
|
|
|
2016-03-21 19:09:20 -04:00
|
|
|
case params[:scope]
|
2018-05-13 22:07:53 -04:00
|
|
|
when 'created_by_me', 'authored'
|
2014-02-10 08:04:52 -05:00
|
|
|
items.where(author_id: current_user.id)
|
2018-05-13 22:07:53 -04:00
|
|
|
when 'assigned_to_me'
|
2017-05-04 08:11:15 -04:00
|
|
|
items.assigned_to(current_user)
|
2014-01-15 09:16:23 -05:00
|
|
|
else
|
2016-03-21 19:09:20 -04:00
|
|
|
items
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2018-02-28 06:16:29 -05:00
|
|
|
def by_updated_at(items)
|
|
|
|
items = items.updated_after(params[:updated_after]) if params[:updated_after].present?
|
|
|
|
items = items.updated_before(params[:updated_before]) if params[:updated_before].present?
|
|
|
|
|
|
|
|
items
|
|
|
|
end
|
|
|
|
|
2019-01-17 12:49:07 -05:00
|
|
|
def by_closed_at(items)
|
|
|
|
items = items.closed_after(params[:closed_after]) if params[:closed_after].present?
|
|
|
|
items = items.closed_before(params[:closed_before]) if params[:closed_before].present?
|
|
|
|
|
|
|
|
items
|
|
|
|
end
|
|
|
|
|
2014-01-15 09:16:23 -05:00
|
|
|
def by_state(items)
|
2016-11-30 03:11:43 -05:00
|
|
|
case params[:state].to_s
|
|
|
|
when 'closed'
|
|
|
|
items.closed
|
|
|
|
when 'merged'
|
|
|
|
items.respond_to?(:merged) ? items.merged : items.closed
|
|
|
|
when 'opened'
|
|
|
|
items.opened
|
2018-06-26 15:30:29 -04:00
|
|
|
when 'locked'
|
2019-10-18 07:11:44 -04:00
|
|
|
items.with_state(:locked)
|
2014-01-15 09:16:23 -05:00
|
|
|
else
|
2016-09-23 09:16:11 -04:00
|
|
|
items
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
def by_project(items)
|
2020-03-30 17:08:01 -04:00
|
|
|
if params.project?
|
|
|
|
items.of_projects(params.projects).references_project
|
|
|
|
elsif params.projects
|
|
|
|
items.merge(params.projects.reorder(nil)).join_project
|
|
|
|
else
|
|
|
|
items.none
|
|
|
|
end
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
def by_search(items)
|
2018-06-05 13:31:32 -04:00
|
|
|
return items unless search
|
2019-10-23 05:06:03 -04:00
|
|
|
return items if items.is_a?(ActiveRecord::NullRelation)
|
2021-09-14 02:11:25 -04:00
|
|
|
return items if Feature.enabled?(:disable_anonymous_search, type: :ops) && current_user.nil?
|
2018-06-05 13:31:32 -04:00
|
|
|
|
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 05:46:13 -04:00
|
|
|
if use_cte_for_search?
|
2021-05-17 08:10:23 -04:00
|
|
|
cte = Gitlab::SQL::CTE.new(klass.table_name, items)
|
2018-06-05 13:31:32 -04:00
|
|
|
|
|
|
|
items = klass.with(cte.to_arel).from(klass.table_name)
|
|
|
|
end
|
|
|
|
|
2019-06-12 20:08:44 -04:00
|
|
|
items.full_search(search, matched_columns: params[:in], use_minimum_char_limit: !use_cte_for_search?)
|
2017-02-17 13:28:32 -05:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2017-02-17 13:28:32 -05:00
|
|
|
def by_iids(items)
|
|
|
|
params[:iids].present? ? items.where(iid: params[:iids]) : items
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
def by_negated_iids(items)
|
|
|
|
not_params[:iids].present? ? items.where.not(iid: not_params[:iids]) : items
|
|
|
|
end
|
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
def sort(items)
|
2015-11-11 06:50:36 -05:00
|
|
|
# Ensure we always have an explicit sort order (instead of inheriting
|
|
|
|
# multiple orders when combining ActiveRecord::Relation objects).
|
2021-09-16 14:11:32 -04:00
|
|
|
params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_filter.label_names_excluded_from_priority_sort) : items.reorder(id: :desc)
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2014-01-15 09:16:23 -05:00
|
|
|
|
2014-12-05 10:13:07 -05:00
|
|
|
def by_author(items)
|
2021-02-22 10:10:48 -05:00
|
|
|
Issuables::AuthorFilter.new(
|
|
|
|
params: original_params,
|
2021-05-03 02:10:30 -04:00
|
|
|
or_filters_enabled: or_filters_enabled?
|
2021-05-26 17:10:49 -04:00
|
|
|
).filter(items)
|
2020-05-06 23:09:46 -04:00
|
|
|
end
|
2020-01-28 10:08:36 -05:00
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
def by_assignee(items)
|
2021-05-26 17:10:49 -04:00
|
|
|
assignee_filter.filter(items)
|
2019-04-07 14:35:16 -04:00
|
|
|
end
|
|
|
|
|
2021-05-26 17:10:49 -04:00
|
|
|
def assignee_filter
|
|
|
|
strong_memoize(:assignee_filter) do
|
|
|
|
Issuables::AssigneeFilter.new(
|
|
|
|
params: original_params,
|
|
|
|
or_filters_enabled: or_filters_enabled?
|
|
|
|
)
|
2020-05-06 23:09:46 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-09-16 14:11:32 -04:00
|
|
|
def by_label(items)
|
|
|
|
label_filter.filter(items)
|
|
|
|
end
|
|
|
|
|
|
|
|
def label_filter
|
|
|
|
strong_memoize(:label_filter) do
|
|
|
|
Issuables::LabelFilter.new(
|
|
|
|
params: original_params,
|
|
|
|
project: params.project,
|
|
|
|
group: params.group
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
2015-10-16 05:43:26 -04:00
|
|
|
def by_milestone(items)
|
2020-03-30 17:08:01 -04:00
|
|
|
return items unless params.milestones?
|
|
|
|
|
|
|
|
if params.filter_by_no_milestone?
|
|
|
|
items.left_joins_milestones.where(milestone_id: [-1, nil])
|
|
|
|
elsif params.filter_by_any_milestone?
|
|
|
|
items.any_milestone
|
|
|
|
elsif params.filter_by_upcoming_milestone?
|
|
|
|
upcoming_ids = Milestone.upcoming_ids(params.projects, params.related_groups)
|
|
|
|
items.left_joins_milestones.where(milestone_id: upcoming_ids)
|
|
|
|
elsif params.filter_by_started_milestone?
|
|
|
|
items.left_joins_milestones.merge(Milestone.started)
|
|
|
|
else
|
|
|
|
items.with_milestone(params[:milestone_title])
|
2015-10-16 05:43:26 -04:00
|
|
|
end
|
|
|
|
end
|
2018-08-27 11:31:01 -04:00
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
2015-10-16 05:43:26 -04:00
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
# rubocop: disable CodeReuse/ActiveRecord
|
|
|
|
def by_negated_milestone(items)
|
|
|
|
return items unless not_params.milestones?
|
|
|
|
|
|
|
|
if not_params.filter_by_upcoming_milestone?
|
|
|
|
items.joins(:milestone).merge(Milestone.not_upcoming)
|
|
|
|
elsif not_params.filter_by_started_milestone?
|
|
|
|
items.joins(:milestone).merge(Milestone.not_started)
|
|
|
|
else
|
|
|
|
items.without_particular_milestone(not_params[:milestone_title])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
# rubocop: enable CodeReuse/ActiveRecord
|
|
|
|
|
2019-11-22 13:06:00 -05:00
|
|
|
def by_release(items)
|
2020-03-30 17:08:01 -04:00
|
|
|
return items unless params.releases?
|
2021-01-07 10:10:33 -05:00
|
|
|
return items if params.group? # don't allow release filtering at group level
|
2019-11-22 13:06:00 -05:00
|
|
|
|
2020-03-30 17:08:01 -04:00
|
|
|
if params.filter_by_no_release?
|
2019-11-22 13:06:00 -05:00
|
|
|
items.without_release
|
2020-03-30 17:08:01 -04:00
|
|
|
elsif params.filter_by_any_release?
|
2019-11-22 13:06:00 -05:00
|
|
|
items.any_release
|
|
|
|
else
|
|
|
|
items.with_release(params[:release_tag], params[:project_id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
def by_negated_release(items)
|
|
|
|
return items unless not_params.releases?
|
|
|
|
|
|
|
|
items.without_particular_release(not_params[:release_tag], not_params[:project_id])
|
|
|
|
end
|
|
|
|
|
2020-03-30 17:08:01 -04:00
|
|
|
def by_my_reaction_emoji(items)
|
|
|
|
return items unless params[:my_reaction_emoji] && current_user
|
2018-10-26 22:52:06 -04:00
|
|
|
|
2020-03-30 17:08:01 -04:00
|
|
|
if params.filter_by_no_reaction?
|
|
|
|
items.not_awarded(current_user)
|
|
|
|
elsif params.filter_by_any_reaction?
|
|
|
|
items.awarded(current_user)
|
2016-03-14 05:46:26 -04:00
|
|
|
else
|
2020-03-30 17:08:01 -04:00
|
|
|
items.awarded(current_user, params[:my_reaction_emoji])
|
2016-03-14 05:46:26 -04:00
|
|
|
end
|
2016-02-19 00:27:41 -05:00
|
|
|
end
|
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
def by_negated_my_reaction_emoji(items)
|
|
|
|
return items unless not_params[:my_reaction_emoji] && current_user
|
|
|
|
|
|
|
|
items.not_awarded(current_user, not_params[:my_reaction_emoji])
|
2016-11-27 05:33:15 -05:00
|
|
|
end
|
|
|
|
|
2020-05-06 23:09:46 -04:00
|
|
|
def by_non_archived(items)
|
|
|
|
params[:non_archived].present? ? items.non_archived : items
|
2020-01-28 10:08:36 -05:00
|
|
|
end
|
2021-02-22 10:10:48 -05:00
|
|
|
|
2021-12-03 01:10:53 -05:00
|
|
|
def by_crm_contact(items)
|
|
|
|
Issuables::CrmContactFilter.new(params: original_params).filter(items)
|
|
|
|
end
|
|
|
|
|
2021-12-06 19:14:07 -05:00
|
|
|
def by_crm_organization(items)
|
|
|
|
Issuables::CrmOrganizationFilter.new(params: original_params).filter(items)
|
|
|
|
end
|
|
|
|
|
2021-02-22 10:10:48 -05:00
|
|
|
def or_filters_enabled?
|
|
|
|
strong_memoize(:or_filters_enabled) do
|
|
|
|
Feature.enabled?(:or_issuable_queries, feature_flag_scope, default_enabled: :yaml)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def feature_flag_scope
|
|
|
|
params.group || params.project
|
|
|
|
end
|
2014-01-15 09:16:23 -05:00
|
|
|
end
|