Commit 8e14a407 authored by Jacob Schatz's avatar Jacob Schatz

Merge branch '17932-move-to-project-dropdown' into 'master'

Move to project dropdown with infinite scroll for better performance

## What does this MR do?

On the Move dropdown on the edit issue page we introduced infinite scrolling to just return a limited number of projects, 50 items. So if the user can move the issue to 50 or more items when scroll down on the list a new set of projects will be requested to the server.

## Are there points in the code the reviewer needs to double check?

## Why was this MR needed?

See #17932

## What are the relevant issue numbers?

Closes #17932

## Screenshots (if relevant)

## Does this MR meet the acceptance criteria?

- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- ~~[ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)~~
- ~~[ ] API support added~~
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)

See merge request !5686
parents 39820d80 ff903e64
...@@ -125,6 +125,7 @@ v 8.11.0 (unreleased) ...@@ -125,6 +125,7 @@ v 8.11.0 (unreleased)
- edit_blob_link will use blob passed onto the options parameter - edit_blob_link will use blob passed onto the options parameter
- Make error pages responsive (Takuya Noguchi) - Make error pages responsive (Takuya Noguchi)
- The performance of the project dropdown used for moving issues has been improved - The performance of the project dropdown used for moving issues has been improved
- Move to project dropdown with infinite scroll for better performance
- Fix skip_repo parameter being ignored when destroying a namespace - Fix skip_repo parameter being ignored when destroying a namespace
- Add all builds into stage/job dropdowns on builds page - Add all builds into stage/job dropdowns on builds page
- Change requests_profiles resource constraint to catch virtually any file - Change requests_profiles resource constraint to catch virtually any file
......
...@@ -102,20 +102,34 @@ ...@@ -102,20 +102,34 @@
}; };
IssuableForm.prototype.initMoveDropdown = function() { IssuableForm.prototype.initMoveDropdown = function() {
var $moveDropdown; var $moveDropdown, pageSize;
$moveDropdown = $('.js-move-dropdown'); $moveDropdown = $('.js-move-dropdown');
if ($moveDropdown.length) { if ($moveDropdown.length) {
pageSize = $moveDropdown.data('page-size');
return $('.js-move-dropdown').select2({ return $('.js-move-dropdown').select2({
ajax: { ajax: {
url: $moveDropdown.data('projects-url'), url: $moveDropdown.data('projects-url'),
results: function(data) { quietMillis: 125,
data: function(term, page, context) {
return { return {
results: data search: term,
offset_id: context
}; };
}, },
data: function(query) { results: function(data) {
var context,
more;
if (data.length >= pageSize)
more = true;
if (data[data.length - 1])
context = data[data.length - 1].id;
return { return {
search: query results: data,
more: more,
context: context
}; };
} }
}, },
......
...@@ -45,7 +45,8 @@ ...@@ -45,7 +45,8 @@
min-width: 175px; min-width: 175px;
} }
.select2-results .select2-result-label { .select2-results .select2-result-label,
.select2-more-results {
padding: 10px 15px; padding: 10px 15px;
} }
......
class MoveToProjectFinder class MoveToProjectFinder
PAGE_SIZE = 50
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -8,6 +10,10 @@ class MoveToProjectFinder ...@@ -8,6 +10,10 @@ class MoveToProjectFinder
projects = projects.search(search) if search.present? projects = projects.search(search) if search.present?
projects = projects.excluding_project(from_project) projects = projects.excluding_project(from_project)
# infinite scroll using offset
projects = projects.where('projects.id < ?', offset_id) if offset_id.present?
projects = projects.limit(PAGE_SIZE)
# to ask for Project#name_with_namespace # to ask for Project#name_with_namespace
projects.includes(namespace: :owner) projects.includes(namespace: :owner)
end end
......
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
= label_tag :move_to_project_id, 'Move', class: 'control-label' = label_tag :move_to_project_id, 'Move', class: 'control-label'
.col-sm-10 .col-sm-10
.issuable-form-select-holder .issuable-form-select-holder
= hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id) } = hidden_field_tag :move_to_project_id, nil, class: 'js-move-dropdown', data: { placeholder: 'Select project', projects_url: autocomplete_projects_path(project_id: @project.id), page_size: MoveToProjectFinder::PAGE_SIZE }
&nbsp; &nbsp;
%span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default', %span{ data: { toggle: 'tooltip', placement: 'auto top' }, style: 'cursor: default',
title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' }
......
...@@ -237,6 +237,56 @@ describe AutocompleteController do ...@@ -237,6 +237,56 @@ describe AutocompleteController do
end end
end end
context 'authorized projects apply limit' do
before do
authorized_project2 = create(:project)
authorized_project3 = create(:project)
authorized_project.team << [user, :master]
authorized_project2.team << [user, :master]
authorized_project3.team << [user, :master]
stub_const 'MoveToProjectFinder::PAGE_SIZE', 2
end
describe 'GET #projects with project ID' do
before do
get(:projects, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body).to be_kind_of(Array)
expect(body.size).to eq 3 # Of a total of 4
end
end
end
context 'authorized projects with offset' do
before do
authorized_project2 = create(:project)
authorized_project3 = create(:project)
authorized_project.team << [user, :master]
authorized_project2.team << [user, :master]
authorized_project3.team << [user, :master]
end
describe 'GET #projects with project ID and offset_id' do
before do
get(:projects, project_id: project.id, offset_id: authorized_project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
end
end
end
context 'authorized projects without admin_issue ability' do context 'authorized projects without admin_issue ability' do
before(:each) do before(:each) do
authorized_project.team << [user, :guest] authorized_project.team << [user, :guest]
......
...@@ -51,6 +51,28 @@ describe MoveToProjectFinder do ...@@ -51,6 +51,28 @@ describe MoveToProjectFinder do
expect(subject.execute(project).to_a).to eq([other_reporter_project]) expect(subject.execute(project).to_a).to eq([other_reporter_project])
end end
it 'returns a page of projects ordered by id in descending order' 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 given offset id' 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 end
context 'search' do context 'search' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment