diff options
Diffstat (limited to 'app/finders')
30 files changed, 703 insertions, 114 deletions
diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb index 8001c70a9b2..2eee90a512a 100644 --- a/app/finders/branches_finder.rb +++ b/app/finders/branches_finder.rb @@ -5,11 +5,15 @@ class BranchesFinder < GitRefsFinder super(repository, params) end - def execute - branches = repository.branches_sorted_by(sort) - branches = by_search(branches) - branches = by_names(branches) - branches + def execute(gitaly_pagination: false) + if gitaly_pagination && names.blank? && search.blank? + repository.branches_sorted_by(sort, pagination_params) + else + branches = repository.branches_sorted_by(sort) + branches = by_search(branches) + branches = by_names(branches) + branches + end end private @@ -18,6 +22,18 @@ class BranchesFinder < GitRefsFinder @params[:names].presence end + def per_page + @params[:per_page].presence + end + + def page_token + "#{Gitlab::Git::BRANCH_REF_PREFIX}#{@params[:page_token]}" if @params[:page_token] + end + + def pagination_params + { limit: per_page, page_token: page_token } + end + def by_names(branches) return branches unless names diff --git a/app/finders/ci/pipelines_finder.rb b/app/finders/ci/pipelines_finder.rb index 9e71e92b456..7347a83d294 100644 --- a/app/finders/ci/pipelines_finder.rb +++ b/app/finders/ci/pipelines_finder.rb @@ -71,7 +71,7 @@ module Ci # rubocop: disable CodeReuse/ActiveRecord def by_status(items) - return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) + return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) items.where(status: params[:status]) end diff --git a/app/finders/ci/pipelines_for_merge_request_finder.rb b/app/finders/ci/pipelines_for_merge_request_finder.rb index c01a68d6749..93d139652b9 100644 --- a/app/finders/ci/pipelines_for_merge_request_finder.rb +++ b/app/finders/ci/pipelines_for_merge_request_finder.rb @@ -7,14 +7,29 @@ module Ci EVENT = 'merge_request_event' - def initialize(merge_request) + def initialize(merge_request, current_user) @merge_request = merge_request + @current_user = current_user end - attr_reader :merge_request + attr_reader :merge_request, :current_user - delegate :commit_shas, :source_project, :source_branch, to: :merge_request + delegate :commit_shas, :target_project, :source_project, :source_branch, to: :merge_request + # Fetch all pipelines that the user can read. + def execute + if can_read_pipeline_in_target_project? && can_read_pipeline_in_source_project? + all + elsif can_read_pipeline_in_source_project? + all.for_project(merge_request.source_project) + elsif can_read_pipeline_in_target_project? + all.for_project(merge_request.target_project) + else + Ci::Pipeline.none + end + end + + # Fetch all pipelines without permission check. def all strong_memoize(:all_pipelines) do next Ci::Pipeline.none unless source_project @@ -35,13 +50,13 @@ module Ci def pipelines_using_cte cte = Gitlab::SQL::CTE.new(:shas, merge_request.all_commits.select(:sha)) - source_pipelines_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha]) - source_pipelines = filter_by(triggered_by_merge_request, cte, source_pipelines_join) - detached_pipelines = filter_by_sha(triggered_by_merge_request, cte) + source_sha_join = cte.table[:sha].eq(Ci::Pipeline.arel_table[:source_sha]) + merged_result_pipelines = filter_by(triggered_by_merge_request, cte, source_sha_join) + detached_merge_request_pipelines = filter_by_sha(triggered_by_merge_request, cte) pipelines_for_branch = filter_by_sha(triggered_for_branch, cte) Ci::Pipeline.with(cte.to_arel) # rubocop: disable CodeReuse/ActiveRecord - .from_union([source_pipelines, detached_pipelines, pipelines_for_branch]) + .from_union([merged_result_pipelines, detached_merge_request_pipelines, pipelines_for_branch]) end def filter_by_sha(pipelines, cte) @@ -65,8 +80,7 @@ module Ci # NOTE: this method returns only parent merge request pipelines. # Child merge request pipelines have a different source. def triggered_by_merge_request - source_project.ci_pipelines - .where(source: :merge_request_event, merge_request: merge_request) # rubocop: disable CodeReuse/ActiveRecord + Ci::Pipeline.triggered_by_merge_request(merge_request) end def triggered_for_branch @@ -86,5 +100,17 @@ module Ci pipelines.order(Arel.sql(query)) # rubocop: disable CodeReuse/ActiveRecord end + + def can_read_pipeline_in_target_project? + strong_memoize(:can_read_pipeline_in_target_project) do + Ability.allowed?(current_user, :read_pipeline, target_project) + end + end + + def can_read_pipeline_in_source_project? + strong_memoize(:can_read_pipeline_in_source_project) do + Ability.allowed?(current_user, :read_pipeline, source_project) + end + end end end diff --git a/app/finders/ci/runner_jobs_finder.rb b/app/finders/ci/runner_jobs_finder.rb index ffcdb407e7e..9dc3c2a2427 100644 --- a/app/finders/ci/runner_jobs_finder.rb +++ b/app/finders/ci/runner_jobs_finder.rb @@ -21,7 +21,7 @@ module Ci # rubocop: disable CodeReuse/ActiveRecord def by_status(items) - return items unless HasStatus::AVAILABLE_STATUSES.include?(params[:status]) + return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) items.where(status: params[:status]) end diff --git a/app/finders/ci/variables_finder.rb b/app/finders/ci/variables_finder.rb new file mode 100644 index 00000000000..d933643ffb2 --- /dev/null +++ b/app/finders/ci/variables_finder.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Ci + class VariablesFinder + attr_reader :project, :params + + def initialize(project, params) + @project, @params = project, params + + raise ArgumentError, 'Please provide params[:key]' if params[:key].blank? + end + + def execute + variables = project.variables + variables = by_key(variables) + variables = by_environment_scope(variables) + variables + end + + private + + def by_key(variables) + variables.by_key(params[:key]) + end + + def by_environment_scope(variables) + environment_scope = params.dig(:filter, :environment_scope) + environment_scope.present? ? variables.by_environment_scope(environment_scope) : variables + end + end +end diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb index 004fbc4cd22..4c619f3d7ea 100644 --- a/app/finders/events_finder.rb +++ b/app/finders/events_finder.rb @@ -54,17 +54,10 @@ class EventsFinder if current_user && scope == 'all' EventCollection.new(current_user.authorized_projects).all_project_events else - # EventCollection is responsible for applying the feature flag - apply_feature_flags(source.events) + source.events end end - def apply_feature_flags(events) - return events if ::Feature.enabled?(:wiki_events) - - events.not_wiki_page - end - # rubocop: disable CodeReuse/ActiveRecord def by_current_user_access(events) events.merge(Project.public_or_visible_to_user(current_user)) diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb index dd8b2f29425..5f24b15156c 100644 --- a/app/finders/group_projects_finder.rb +++ b/app/finders/group_projects_finder.rb @@ -19,6 +19,9 @@ # personal: boolean # search: string # non_archived: boolean +# with_issues_enabled: boolean +# with_merge_requests_enabled: boolean +# min_access_level: int # class GroupProjectsFinder < ProjectsFinder DEFAULT_PROJECTS_LIMIT = 100 @@ -42,6 +45,12 @@ class GroupProjectsFinder < ProjectsFinder private + def filter_projects(collection) + projects = super + projects = by_feature_availability(projects) + projects + end + def limit(collection) limit = options[:limit] @@ -49,35 +58,37 @@ class GroupProjectsFinder < ProjectsFinder end def init_collection - projects = if current_user - collection_with_user - else - collection_without_user - end + projects = + if only_shared? + [shared_projects] + elsif only_owned? + [owned_projects] + else + [owned_projects, shared_projects] + end + + projects.map! do |project_relation| + filter_by_visibility(project_relation) + end union(projects) end - def collection_with_user - if only_shared? - [shared_projects.public_or_visible_to_user(current_user)] - elsif only_owned? - [owned_projects.public_or_visible_to_user(current_user)] - else - [ - owned_projects.public_or_visible_to_user(current_user), - shared_projects.public_or_visible_to_user(current_user) - ] - end + def by_feature_availability(projects) + projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled].present? + projects = projects.with_merge_requests_available_for_user(current_user) if params[:with_merge_requests_enabled].present? + projects end - def collection_without_user - if only_shared? - [shared_projects.public_only] - elsif only_owned? - [owned_projects.public_only] + def filter_by_visibility(relation) + if current_user + if min_access_level? + relation.visible_to_user_and_access_level(current_user, params[:min_access_level]) + else + relation.public_or_visible_to_user(current_user) + end else - [shared_projects.public_only, owned_projects.public_only] + relation.public_only end end diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb index 5b48d0817e3..8a194f34f74 100644 --- a/app/finders/issuable_finder/params.rb +++ b/app/finders/issuable_finder/params.rb @@ -110,7 +110,9 @@ class IssuableFinder def group strong_memoize(:group) do - if params[:group_id].present? + if params[:group_id].is_a?(Group) + params[:group_id] + elsif params[:group_id].present? Group.find(params[:group_id]) else nil diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 72695a9d501..2b2e6b377b4 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -24,6 +24,7 @@ # created_before: datetime # updated_after: datetime # updated_before: datetime +# confidential: boolean # class IssuesFinder < IssuableFinder CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER diff --git a/app/finders/issues_finder/params.rb b/app/finders/issues_finder/params.rb index cd92b79265d..668d969f7c0 100644 --- a/app/finders/issues_finder/params.rb +++ b/app/finders/issues_finder/params.rb @@ -27,19 +27,14 @@ class IssuesFinder end def user_can_see_all_confidential_issues? - return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues) - - return @user_can_see_all_confidential_issues = false if current_user.blank? - return @user_can_see_all_confidential_issues = true if current_user.can_read_all_resources? - - @user_can_see_all_confidential_issues = - if project? && project - project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL - elsif group - group.max_member_access_for_user(current_user) >= CONFIDENTIAL_ACCESS_LEVEL + strong_memoize(:user_can_see_all_confidential_issues) do + parent = project? ? project : group + if parent + Ability.allowed?(current_user, :read_confidential_issues, parent) else - false + Ability.allowed?(current_user, :read_all_resources) end + end end def user_cannot_see_confidential_issues? diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 8e57014f66e..1a3f011d9eb 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -158,13 +158,16 @@ class NotesFinder end # Notes changed since last fetch - # Uses overlapping intervals to avoid worrying about race conditions def since_fetch_at(notes) return notes unless @params[:last_fetched_at] # Default to 0 to remain compatible with old clients - last_fetched_at = Time.at(@params.fetch(:last_fetched_at, 0).to_i) - notes.updated_after(last_fetched_at - FETCH_OVERLAP) + last_fetched_at = @params.fetch(:last_fetched_at, Time.at(0)) + + # Use overlapping intervals to avoid worrying about race conditions + last_fetched_at -= FETCH_OVERLAP + + notes.updated_after(last_fetched_at) end def notes_filter? diff --git a/app/finders/packages/composer/packages_finder.rb b/app/finders/packages/composer/packages_finder.rb new file mode 100644 index 00000000000..e63b2ee03fa --- /dev/null +++ b/app/finders/packages/composer/packages_finder.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +module Packages + module Composer + class PackagesFinder < Packages::GroupPackagesFinder + def initialize(current_user, group, params = {}) + @current_user = current_user + @group = group + @params = params + end + + def execute + packages_for_group_projects.composer.preload_composer + end + end + end +end diff --git a/app/finders/packages/conan/package_file_finder.rb b/app/finders/packages/conan/package_file_finder.rb new file mode 100644 index 00000000000..edf35388a36 --- /dev/null +++ b/app/finders/packages/conan/package_file_finder.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Packages + module Conan + class PackageFileFinder < ::Packages::PackageFileFinder + private + + def package_files + files = super + files = by_conan_file_type(files) + files = by_conan_package_reference(files) + files + end + + def by_conan_file_type(files) + return files unless params[:conan_file_type] + + files.with_conan_file_type(params[:conan_file_type]) + end + + def by_conan_package_reference(files) + return files unless params[:conan_package_reference] + + files.with_conan_package_reference(params[:conan_package_reference]) + end + end + end +end diff --git a/app/finders/packages/conan/package_finder.rb b/app/finders/packages/conan/package_finder.rb new file mode 100644 index 00000000000..26e9182f4e1 --- /dev/null +++ b/app/finders/packages/conan/package_finder.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Packages + module Conan + class PackageFinder + attr_reader :current_user, :query + + def initialize(current_user, params) + @current_user = current_user + @query = params[:query] + end + + def execute + packages_for_current_user.with_name_like(query).order_name_asc if query + end + + private + + def packages + Packages::Package.conan + end + + def packages_for_current_user + packages.for_projects(projects_visible_to_current_user) + end + + def projects_visible_to_current_user + ::Project.public_or_visible_to_user(current_user) + end + end + end +end diff --git a/app/finders/packages/go/module_finder.rb b/app/finders/packages/go/module_finder.rb new file mode 100644 index 00000000000..ed8bd5599d9 --- /dev/null +++ b/app/finders/packages/go/module_finder.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Packages + module Go + class ModuleFinder + include Gitlab::Golang + + attr_reader :project, :module_name + + def initialize(project, module_name) + module_name = Pathname.new(module_name).cleanpath.to_s + + @project = project + @module_name = module_name + end + + def execute + return if @module_name.blank? || !@module_name.start_with?(local_module_prefix) + + module_path = @module_name[local_module_prefix.length..].split('/') + project_path = project.full_path.split('/') + module_project_path = module_path.shift(project_path.length) + return unless module_project_path == project_path + + Packages::Go::Module.new(@project, @module_name, module_path.join('/')) + end + end + end +end diff --git a/app/finders/packages/go/version_finder.rb b/app/finders/packages/go/version_finder.rb new file mode 100644 index 00000000000..8e2fab8ba35 --- /dev/null +++ b/app/finders/packages/go/version_finder.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Packages + module Go + class VersionFinder + include Gitlab::Golang + + attr_reader :mod + + def initialize(mod) + @mod = mod + end + + def execute + @mod.project.repository.tags + .filter { |tag| semver_tag? tag } + .map { |tag| @mod.version_by(ref: tag) } + .filter { |ver| ver.valid? } + end + + def find(target) + case target + when String + if pseudo_version? target + semver = parse_semver(target) + commit = pseudo_version_commit(@mod.project, semver) + Packages::Go::ModuleVersion.new(@mod, :pseudo, commit, name: target, semver: semver) + else + @mod.version_by(ref: target) + end + + when Gitlab::Git::Ref + @mod.version_by(ref: target) + + when ::Commit, Gitlab::Git::Commit + @mod.version_by(commit: target) + + else + raise ArgumentError.new 'not a valid target' + end + end + end + end +end diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb new file mode 100644 index 00000000000..ffc8c35fbcc --- /dev/null +++ b/app/finders/packages/group_packages_finder.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Packages + class GroupPackagesFinder + attr_reader :current_user, :group, :params + + InvalidPackageTypeError = Class.new(StandardError) + + def initialize(current_user, group, params = { exclude_subgroups: false, order_by: 'created_at', sort: 'asc' }) + @current_user = current_user + @group = group + @params = params + end + + def execute + return ::Packages::Package.none unless group + + packages_for_group_projects + end + + private + + def packages_for_group_projects + packages = ::Packages::Package + .for_projects(group_projects_visible_to_current_user) + .processed + .has_version + .sort_by_attribute("#{params[:order_by]}_#{params[:sort]}") + + packages = filter_by_package_type(packages) + packages = filter_by_package_name(packages) + packages + end + + def group_projects_visible_to_current_user + ::Project + .in_namespace(groups) + .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER) + .with_project_feature + .select { |project| Ability.allowed?(current_user, :read_package, project) } + end + + def package_type + params[:package_type].presence + end + + def groups + return [group] if exclude_subgroups? + + group.self_and_descendants + end + + def exclude_subgroups? + params[:exclude_subgroups] + end + + def filter_by_package_type(packages) + return packages unless package_type + raise InvalidPackageTypeError unless Package.package_types.key?(package_type) + + packages.with_package_type(package_type) + end + + def filter_by_package_name(packages) + return packages unless params[:package_name].present? + + packages.search_by_name(params[:package_name]) + end + end +end diff --git a/app/finders/packages/maven/package_finder.rb b/app/finders/packages/maven/package_finder.rb new file mode 100644 index 00000000000..775db12adb7 --- /dev/null +++ b/app/finders/packages/maven/package_finder.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +module Packages + module Maven + class PackageFinder + attr_reader :path, :current_user, :project, :group + + def initialize(path, current_user, project: nil, group: nil) + @path = path + @current_user = current_user + @project = project + @group = group + end + + def execute + packages_with_path.last + end + + def execute! + packages_with_path.last! + end + + private + + def base + if project + packages_for_a_single_project + elsif group + packages_for_multiple_projects + else + packages + end + end + + def packages_with_path + base.only_maven_packages_with_path(path) + end + + # Produces a query that returns all packages. + def packages + ::Packages::Package.all + end + + # Produces a query that retrieves packages from a single project. + def packages_for_a_single_project + project.packages + end + + # Produces a query that retrieves packages from multiple projects that + # the current user can view within a group. + def packages_for_multiple_projects + ::Packages::Package.for_projects(projects_visible_to_current_user) + end + + # Returns the projects that the current user can view within a group. + def projects_visible_to_current_user + ::Project + .in_namespace(group.self_and_descendants.select(:id)) + .public_or_visible_to_user(current_user) + end + end + end +end diff --git a/app/finders/packages/npm/package_finder.rb b/app/finders/packages/npm/package_finder.rb new file mode 100644 index 00000000000..8599fd07e7f --- /dev/null +++ b/app/finders/packages/npm/package_finder.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true +module Packages + module Npm + class PackageFinder + attr_reader :project, :package_name + + delegate :find_by_version, to: :execute + + def initialize(project, package_name) + @project = project + @package_name = package_name + end + + def execute + packages + end + + private + + def packages + project.packages + .npm + .with_name(package_name) + .last_of_each_version + .preload_files + end + end + end +end diff --git a/app/finders/packages/nuget/package_finder.rb b/app/finders/packages/nuget/package_finder.rb new file mode 100644 index 00000000000..e6fb6712d47 --- /dev/null +++ b/app/finders/packages/nuget/package_finder.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +module Packages + module Nuget + class PackageFinder + MAX_PACKAGES_COUNT = 50 + + def initialize(project, package_name:, package_version: nil, limit: MAX_PACKAGES_COUNT) + @project = project + @package_name = package_name + @package_version = package_version + @limit = limit + end + + def execute + packages.limit_recent(@limit) + end + + private + + def packages + result = @project.packages + .nuget + .has_version + .processed + .with_name_like(@package_name) + result = result.with_version(@package_version) if @package_version.present? + result + end + end + end +end diff --git a/app/finders/packages/package_file_finder.rb b/app/finders/packages/package_file_finder.rb new file mode 100644 index 00000000000..d015f4adfa6 --- /dev/null +++ b/app/finders/packages/package_file_finder.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +class Packages::PackageFileFinder + attr_reader :package, :file_name, :params + + def initialize(package, file_name, params = {}) + @package = package + @file_name = file_name + @params = params + end + + def execute + package_files.last + end + + def execute! + package_files.last! + end + + private + + def package_files + files = package.package_files + + files = by_file_name(files) + + files + end + + def by_file_name(files) + if params[:with_file_name_like] + files.with_file_name_like(file_name) + else + files.with_file_name(file_name) + end + end +end diff --git a/app/finders/packages/package_finder.rb b/app/finders/packages/package_finder.rb new file mode 100644 index 00000000000..0e911491da2 --- /dev/null +++ b/app/finders/packages/package_finder.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +module Packages + class PackageFinder + def initialize(project, package_id) + @project = project + @package_id = package_id + end + + def execute + @project + .packages + .processed + .find(@package_id) + end + end +end diff --git a/app/finders/packages/packages_finder.rb b/app/finders/packages/packages_finder.rb new file mode 100644 index 00000000000..c533cb266a2 --- /dev/null +++ b/app/finders/packages/packages_finder.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Packages + class PackagesFinder + attr_reader :params, :project + + def initialize(project, params = {}) + @project = project + @params = params + + params[:order_by] ||= 'created_at' + params[:sort] ||= 'asc' + end + + def execute + packages = project.packages.processed.has_version + packages = filter_by_package_type(packages) + packages = filter_by_package_name(packages) + packages = order_packages(packages) + packages + end + + private + + def filter_by_package_type(packages) + return packages unless params[:package_type] + + packages.with_package_type(params[:package_type]) + end + + def filter_by_package_name(packages) + return packages unless params[:package_name] + + packages.search_by_name(params[:package_name]) + end + + def order_packages(packages) + packages.sort_by_attribute("#{params[:order_by]}_#{params[:sort]}") + end + end +end diff --git a/app/finders/packages/tags_finder.rb b/app/finders/packages/tags_finder.rb new file mode 100644 index 00000000000..020b3d8072a --- /dev/null +++ b/app/finders/packages/tags_finder.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +class Packages::TagsFinder + attr_reader :project, :package_name, :params + + delegate :find_by_name, to: :execute + + def initialize(project, package_name, params = {}) + @project = project + @package_name = package_name + @params = params + end + + def execute + packages = project.packages + .with_name(package_name) + packages = packages.with_package_type(package_type) if package_type.present? + + Packages::Tag.for_packages(packages) + end + + private + + def package_type + params[:package_type] + end +end diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb index 7b15a3b0c10..e3d5f2ae8de 100644 --- a/app/finders/personal_access_tokens_finder.rb +++ b/app/finders/personal_access_tokens_finder.rb @@ -51,6 +51,8 @@ class PersonalAccessTokensFinder tokens.active when 'inactive' tokens.inactive + when 'active_or_expired' + tokens.not_revoked.expired.or(tokens.active) else tokens end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 8846ff54eb2..7c7cd87a7c1 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -23,6 +23,7 @@ # min_access_level: integer # last_activity_after: datetime # last_activity_before: datetime +# repository_storage: string # class ProjectsFinder < UnionFinder include CustomAttributesFilter @@ -75,6 +76,7 @@ class ProjectsFinder < UnionFinder collection = by_deleted_status(collection) collection = by_last_activity_after(collection) collection = by_last_activity_before(collection) + collection = by_repository_storage(collection) collection end @@ -197,6 +199,14 @@ class ProjectsFinder < UnionFinder end end + def by_repository_storage(items) + if params[:repository_storage].present? + items.where(repository_storage: params[:repository_storage]) # rubocop: disable CodeReuse/ActiveRecord + else + items + end + end + def sort(items) params[:sort].present? ? items.sort_by_attribute(params[:sort]) : items.projects_order_id_desc end diff --git a/app/finders/resource_milestone_event_finder.rb b/app/finders/resource_milestone_event_finder.rb index 7af34f0a4bc..f3b779c8f77 100644 --- a/app/finders/resource_milestone_event_finder.rb +++ b/app/finders/resource_milestone_event_finder.rb @@ -1,69 +1,56 @@ # frozen_string_literal: true class ResourceMilestoneEventFinder - include FinderMethods - - MAX_PER_PAGE = 100 - - attr_reader :params, :current_user, :eventable - - def initialize(current_user, eventable, params = {}) + def initialize(current_user, eventable) @current_user = current_user @eventable = eventable - @params = params end + # Returns the ResourceMilestoneEvents of the eventable + # visible to the user. + # + # @return ResourceMilestoneEvent::ActiveRecord_AssociationRelation def execute - Kaminari.paginate_array(visible_events) + eventable.resource_milestone_events.include_relations + .where(milestone_id: readable_milestone_ids) # rubocop: disable CodeReuse/ActiveRecord end private - def visible_events - @visible_events ||= visible_to_user(events) - end + attr_reader :current_user, :eventable - def events - @events ||= eventable.resource_milestone_events.include_relations.page(page).per(per_page) - end + def readable_milestone_ids + readable_milestones = events_milestones.select do |milestone| + parent_availabilities[key_for_parent(milestone.parent)] + end - def visible_to_user(events) - events.select { |event| visible_for_user?(event) } + readable_milestones.map(&:id).uniq end - def visible_for_user?(event) - milestone = event_milestones[event.milestone_id] - return if milestone.blank? + # rubocop: disable CodeReuse/ActiveRecord + def events_milestones + @events_milestones ||= Milestone.where(id: unique_milestone_ids_from_events) + .includes(:project, :group) + end + # rubocop: enable CodeReuse/ActiveRecord - parent = milestone.parent - parent_availabilities[key_for_parent(parent)] + def relevant_milestone_parents + events_milestones.map(&:parent).uniq end def parent_availabilities - @parent_availabilities ||= relevant_parents.to_h do |parent| + @parent_availabilities ||= relevant_milestone_parents.to_h do |parent| [key_for_parent(parent), Ability.allowed?(current_user, :read_milestone, parent)] end end - def key_for_parent(parent) - "#{parent.class.name}_#{parent.id}" - end - - def event_milestones - @milestones ||= events.map(&:milestone).uniq.to_h do |milestone| - [milestone.id, milestone] - end - end - - def relevant_parents - @relevant_parents ||= event_milestones.map { |_id, milestone| milestone.parent } + # rubocop: disable CodeReuse/ActiveRecord + def unique_milestone_ids_from_events + eventable.resource_milestone_events.select(:milestone_id).distinct end + # rubocop: enable CodeReuse/ActiveRecord - def per_page - [params[:per_page], MAX_PER_PAGE].compact.min - end - - def page - params[:page] || 1 + def key_for_parent(parent) + "#{parent.class.name}_#{parent.id}" end end diff --git a/app/finders/resource_state_event_finder.rb b/app/finders/resource_state_event_finder.rb new file mode 100644 index 00000000000..7f4ac3332cd --- /dev/null +++ b/app/finders/resource_state_event_finder.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class ResourceStateEventFinder + include FinderMethods + + def initialize(current_user, eventable) + @current_user = current_user + @eventable = eventable + end + + def execute + return ResourceStateEvent.none unless can_read_eventable? + + eventable.resource_state_events.includes(:user) # rubocop: disable CodeReuse/ActiveRecord + end + + def can_read_eventable? + return unless eventable + + Ability.allowed?(current_user, read_ability, eventable) + end + + private + + attr_reader :current_user, :eventable + + def read_ability + :"read_#{eventable.class.to_ability_name}" + end +end diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 4f63810423b..941abb70400 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -43,7 +43,7 @@ class SnippetsFinder < UnionFinder include Gitlab::Utils::StrongMemoize attr_accessor :current_user, :params - delegate :explore, :only_personal, :only_project, :scope, to: :params + delegate :explore, :only_personal, :only_project, :scope, :sort, to: :params def initialize(current_user = nil, params = {}) @current_user = current_user @@ -69,7 +69,9 @@ class SnippetsFinder < UnionFinder items = init_collection items = by_ids(items) - items.with_optional_visibility(visibility_from_scope).fresh + items = items.with_optional_visibility(visibility_from_scope) + + items.order_by(sort_param) end private @@ -115,7 +117,7 @@ class SnippetsFinder < UnionFinder queries << snippets_of_authorized_projects if current_user end - find_union(queries, Snippet) + prepared_union(queries) end def snippets_for_a_single_project @@ -202,6 +204,21 @@ class SnippetsFinder < UnionFinder params[:project].is_a?(Project) ? params[:project] : Project.find_by_id(params[:project]) end end + + def sort_param + sort.presence || 'id_desc' + end + + def prepared_union(queries) + return Snippet.none if queries.empty? + return queries.first if queries.length == 1 + + # The queries are going to be part of a global `where` + # therefore we only need to retrieve the `id` column + # which will speed the query + queries.map! { |rel| rel.select(:id) } + Snippet.id_in(find_union(queries, Snippet)) + end end SnippetsFinder.prepend_if_ee('EE::SnippetsFinder') diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 672bbd52b07..a2054f73c9d 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -11,7 +11,7 @@ # author_id: integer # project_id; integer # state: 'pending' (default) or 'done' -# type: 'Issue' or 'MergeRequest' +# type: 'Issue' or 'MergeRequest' or ['Issue', 'MergeRequest'] # class TodosFinder @@ -40,13 +40,14 @@ class TodosFinder def execute return Todo.none if current_user.nil? + raise ArgumentError, invalid_type_message unless valid_types? items = current_user.todos items = by_action_id(items) items = by_action(items) items = by_author(items) items = by_state(items) - items = by_type(items) + items = by_types(items) items = by_group(items) # Filtering by project HAS TO be the last because we use # the project IDs yielded by the todos query thus far @@ -123,12 +124,16 @@ class TodosFinder end end - def type? - type.present? && self.class.todo_types.include?(type) + def types + @types ||= Array(params[:type]).reject(&:blank?) end - def type - params[:type] + def valid_types? + types.all? { |type| self.class.todo_types.include?(type) } + end + + def invalid_type_message + _("Unsupported todo type passed. Supported todo types are: %{todo_types}") % { todo_types: self.class.todo_types.to_a.join(', ') } end def sort(items) @@ -193,9 +198,9 @@ class TodosFinder items.with_states(params[:state]) end - def by_type(items) - if type? - items.for_type(type) + def by_types(items) + if types.any? + items.for_type(types) else items end |