diff options
author | Paco Guzman <pacoguzmanp@gmail.com> | 2016-08-09 14:36:06 +0200 |
---|---|---|
committer | Paco Guzman <pacoguzmanp@gmail.com> | 2016-08-09 16:05:10 +0200 |
commit | ca9de28f0de3c693d191f5bafc227e509f2252dd (patch) | |
tree | f9f1b264f9baeaff35041c42e2d9d9a7659acd38 | |
parent | 51f40a919a6686203ffc638a1c82e179811959cb (diff) | |
download | gitlab-ce-17932-move-to-project-dropdown-sql.tar.gz |
Use just SQL to check is a user can admin_issue on a project17932-move-to-project-dropdown-sql
Using offset pagination instead pages to avoid a count query
Tradeoff
- we duplicate how we check admin_issue in a SQL relation in the Ability class
-rw-r--r-- | app/controllers/autocomplete_controller.rb | 2 | ||||
-rw-r--r-- | app/finders/move_to_project_finder.rb | 25 | ||||
-rw-r--r-- | app/models/project_team.rb | 12 | ||||
-rw-r--r-- | app/models/user.rb | 7 | ||||
-rw-r--r-- | spec/finders/move_to_project_finder_spec.rb | 97 |
5 files changed, 111 insertions, 32 deletions
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 350106a0b29..c39a0e2c6a5 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -36,8 +36,6 @@ class AutocompleteController < ApplicationController project = Project.find_by_id(params[:project_id]) projects = projects_finder.execute(project, search: params[:search], offset_id: params[:offset_id]) - ActiveRecord::Associations::Preloader.new.preload(projects, { namespace: :owner }) - no_project = { id: 0, name_with_namespace: 'No project', diff --git a/app/finders/move_to_project_finder.rb b/app/finders/move_to_project_finder.rb index 02592796ddc..b2a0dd7d699 100644 --- a/app/finders/move_to_project_finder.rb +++ b/app/finders/move_to_project_finder.rb @@ -6,26 +6,15 @@ class MoveToProjectFinder end def execute(from_project, search: nil, offset_id: nil) - projects = @user.authorized_projects + projects = @user.projects_where_can_admin_issues projects = projects.search(search) if search.present? - projects = skip_projects_before(projects, offset_id.to_i) if offset_id.present? - ProjectTeam.preload_max_member_access(projects.map(&:team)) - projects = take_projects(projects) - projects.delete(from_project) - projects - end - - private + projects = projects.where.not(id: from_project.id).order(id: :desc) - def skip_projects_before(projects, offset_project_id) - index = projects.index { |project| project.id == offset_project_id } - - index ? projects.drop(index + 1) : projects - end + # infinite scroll using offset + projects = projects.where("projects.id < #{offset_id}") if offset_id.present? + projects = projects.limit(PAGE_SIZE) - def take_projects(projects) - projects.lazy.select do |project| - @user.can?(:admin_issue, project) - end.take(PAGE_SIZE).to_a + # to ask for Project#name_with_namespace + projects.preload(namespace: :owner) end end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 2c630c1f626..b7c1782646f 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -5,18 +5,6 @@ class ProjectTeam @project = project end - class << self - def preload_max_member_access(project_teams) - projects = project_teams.map(&:project) - run_preload(projects, [:namespace, :group]) - run_preload(projects.select(&:allowed_to_share_with_group?), [project_group_links: :group]) - end - - def run_preload(projects, associations) - ActiveRecord::Associations::Preloader.new.preload(projects, associations) - end - end - # Shortcut to add users # # Use: diff --git a/app/models/user.rb b/app/models/user.rb index db747434959..18de66f6f34 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -429,6 +429,13 @@ class User < ActiveRecord::Base owned_groups.select(:id), namespace.id).joins(:namespace) end + # Returns projects user can admin_issue on (for example to move an issue to that project). + # + # This logic is extracted from `Ability#project_abilities` into a SQL form. + def projects_where_can_admin_issues + authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false) + end + def is_admin? admin end diff --git a/spec/finders/move_to_project_finder_spec.rb b/spec/finders/move_to_project_finder_spec.rb new file mode 100644 index 00000000000..67ec15ff27d --- /dev/null +++ b/spec/finders/move_to_project_finder_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe MoveToProjectFinder do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:no_access_project) { create(:project) } + let(:guess_project) { create(:project) } + let(:reporter_project) { create(:project) } + let(:developer_project) { create(:project) } + let(:master_project) { create(:project) } + + subject { described_class.new(user) } + + describe '#execute' do + context 'filter' do + it 'does not return projects under Gitlab::Access::REPORTER' do + guess_project.team << [user, :guest] + + expect(subject.execute(project)).to be_empty + end + + it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id desc' do + reporter_project.team << [user, :reporter] + developer_project.team << [user, :developer] + master_project.team << [user, :master] + + expect(subject.execute(project).to_a).to eq([master_project, developer_project, reporter_project]) + end + + it 'does not return the project we pass (from the project we move the issue)' do + project.team << [user, :reporter] + + expect(subject.execute(project).to_a).to be_empty + end + + it 'does not return archived projects' do + reporter_project.team << [user, :reporter] + reporter_project.update_attributes(archived: true) + other_reporter_project = create(:project) + other_reporter_project.team << [user, :reporter] + + expect(subject.execute(project).to_a).to eq([other_reporter_project]) + end + + it 'does not return projects with issues disabled' do + reporter_project.team << [user, :reporter] + reporter_project.update_attributes(issues_enabled: false) + other_reporter_project = create(:project) + other_reporter_project.team << [user, :reporter] + + expect(subject.execute(project).to_a).to eq([other_reporter_project]) + end + + it 'returns a page of projects ordered by id desc' do + stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 + + reporter_project.team << [user, :reporter] + developer_project.team << [user, :developer] + master_project.team << [user, :master] + + expect(subject.execute(project).to_a).to eq([master_project, developer_project]) + end + + it 'returns projects after the offset_id provided' do + stub_const 'MoveToProjectFinder::PAGE_SIZE', 2 + + reporter_project.team << [user, :reporter] + developer_project.team << [user, :developer] + master_project.team << [user, :master] + + expect(subject.execute(project, search: nil, offset_id: master_project.id).to_a).to eq([developer_project, reporter_project]) + expect(subject.execute(project, search: nil, offset_id: developer_project.id).to_a).to eq([reporter_project]) + expect(subject.execute(project, search: nil, offset_id: reporter_project.id).to_a).to be_empty + end + end + + context 'search' do + it 'uses Project#search' do + expect(user).to receive_message_chain(:can_admin_issue_projects, :search) { Project.all } + + subject.execute(project, search: 'wadus') + end + + it 'returns searched projects' do + foo_project = create(:project) + foo_project.team << [user, :master] + + wadus_project = create(:project, name: 'wadus') + wadus_project.team << [user, :master] + + expect(subject.execute(project).to_a).to eq([wadus_project, foo_project]) + expect(subject.execute(project, search: 'wadus').to_a).to eq([wadus_project]) + end + end + end +end |