diff options
Diffstat (limited to 'app/finders')
-rw-r--r-- | app/finders/branches_finder.rb | 4 | ||||
-rw-r--r-- | app/finders/ci/pipelines_finder.rb | 15 | ||||
-rw-r--r-- | app/finders/ci/pipelines_for_merge_request_finder.rb | 24 | ||||
-rw-r--r-- | app/finders/ci/runners_finder.rb | 18 | ||||
-rw-r--r-- | app/finders/error_tracking/errors_finder.rb | 14 | ||||
-rw-r--r-- | app/finders/groups/user_groups_finder.rb | 60 | ||||
-rw-r--r-- | app/finders/issuable_finder.rb | 63 | ||||
-rw-r--r-- | app/finders/issuable_finder/params.rb | 32 | ||||
-rw-r--r-- | app/finders/issuables/label_filter.rb | 155 | ||||
-rw-r--r-- | app/finders/issues_finder.rb | 7 | ||||
-rw-r--r-- | app/finders/issues_finder/params.rb | 4 | ||||
-rw-r--r-- | app/finders/packages/helm/package_files_finder.rb | 2 | ||||
-rw-r--r-- | app/finders/packages/helm/packages_finder.rb | 30 | ||||
-rw-r--r-- | app/finders/packages/npm/package_finder.rb | 17 | ||||
-rw-r--r-- | app/finders/projects_finder.rb | 10 | ||||
-rw-r--r-- | app/finders/repositories/tree_finder.rb | 61 |
16 files changed, 402 insertions, 114 deletions
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb index 157c454183a..a62d47071d4 100644 --- a/app/finders/branches_finder.rb +++ b/app/finders/branches_finder.rb @@ -15,6 +15,10 @@ class BranchesFinder < GitRefsFinder end end + def total + repository.branch_count + end + private def names diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb index a79840216da..39355853d88 100644 --- a/app/finders/ci/pipelines_finder.rb +++ b/app/finders/ci/pipelines_finder.rb @@ -25,12 +25,10 @@ module Ci items = by_status(items) items = by_ref(items) items = by_sha(items) - items = by_name(items) items = by_username(items) items = by_yaml_errors(items) items = by_updated_at(items) - - items = by_source(items) if Feature.enabled?(:pipeline_source_filter, project, default_enabled: :yaml) + items = by_source(items) sort_items(items) end @@ -116,17 +114,6 @@ module Ci end # rubocop: enable CodeReuse/ActiveRecord - # This method is deprecated and will be removed in 14.3 - # rubocop: disable CodeReuse/ActiveRecord - def by_name(items) - if params[:name].present? - items.joins(:user).where(users: { name: params[:name] }) - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def by_username(items) return items unless params[:username].present? diff --git a/app/finders/ci/pipelines_for_merge_request_finder.rb b/app/finders/ci/pipelines_for_merge_request_finder.rb index f769da03738..5d794c0903a 100644 --- a/app/finders/ci/pipelines_for_merge_request_finder.rb +++ b/app/finders/ci/pipelines_for_merge_request_finder.rb @@ -29,17 +29,19 @@ module Ci # Fetch all pipelines without permission check. def all - strong_memoize(:all_pipelines) do - next Ci::Pipeline.none unless source_project - - pipelines = - if merge_request.persisted? - pipelines_using_cte - else - triggered_for_branch.for_sha(commit_shas) - end - - sort(pipelines) + ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336891') do + strong_memoize(:all_pipelines) do + next Ci::Pipeline.none unless source_project + + pipelines = + if merge_request.persisted? + pipelines_using_cte + else + triggered_for_branch.for_sha(commit_shas) + end + + sort(pipelines) + end end end diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb index d34b3202433..8bc2a47a024 100644 --- a/app/finders/ci/runners_finder.rb +++ b/app/finders/ci/runners_finder.rb @@ -7,9 +7,9 @@ module Ci ALLOWED_SORTS = %w[contacted_asc contacted_desc created_at_asc created_at_desc created_date].freeze DEFAULT_SORT = 'created_at_desc' - def initialize(current_user:, group: nil, params:) + def initialize(current_user:, params:) @params = params - @group = group + @group = params.delete(:group) @current_user = current_user end @@ -48,10 +48,16 @@ module Ci def group_runners raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group) - # Getting all runners from the group itself and all its descendants - descendant_projects = Project.for_group_and_its_subgroups(@group) - - @runners = Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects) + @runners = case @params[:membership] + when :direct + Ci::Runner.belonging_to_group(@group.id) + when :descendants, nil + # Getting all runners from the group itself and all its descendant groups/projects + descendant_projects = Project.for_group_and_its_subgroups(@group) + Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects) + else + raise ArgumentError, 'Invalid membership filter' + end end def filter_by_status! diff --git a/app/finders/error_tracking/errors_finder.rb b/app/finders/error_tracking/errors_finder.rb index fb2d4b14dfa..d83a0c487e6 100644 --- a/app/finders/error_tracking/errors_finder.rb +++ b/app/finders/error_tracking/errors_finder.rb @@ -13,9 +13,10 @@ module ErrorTracking collection = project.error_tracking_errors collection = by_status(collection) + collection = sort(collection) - # Limit collection until pagination implemented - collection.limit(20) + # Limit collection until pagination implemented. + limit(collection) end private @@ -33,5 +34,14 @@ module ErrorTracking def authorized? Ability.allowed?(current_user, :read_sentry_issue, project) end + + def sort(collection) + params[:sort] ? collection.sort_by_attribute(params[:sort]) : collection.order_id_desc + end + + def limit(collection) + # Restrict the maximum limit at 100 records. + collection.limit([(params[:limit] || 20).to_i, 100].min) + end end end diff --git a/app/finders/groups/user_groups_finder.rb b/app/finders/groups/user_groups_finder.rb new file mode 100644 index 00000000000..5946e3a8933 --- /dev/null +++ b/app/finders/groups/user_groups_finder.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# Groups::UserGroupsFinder +# +# Used to filter Groups where a user is member +# +# Arguments: +# current_user - user requesting group info on target user +# target_user - user for which groups will be found +# params: +# permissions: string (see Types::Groups::UserPermissionsEnum) +# search: string used for search on path and group name +# +# Initially created to filter user groups and descendants where the user can create projects +module Groups + class UserGroupsFinder + def initialize(current_user, target_user, params = {}) + @current_user = current_user + @target_user = target_user + @params = params + end + + def execute + return Group.none unless current_user&.can?(:read_user_groups, target_user) + return Group.none if target_user.blank? + + items = by_permission_scope + items = by_search(items) + + sort(items) + end + + private + + attr_reader :current_user, :target_user, :params + + def sort(items) + items.order(path: :asc, id: :asc) # rubocop: disable CodeReuse/ActiveRecord + end + + def by_search(items) + return items if params[:search].blank? + + items.search(params[:search]) + end + + def by_permission_scope + if permission_scope_create_projects? + target_user.manageable_groups(include_groups_with_developer_maintainer_access: true) + else + target_user.groups + end + end + + def permission_scope_create_projects? + params[:permission_scope] == :create_projects && + Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml) + end + end +end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9f3ca385d93..cf706a8f98e 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -41,7 +41,6 @@ class IssuableFinder include FinderMethods include CreatedAtFilter include Gitlab::Utils::StrongMemoize - prepend OptimizedIssuableLabelFilter requires_cross_project_access unless: -> { params.project? } @@ -149,7 +148,6 @@ class IssuableFinder # Negates all params found in `negatable_params` def filter_negated_items(items) - items = by_negated_label(items) items = by_negated_milestone(items) items = by_negated_release(items) items = by_negated_my_reaction_emoji(items) @@ -172,29 +170,19 @@ class IssuableFinder count_params = params.merge(state: nil, sort: nil, force_cte: true) finder = self.class.new(current_user, count_params) + state_counts = finder + .execute + .reorder(nil) + .group(:state_id) + .count + counts = Hash.new(0) - # Searching by label includes a GROUP BY in the query, but ours will be last - # because it is added last. Searching by multiple labels also includes a row - # per issuable, so we have to count those in Ruby - which is bad, but still - # better than performing multiple queries. - # - # This does not apply when we are using a CTE for the search, as the labels - # GROUP BY is inside the subquery in that case, so we set labels_count to 1. - # - # Groups and projects have separate feature flags to suggest the use - # of a CTE. The CTE will not be used if the sort doesn't support it, - # but will always be used for the counts here as we ignore sorting - # anyway. - labels_count = params.label_names.any? ? params.label_names.count : 1 - labels_count = 1 if use_cte_for_search? - - finder.execute.reorder(nil).group(:state_id).count.each do |key, value| - counts[count_key(key)] += value / labels_count + state_counts.each do |key, value| + counts[count_key(key)] += value end counts[:all] = counts.values.sum - counts.with_indifferent_access end # rubocop: enable CodeReuse/ActiveRecord @@ -332,6 +320,7 @@ class IssuableFinder def by_search(items) return items unless search return items if items.is_a?(ActiveRecord::NullRelation) + return items if Feature.enabled?(:disable_anonymous_search, type: :ops) && current_user.nil? if use_cte_for_search? cte = Gitlab::SQL::CTE.new(klass.table_name, items) @@ -359,7 +348,7 @@ class IssuableFinder def sort(items) # Ensure we always have an explicit sort order (instead of inheriting # multiple orders when combining ActiveRecord::Relation objects). - params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: params.label_names) : items.reorder(id: :desc) + params[:sort] ? items.sort_by_attribute(params[:sort], excluded_labels: label_filter.label_names_excluded_from_priority_sort) : items.reorder(id: :desc) end # rubocop: enable CodeReuse/ActiveRecord @@ -383,6 +372,20 @@ class IssuableFinder end end + 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 + # rubocop: disable CodeReuse/ActiveRecord def by_milestone(items) return items unless params.milestones? @@ -435,24 +438,6 @@ class IssuableFinder items.without_particular_release(not_params[:release_tag], not_params[:project_id]) end - def by_label(items) - return items unless params.labels? - - if params.filter_by_no_label? - items.without_label - elsif params.filter_by_any_label? - items.any_label(params[:sort]) - else - items.with_label(params.label_names, params[:sort]) - end - end - - def by_negated_label(items) - return items unless not_params.labels? - - items.without_particular_labels(not_params.label_names) - end - def by_my_reaction_emoji(items) return items unless params[:my_reaction_emoji] && current_user diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb index 595f4e4cf8a..359a56bd39b 100644 --- a/app/finders/issuable_finder/params.rb +++ b/app/finders/issuable_finder/params.rb @@ -29,20 +29,6 @@ class IssuableFinder params.present? 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? || params[:milestone_wildcard_id].present? end @@ -160,24 +146,6 @@ class IssuableFinder end 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? diff --git a/app/finders/issuables/label_filter.rb b/app/finders/issuables/label_filter.rb new file mode 100644 index 00000000000..2bbc963aa90 --- /dev/null +++ b/app/finders/issuables/label_filter.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +module Issuables + class LabelFilter < BaseFilter + include Gitlab::Utils::StrongMemoize + extend Gitlab::Cache::RequestCache + + def initialize(project:, group:, **kwargs) + @project = project + @group = group + + super(**kwargs) + end + + def filter(issuables) + filtered = by_label(issuables) + by_negated_label(filtered) + end + + def label_names_excluded_from_priority_sort + label_names_from_params + end + + private + + # rubocop: disable CodeReuse/ActiveRecord + def by_label(issuables) + return issuables unless label_names_from_params.present? + + target_model = issuables.model + + if filter_by_no_label? + issuables.where(label_link_query(target_model).arel.exists.not) + elsif filter_by_any_label? + issuables.where(label_link_query(target_model).arel.exists) + else + issuables_with_selected_labels(issuables, label_names_from_params) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + def by_negated_label(issuables) + return issuables unless label_names_from_not_params.present? + + issuables_without_selected_labels(issuables, label_names_from_not_params) + end + + def filter_by_no_label? + label_names_from_params.map(&:downcase).include?(FILTER_NONE) + end + + def filter_by_any_label? + label_names_from_params.map(&:downcase).include?(FILTER_ANY) + end + + # rubocop: disable CodeReuse/ActiveRecord + def issuables_with_selected_labels(issuables, label_names) + target_model = issuables.model + + if root_namespace + all_label_ids = find_label_ids(label_names) + # Found less labels in the DB than we were searching for. Return nothing. + return issuables.none if all_label_ids.size != label_names.size + + all_label_ids.each do |label_ids| + issuables = issuables.where(label_link_query(target_model, label_ids: label_ids).arel.exists) + end + else + label_names.each do |label_name| + issuables = issuables.where(label_link_query(target_model, label_names: label_name).arel.exists) + end + end + + issuables + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def issuables_without_selected_labels(issuables, label_names) + target_model = issuables.model + + if root_namespace + label_ids = find_label_ids(label_names).flatten(1) + + issuables.where(label_link_query(target_model, label_ids: label_ids).arel.exists.not) + else + issuables.where(label_link_query(target_model, label_names: label_names).arel.exists.not) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def find_label_ids(label_names) + group_labels = Label + .where(project_id: nil) + .where(title: label_names) + .where(group_id: root_namespace.self_and_descendant_ids) + + project_labels = Label + .where(group_id: nil) + .where(title: label_names) + .where(project_id: Project.select(:id).where(namespace_id: root_namespace.self_and_descendant_ids)) + + Label + .from_union([group_labels, project_labels], remove_duplicates: false) + .reorder(nil) + .pluck(:title, :id) + .group_by(&:first) + .values + .map { |labels| labels.map(&:last) } + end + # Avoid repeating label queries times when the finder is instantiated multiple times during the request. + request_cache(:find_label_ids) { root_namespace.id } + # rubocop: enable CodeReuse/ActiveRecord + + # rubocop: disable CodeReuse/ActiveRecord + def label_link_query(target_model, label_ids: nil, label_names: nil) + relation = LabelLink + .where(target_type: target_model.name) + .where(LabelLink.arel_table['target_id'].eq(target_model.arel_table['id'])) + + relation = relation.where(label_id: label_ids) if label_ids + relation = relation.joins(:label).where(labels: { name: label_names }) if label_names + + relation + end + # rubocop: enable CodeReuse/ActiveRecord + + def label_names_from_params + return if params[:label_name].blank? + + strong_memoize(:label_names_from_params) do + split_label_names(params[:label_name]) + end + end + + def label_names_from_not_params + return if not_params.blank? || not_params[:label_name].blank? + + strong_memoize(:label_names_from_not_params) do + split_label_names(not_params[:label_name]) + end + end + + def split_label_names(label_name_param) + label_name_param.is_a?(String) ? label_name_param.split(',') : label_name_param + end + + def root_namespace + strong_memoize(:root_namespace) do + (@project || @group)&.root_ancestor + end + end + end +end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 7595b1c7a15..abf0c180d6b 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -20,6 +20,7 @@ # sort: string # my_reaction_emoji: string # public_only: boolean +# include_hidden: boolean # due_date: date or '0', '', 'overdue', 'week', or 'month' # created_after: datetime # created_before: datetime @@ -47,8 +48,6 @@ class IssuesFinder < IssuableFinder # rubocop: disable CodeReuse/ActiveRecord def with_confidentiality_access_check - return Issue.all if params.user_can_see_all_issues? - # Only admins can see hidden issues, so for non-admins, we filter out any hidden issues issues = Issue.without_hidden @@ -76,7 +75,9 @@ class IssuesFinder < IssuableFinder private def init_collection - if params.public_only? + if params.include_hidden? + Issue.all + elsif params.public_only? Issue.public_only else with_confidentiality_access_check diff --git a/app/finders/issues_finder/params.rb b/app/finders/issues_finder/params.rb index 2edd8a6f099..02b89f08f9e 100644 --- a/app/finders/issues_finder/params.rb +++ b/app/finders/issues_finder/params.rb @@ -6,6 +6,10 @@ class IssuesFinder params.fetch(:public_only, false) end + def include_hidden? + user_can_see_all_issues? + end + def filter_by_no_due_date? due_date? && params[:due_date] == Issue::NoDueDate.name end diff --git a/app/finders/packages/helm/package_files_finder.rb b/app/finders/packages/helm/package_files_finder.rb index ba400b27554..c6504d09dce 100644 --- a/app/finders/packages/helm/package_files_finder.rb +++ b/app/finders/packages/helm/package_files_finder.rb @@ -6,6 +6,8 @@ module Packages DEFAULT_PACKAGE_FILES_COUNT = 20 MAX_PACKAGE_FILES_COUNT = 1000 + delegate :most_recent!, to: :execute + def initialize(project, channel, params = {}) @project = project @channel = channel diff --git a/app/finders/packages/helm/packages_finder.rb b/app/finders/packages/helm/packages_finder.rb new file mode 100644 index 00000000000..c58d9292e9f --- /dev/null +++ b/app/finders/packages/helm/packages_finder.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Packages + module Helm + class PackagesFinder + include ::Packages::FinderHelper + + MAX_PACKAGES_COUNT = 300 + + def initialize(project, channel) + @project = project + @channel = channel + end + + def execute + if @channel.blank? || @project.blank? + return ::Packages::Package.none + end + + pkg_files = ::Packages::PackageFile.for_helm_with_channel(@project, @channel) + + # we use a subquery to get unique packages and at the same time + # order + limit them. + ::Packages::Package + .limit_recent(MAX_PACKAGES_COUNT) + .id_in(pkg_files.select(:package_id)) + end + end + end +end diff --git a/app/finders/packages/npm/package_finder.rb b/app/finders/packages/npm/package_finder.rb index 92ceac297ee..a367fda37de 100644 --- a/app/finders/packages/npm/package_finder.rb +++ b/app/finders/packages/npm/package_finder.rb @@ -5,18 +5,23 @@ module Packages delegate :find_by_version, to: :execute delegate :last, to: :execute - def initialize(package_name, project: nil, namespace: nil) + # /!\ CAUTION: don't use last_of_each_version: false with find_by_version. Ordering is not + # guaranteed! + def initialize(package_name, project: nil, namespace: nil, last_of_each_version: true) @package_name = package_name @project = project @namespace = namespace + @last_of_each_version = last_of_each_version end def execute - base.npm - .with_name(@package_name) - .installable - .last_of_each_version - .preload_files + result = base.npm + .with_name(@package_name) + .installable + + return result unless @last_of_each_version + + result.last_of_each_version end private diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index dca3d12f3c9..5537058cc79 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -180,12 +180,20 @@ class ProjectsFinder < UnionFinder # rubocop: enable CodeReuse/ActiveRecord def by_topics(items) - params[:topic].present? ? items.tagged_with(params[:topic]) : items + return items unless params[:topic].present? + + topics = params[:topic].instance_of?(String) ? params[:topic].strip.split(/\s*,\s*/) : params[:topic] + topics.each do |topic| + items = items.with_topic(topic) + end + + items end def by_search(items) params[:search] ||= params[:name] + return items if Feature.enabled?(:disable_anonymous_project_search, type: :ops) && current_user.nil? return items.none if params[:search].present? && params[:minimum_search_length].present? && params[:search].length < params[:minimum_search_length].to_i items.optionally_search(params[:search], include_namespace: params[:search_namespaces].present?) diff --git a/app/finders/repositories/tree_finder.rb b/app/finders/repositories/tree_finder.rb new file mode 100644 index 00000000000..2ea5a8856ec --- /dev/null +++ b/app/finders/repositories/tree_finder.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Repositories + class TreeFinder < GitRefsFinder + attr_reader :user_project + + CommitMissingError = Class.new(StandardError) + + def initialize(user_project, params = {}) + super(user_project.repository, params) + + @user_project = user_project + end + + def execute(gitaly_pagination: false) + raise CommitMissingError unless commit_exists? + + request_params = { recursive: recursive } + request_params[:pagination_params] = pagination_params if gitaly_pagination + tree = user_project.repository.tree(commit.id, path, **request_params) + + tree.sorted_entries + end + + def total + # This is inefficient and we'll look at replacing this implementation + Gitlab::Cache.fetch_once([user_project, repository.commit, :tree_size, commit.id, path, recursive]) do + user_project.repository.tree(commit.id, path, recursive: recursive).entries.size + end + end + + def commit_exists? + commit.present? + end + + private + + def commit + @commit ||= user_project.commit(ref) + end + + def ref + params[:ref] || user_project.default_branch + end + + def path + params[:path] + end + + def recursive + params[:recursive] + end + + def pagination_params + { + limit: params[:per_page] || Kaminari.config.default_per_page, + page_token: params[:page_token] + } + end + end +end |