diff options
Diffstat (limited to 'app/finders')
22 files changed, 267 insertions, 111 deletions
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb index dc7b9f6a0ce..8f90ce40bb4 100644 --- a/app/finders/branches_finder.rb +++ b/app/finders/branches_finder.rb @@ -1,13 +1,11 @@ # frozen_string_literal: true class BranchesFinder < GitRefsFinder - def initialize(repository, params = {}) - super(repository, params) - end - def execute(gitaly_pagination: false) if gitaly_pagination && names.blank? && search.blank? && regex.blank? - repository.branches_sorted_by(sort, pagination_params) + repository.branches_sorted_by(sort, pagination_params).tap do |branches| + set_next_cursor(branches) + end else branches = repository.branches_sorted_by(sort) branches = by_search(branches) diff --git a/app/finders/concerns/packages/finder_helper.rb b/app/finders/concerns/packages/finder_helper.rb index 585b35981a6..3f0e849d7ef 100644 --- a/app/finders/concerns/packages/finder_helper.rb +++ b/app/finders/concerns/packages/finder_helper.rb @@ -13,6 +13,21 @@ module Packages project.packages.installable end + # /!\ This function doesn't check user permissions + # at the package level. + def packages_for(user, within_group:) + return ::Packages::Package.none unless within_group + return ::Packages::Package.none unless Ability.allowed?(user, :read_group, within_group) + + projects = if user.is_a?(DeployToken) + user.accessible_projects + else + within_group.all_projects + end + + ::Packages::Package.for_projects(projects).installable + end + def packages_visible_to_user(user, within_group:, with_package_registry_enabled: false) return ::Packages::Package.none unless within_group return ::Packages::Package.none unless Ability.allowed?(user, :read_group, within_group) diff --git a/app/finders/deploy_keys/deploy_keys_finder.rb b/app/finders/deploy_keys/deploy_keys_finder.rb new file mode 100644 index 00000000000..5924a656801 --- /dev/null +++ b/app/finders/deploy_keys/deploy_keys_finder.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module DeployKeys + class DeployKeysFinder + attr_reader :project, :current_user, :params + + def initialize(project, current_user, params = {}) + @project = project + @current_user = current_user + @params = params + end + + def execute + return empty unless can_admin_project? + + case params[:filter] + when :enabled_keys + enabled_keys + when :available_project_keys + available_project_keys + when :available_public_keys + available_public_keys + else + empty + end + end + + private + + def enabled_keys + project.deploy_keys.with_projects + end + + def available_project_keys + current_user.project_deploy_keys.with_projects.not_in(enabled_keys) + end + + def available_public_keys + DeployKey.are_public.with_projects.not_in(enabled_keys) + end + + def empty + DeployKey.none + end + + def can_admin_project? + current_user.can?(:admin_project, project) + end + end +end diff --git a/app/finders/design_management/designs_finder.rb b/app/finders/design_management/designs_finder.rb index 857b828dc47..4793d5e0073 100644 --- a/app/finders/design_management/designs_finder.rb +++ b/app/finders/design_management/designs_finder.rb @@ -30,7 +30,7 @@ module DesignManagement attr_reader :issue, :current_user, :params def init_collection - return ::DesignManagement::Design.none unless can?(current_user, :read_design, issue) + return DesignManagement::Design.none unless can?(current_user, :read_design, issue) issue.designs end @@ -43,14 +43,14 @@ module DesignManagement def by_filename(items) return items if params[:filenames].nil? - return ::DesignManagement::Design.none if params[:filenames].empty? + return DesignManagement::Design.none if params[:filenames].empty? items.with_filename(params[:filenames]) end def by_id(items) return items if params[:ids].nil? - return ::DesignManagement::Design.none if params[:ids].empty? + return DesignManagement::Design.none if params[:ids].empty? items.id_in(params[:ids]) end diff --git a/app/finders/design_management/versions_finder.rb b/app/finders/design_management/versions_finder.rb index c4aefd3078e..8769cfdcec0 100644 --- a/app/finders/design_management/versions_finder.rb +++ b/app/finders/design_management/versions_finder.rb @@ -25,7 +25,7 @@ module DesignManagement def execute unless Ability.allowed?(current_user, :read_design, design_or_collection) - return ::DesignManagement::Version.none + return DesignManagement::Version.none end items = design_or_collection.versions diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb index 7bccfe453ab..4ed447a90ce 100644 --- a/app/finders/events_finder.rb +++ b/app/finders/events_finder.rb @@ -61,7 +61,6 @@ class EventsFinder def by_current_user_access(events) events.merge(Project.public_or_visible_to_user(current_user)) .joins(:project) - .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/417462") end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/git_refs_finder.rb b/app/finders/git_refs_finder.rb index 3c8d53051d6..521b4aa171f 100644 --- a/app/finders/git_refs_finder.rb +++ b/app/finders/git_refs_finder.rb @@ -3,9 +3,12 @@ class GitRefsFinder include Gitlab::Utils::StrongMemoize + attr_reader :next_cursor + def initialize(repository, params = {}) @repository = repository @params = params + @next_cursor = nil end protected @@ -54,4 +57,13 @@ class GitRefsFinder def unescape_regex_operators(regex_string) regex_string.sub('\^', '^').gsub('\*', '.*?').sub('\$', '$') end + + def set_next_cursor(records) + return if records.blank? + + # TODO: Gitaly should be responsible for a cursor generation + # Follow-up for branches: https://gitlab.com/gitlab-org/gitlab/-/issues/431903 + # Follow-up for tags: https://gitlab.com/gitlab-org/gitlab/-/issues/431904 + @next_cursor = records.last.name + end end diff --git a/app/finders/groups/custom_emoji_finder.rb b/app/finders/groups/custom_emoji_finder.rb new file mode 100644 index 00000000000..80a4e948f8b --- /dev/null +++ b/app/finders/groups/custom_emoji_finder.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Groups + class CustomEmojiFinder < Base + include FinderWithGroupHierarchy + include Gitlab::Utils::StrongMemoize + + def initialize(group, params = {}) + @group = group + @params = params + @skip_authorization = true + end + + def execute + return CustomEmoji.none if Feature.disabled?(:custom_emoji, group) + + return CustomEmoji.for_resource(group) unless params[:include_ancestor_groups] + + CustomEmoji.for_namespaces(group_ids_for(group)) + end + + private + + attr_reader :group, :params, :skip_authorization + end +end diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index 074eb9add0f..9cc27a3096d 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -17,6 +17,7 @@ # include_parent_descendants: boolean (defaults to false) - includes descendant groups when # filtering by parent. The parent param must be present. # include_ancestors: boolean (defaults to true) +# organization: Scope the groups to the Organizations::Organization # # Users with full private access can see all groups. The `owned` and `parent` # params can be used to restrict the groups that are returned. @@ -44,6 +45,7 @@ class GroupsFinder < UnionFinder attr_reader :current_user, :params def filter_groups(groups) + groups = by_organization(groups) groups = by_parent(groups) groups = by_custom_attributes(groups) groups = filter_group_ids(groups) @@ -93,6 +95,13 @@ class GroupsFinder < UnionFinder groups.id_in(params[:filter_group_ids]) end + def by_organization(groups) + organization = params[:organization] + return groups unless organization + + groups.in_organization(organization) + end + # rubocop: disable CodeReuse/ActiveRecord def by_parent(groups) return groups unless params[:parent] diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 93b7292bb69..2b4e4592020 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -130,9 +130,7 @@ class IssuableFinder end def filter_items(items) - # Selection by group is already covered by `by_project` and `projects` for project-based issuables - # Group-based issuables have their own group filter methods - items = by_project(items) + items = by_parent(items) items = by_scope(items) items = by_created_at(items) items = by_updated_at(items) @@ -313,7 +311,7 @@ class IssuableFinder end # rubocop: disable CodeReuse/ActiveRecord - def by_project(items) + def by_parent(items) # When finding issues for multiple projects it's more efficient # to use a JOIN instead of running a sub-query # See https://gitlab.com/gitlab-org/gitlab/-/commit/8591cc02be6b12ed60f763a5e0147f2cbbca99e1 diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 0ba93a76342..2297c0569d9 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -109,6 +109,21 @@ class IssuesFinder < IssuableFinder super.with_projects_matching_search_data end + override :by_parent + def by_parent(items) + return super unless include_namespace_level_work_items? + + items.in_namespaces( + Namespace.from_union( + [ + Group.id_in(params.group).select(:id), + params.projects.select(:project_namespace_id) + ], + remove_duplicates: false + ) + ) + end + def by_confidential(items) return items if params[:confidential].nil? @@ -157,6 +172,12 @@ class IssuesFinder < IssuableFinder def model_class Issue end + + def include_namespace_level_work_items? + params.group? && + Array(params[:issue_types]).map(&:to_s).include?('epic') && + Feature.enabled?(:namespace_level_work_items, params.group) + end end IssuesFinder.prepend_mod_with('IssuesFinder') diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb index 6348bceb157..5b4b83652df 100644 --- a/app/finders/members_finder.rb +++ b/app/finders/members_finder.rb @@ -90,10 +90,8 @@ class MembersFinder # enumerate the columns here since we are enumerating them in the union and want to be immune to # column caching issues when adding/removing columns - members = Member.select(*Member.column_names) - .includes(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord - # The left join with the table users in the method distinct_on needs to be resolved - members.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/417456") + Member.select(*Member.column_names) + .preload(:user).from([Arel.sql("(#{sql}) AS #{Member.table_name}")]) # rubocop: disable CodeReuse/ActiveRecord -- TODO: Usage of `from` forces us to use this. end def distinct_on(union) @@ -102,8 +100,7 @@ class MembersFinder <<~SQL SELECT DISTINCT ON (user_id, invite_email) #{member_columns} FROM (#{union.to_sql}) AS #{member_union_table} - LEFT JOIN users on users.id = member_union.user_id - LEFT JOIN project_authorizations on project_authorizations.user_id = users.id + LEFT JOIN project_authorizations on project_authorizations.user_id = member_union.user_id AND project_authorizations.project_id = #{project.id} ORDER BY user_id, diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 9ffd623338f..820fb6ea291 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -27,9 +27,8 @@ class MilestonesFinder def execute items = Milestone.all - items = by_ids(items) + items = by_ids_or_title(items) items = by_groups_and_projects(items) - items = by_title(items) items = by_search_title(items) items = by_search(items) items = by_state(items) @@ -43,26 +42,18 @@ class MilestonesFinder private - def by_ids(items) - return items unless params[:ids].present? + def by_ids_or_title(items) + return items if params[:ids].blank? && params[:title].blank? + return items.id_in(params[:ids]) if params[:ids].present? && params[:title].blank? + return items.with_title(params[:title]) if params[:ids].blank? && params[:title].present? - items.id_in(params[:ids]) + items.with_ids_or_title(ids: params[:ids], title: params[:title]) end def by_groups_and_projects(items) items.for_projects_and_groups(params[:project_ids], params[:group_ids]) end - # rubocop: disable CodeReuse/ActiveRecord - def by_title(items) - if params[:title] - items.where(title: params[:title]) - else - items - end - end - # rubocop: enable CodeReuse/ActiveRecord - def by_search_title(items) if params[:search_title].present? items.search_title(params[:search_title]) @@ -96,7 +87,7 @@ class MilestonesFinder end def by_iids(items) - return items unless params[:iids].present? && !params[:include_parent_milestones] + return items unless params[:iids].present? && !params[:include_ancestors] items.by_iid(params[:iids]) end diff --git a/app/finders/organizations/groups_finder.rb b/app/finders/organizations/groups_finder.rb deleted file mode 100644 index 2b59a3106a3..00000000000 --- a/app/finders/organizations/groups_finder.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true - -# Organizations::GroupsFinder -# -# Used to find Groups within an Organization -module Organizations - class GroupsFinder - # @param organization [Organizations::Organization] - # @param current_user [User] - # @param params [{ sort: { field: [String], direction: [String] }, search: [String] }] - def initialize(organization:, current_user:, params: {}) - @organization = organization - @current_user = current_user - @params = params - end - - def execute - return Group.none if organization.nil? || !authorized? - - filter_groups(all_accessible_groups) - .then { |groups| sort(groups) } - .then(&:with_route) - end - - private - - attr_reader :organization, :params, :current_user - - def all_accessible_groups - current_user.authorized_groups.in_organization(organization) - end - - def filter_groups(groups) - by_search(groups) - end - - def by_search(groups) - return groups unless params[:search].present? - - groups.search(params[:search]) - end - - def sort(groups) - return default_sort_order(groups) if params[:sort].blank? - - field = params[:sort][:field] - direction = params[:sort][:direction] - groups.reorder(field => direction) # rubocop: disable CodeReuse/ActiveRecord - end - - def default_sort_order(groups) - groups.sort_by_attribute('name_asc') - end - - def authorized? - Ability.allowed?(current_user, :read_organization, organization) - end - end -end diff --git a/app/finders/packages/maven/package_finder.rb b/app/finders/packages/maven/package_finder.rb index 03855afb6e4..b288611914f 100644 --- a/app/finders/packages/maven/package_finder.rb +++ b/app/finders/packages/maven/package_finder.rb @@ -3,6 +3,8 @@ module Packages module Maven class PackageFinder < ::Packages::GroupOrProjectPackageFinder + extend ::Gitlab::Utils::Override + def execute packages end @@ -15,6 +17,15 @@ module Packages matching_packages end + + override :group_packages + def group_packages + if Feature.enabled?(:maven_remove_permissions_check_from_finder, @project_or_group) + packages_for(@current_user, within_group: @project_or_group) + else + super + end + end end end end diff --git a/app/finders/projects/ml/model_finder.rb b/app/finders/projects/ml/model_finder.rb index 57e0620c7a7..024b353c920 100644 --- a/app/finders/projects/ml/model_finder.rb +++ b/app/finders/projects/ml/model_finder.rb @@ -5,7 +5,7 @@ module Projects class ModelFinder include Gitlab::Utils::StrongMemoize - VALID_ORDER_BY = %w[name created_at id].freeze + VALID_ORDER_BY = %w[name created_at updated_at id].freeze VALID_SORT = %w[asc desc].freeze def initialize(project, params = {}) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 1aa5245590e..2a781c037f6 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -29,6 +29,7 @@ # repository_storage: string # not_aimed_for_deletion: boolean # full_paths: string[] +# organization_id: int # class ProjectsFinder < UnionFinder include CustomAttributesFilter @@ -95,6 +96,7 @@ class ProjectsFinder < UnionFinder collection = by_language(collection) collection = by_feature_availability(collection) collection = by_updated_at(collection) + collection = by_organization_id(collection) by_repository_storage(collection) end @@ -293,6 +295,10 @@ class ProjectsFinder < UnionFinder items end + def by_organization_id(items) + params[:organization_id].present? ? items.in_organization(params[:organization_id]) : items + end + def finder_params return {} unless min_access_level? diff --git a/app/finders/releases_finder.rb b/app/finders/releases_finder.rb index c7d35f62673..fcc216f213b 100644 --- a/app/finders/releases_finder.rb +++ b/app/finders/releases_finder.rb @@ -27,11 +27,11 @@ class ReleasesFinder private def get_releases - Release.where(project_id: authorized_projects).where.not(tag: nil) # rubocop: disable CodeReuse/ActiveRecord + Release.for_projects(authorized_projects).tagged end def get_latest_releases - Release.latest_for_projects(authorized_projects, order_by: params[:order_by_for_latest]).where.not(tag: nil) # rubocop: disable CodeReuse/ActiveRecord + Release.latest_for_projects(authorized_projects, order_by: params[:order_by_for_latest]).tagged end def authorized_projects @@ -43,14 +43,6 @@ class ReleasesFinder end strong_memoize_attr :authorized_projects - # rubocop: disable CodeReuse/ActiveRecord - def by_tag(releases) - return releases unless params[:tag].present? - - releases.where(tag: params[:tag]) - end - # rubocop: enable CodeReuse/ActiveRecord - def order_releases(releases) releases.sort_by_attribute("#{params[:order_by]}_#{params[:sort]}") end @@ -58,4 +50,10 @@ class ReleasesFinder def authorized?(project) Ability.allowed?(current_user, :read_release, project) end + + def by_tag(releases) + return releases unless params[:tag].present? + + releases.by_tag(params[:tag]) + end end diff --git a/app/finders/repositories/tree_finder.rb b/app/finders/repositories/tree_finder.rb index 2a8971d4d86..8280908ff42 100644 --- a/app/finders/repositories/tree_finder.rb +++ b/app/finders/repositories/tree_finder.rb @@ -4,10 +4,13 @@ module Repositories class TreeFinder CommitMissingError = Class.new(StandardError) + attr_reader :next_cursor + def initialize(project, params = {}) @project = project @repository = project.repository @params = params + @next_cursor = nil end def execute(gitaly_pagination: false) @@ -16,7 +19,11 @@ module Repositories request_params = { recursive: recursive, rescue_not_found: rescue_not_found } request_params[:pagination_params] = pagination_params if gitaly_pagination - repository.tree(commit.id, path, **request_params).sorted_entries + tree = repository.tree(commit.id, path, **request_params) + + @next_cursor = tree.cursor&.next_cursor if gitaly_pagination + + tree.sorted_entries end def total diff --git a/app/finders/tags_finder.rb b/app/finders/tags_finder.rb index 52b1fff4883..a25d17dbaf4 100644 --- a/app/finders/tags_finder.rb +++ b/app/finders/tags_finder.rb @@ -8,8 +8,9 @@ class TagsFinder < GitRefsFinder repository.tags_sorted_by(sort) end - by_search(tags) - + by_search(tags).tap do |records| + set_next_cursor(records) if gitaly_pagination + end rescue ArgumentError => e raise Gitlab::Git::InvalidPageToken, "Invalid page token: #{page_token}" if e.message.include?('page token') diff --git a/app/finders/timelogs/timelogs_finder.rb b/app/finders/timelogs/timelogs_finder.rb new file mode 100644 index 00000000000..610dba43317 --- /dev/null +++ b/app/finders/timelogs/timelogs_finder.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module Timelogs + class TimelogsFinder + attr_reader :issuable, :params + + def initialize(issuable, params = {}) + @issuable = issuable + @params = params + end + + def execute + timelogs = issuable&.timelogs || Timelog.all + timelogs = by_time(timelogs) + timelogs = by_user(timelogs) + timelogs = by_group(timelogs) + timelogs = by_project(timelogs) + apply_sorting(timelogs) + end + + private + + def by_time(timelogs) + return timelogs unless params[:start_time] || params[:end_time] + + validate_time_difference! + + timelogs = timelogs.at_or_after(params[:start_time]) if params[:start_time] + timelogs = timelogs.at_or_before(params[:end_time]) if params[:end_time] + + timelogs + end + + def by_user(timelogs) + return timelogs unless params[:username] + + user = User.find_by_username(params[:username]) + timelogs.for_user(user) + end + + def by_group(timelogs) + return timelogs unless params[:group_id] + + group = Group.find_by_id(params[:group_id]) + raise(ActiveRecord::RecordNotFound, "Group with id '#{params[:group_id]}' could not be found") unless group + + timelogs.in_group(group) + end + + def by_project(timelogs) + return timelogs unless params[:project_id] + + timelogs.in_project(params[:project_id]) + end + + def apply_sorting(timelogs) + return timelogs unless params[:sort] + + timelogs.sort_by_field(params[:sort]) + end + + def validate_time_difference! + return unless end_time_before_start_time? + + raise ArgumentError, 'Start argument must be before End argument' + end + + def end_time_before_start_time? + times_provided? && params[:end_time] < params[:start_time] + end + + def times_provided? + params[:start_time] && params[:end_time] + end + end +end diff --git a/app/finders/work_items/namespace_work_items_finder.rb b/app/finders/work_items/namespace_work_items_finder.rb index da6437e0907..95075e0ca0d 100644 --- a/app/finders/work_items/namespace_work_items_finder.rb +++ b/app/finders/work_items/namespace_work_items_finder.rb @@ -54,8 +54,8 @@ module WorkItems end strong_memoize_attr :namespace - override :by_project - def by_project(items) + override :by_parent + def by_parent(items) items end end |