Commit 49c74068 authored by Mario de la Ossa's avatar Mario de la Ossa

Save sorting preference for Issues/MRs in BE

In order to let users' sorting preferences transfer between devices, we
save the preference for issues and MRs (one preference for issues, one
for MRs) in the backend inside the UserPreference object
parent 958a819f
...@@ -129,13 +129,13 @@ module IssuableCollections ...@@ -129,13 +129,13 @@ module IssuableCollections
return sort_param if Gitlab::Database.read_only? return sort_param if Gitlab::Database.read_only?
if user_preference[issuable_sorting_field] != sort_param if user_preference[issuable_sorting_field] != sort_param
user_preference.update_attribute(issuable_sorting_field, sort_param) user_preference.update(issuable_sorting_field => sort_param)
end end
sort_param sort_param
end end
# Implement default_sorting_field method on controllers # Implement issuable_sorting_field method on controllers
# to choose which column to store the sorting parameter. # to choose which column to store the sorting parameter.
def issuable_sorting_field def issuable_sorting_field
nil nil
......
# frozen_string_literal: true # frozen_string_literal: true
module IssuesAction module IssuableCollectionsAction
extend ActiveSupport::Concern extend ActiveSupport::Concern
include IssuableCollections include IssuableCollections
include IssuesCalendar include IssuesCalendar
...@@ -18,6 +18,12 @@ module IssuesAction ...@@ -18,6 +18,12 @@ module IssuesAction
format.atom { render layout: 'xml.atom' } format.atom { render layout: 'xml.atom' }
end end
end end
def merge_requests
@merge_requests = issuables_collection.page(params[:page])
@issuable_meta_data = issuable_meta_data(@merge_requests, collection_type)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
def issues_calendar def issues_calendar
...@@ -26,8 +32,29 @@ module IssuesAction ...@@ -26,8 +32,29 @@ module IssuesAction
private private
def issuable_sorting_field
case action_name
when 'issues'
Issue::SORTING_PREFERENCE_FIELD
when 'merge_requests'
MergeRequest::SORTING_PREFERENCE_FIELD
else
nil
end
end
def finder_type def finder_type
(super if defined?(super)) || case action_name
(IssuesFinder if %w(issues issues_calendar).include?(action_name)) when 'issues', 'issues_calendar'
IssuesFinder
when 'merge_requests'
MergeRequestsFinder
else
nil
end
end
def finder_options
super.merge(non_archived: true)
end end
end end
# frozen_string_literal: true
module MergeRequestsAction
extend ActiveSupport::Concern
include IssuableCollections
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def merge_requests
@merge_requests = issuables_collection.page(params[:page])
@issuable_meta_data = issuable_meta_data(@merge_requests, collection_type)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private
def finder_type
(super if defined?(super)) ||
(MergeRequestsFinder if action_name == 'merge_requests')
end
def finder_options
super.merge(non_archived: true)
end
end
# frozen_string_literal: true # frozen_string_literal: true
class DashboardController < Dashboard::ApplicationController class DashboardController < Dashboard::ApplicationController
include IssuesAction include IssuableCollectionsAction
include MergeRequestsAction
prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) } prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) } prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
......
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
class GroupsController < Groups::ApplicationController class GroupsController < Groups::ApplicationController
include API::Helpers::RelatedResourcesHelpers include API::Helpers::RelatedResourcesHelpers
include IssuesAction include IssuableCollectionsAction
include MergeRequestsAction
include ParamsBackwardCompatibility include ParamsBackwardCompatibility
include PreviewMarkdown include PreviewMarkdown
......
...@@ -191,6 +191,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -191,6 +191,10 @@ class Projects::IssuesController < Projects::ApplicationController
protected protected
def issuable_sorting_field
Issue::SORTING_PREFERENCE_FIELD
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def issue def issue
return @issue if defined?(@issue) return @issue if defined?(@issue)
......
...@@ -230,6 +230,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -230,6 +230,10 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
alias_method :issuable, :merge_request alias_method :issuable, :merge_request
alias_method :awardable, :merge_request alias_method :awardable, :merge_request
def issuable_sorting_field
MergeRequest::SORTING_PREFERENCE_FIELD
end
def merge_params def merge_params
params.permit(merge_params_attributes) params.permit(merge_params_attributes)
end end
......
...@@ -26,6 +26,8 @@ class Issue < ActiveRecord::Base ...@@ -26,6 +26,8 @@ class Issue < ActiveRecord::Base
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
SORTING_PREFERENCE_FIELD = :issues_sort
belongs_to :project belongs_to :project
belongs_to :moved_to, class_name: 'Issue' belongs_to :moved_to, class_name: 'Issue'
belongs_to :closed_by, class_name: 'User' belongs_to :closed_by, class_name: 'User'
......
...@@ -21,6 +21,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -21,6 +21,8 @@ class MergeRequest < ActiveRecord::Base
self.reactive_cache_refresh_interval = 10.minutes self.reactive_cache_refresh_interval = 10.minutes
self.reactive_cache_lifetime = 10.minutes self.reactive_cache_lifetime = 10.minutes
SORTING_PREFERENCE_FIELD = :merge_requests_sort
ignore_column :locked_at, ignore_column :locked_at,
:ref_fetched, :ref_fetched,
:deleted_at :deleted_at
......
---
title: Save issues/merge request sorting options to backend
merge_request: 24198
author:
type: added
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddSortingFieldsToUserPreference < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def up
add_column :user_preferences, :issues_sort, :string
add_column :user_preferences, :merge_requests_sort, :string
end
def down
remove_column :user_preferences, :issues_sort
remove_column :user_preferences, :merge_requests_sort
end
end
...@@ -2146,6 +2146,8 @@ ActiveRecord::Schema.define(version: 20190124200344) do ...@@ -2146,6 +2146,8 @@ ActiveRecord::Schema.define(version: 20190124200344) do
t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
t.string "issues_sort"
t.string "merge_requests_sort"
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
end end
......
...@@ -17,10 +17,55 @@ describe IssuableCollections do ...@@ -17,10 +17,55 @@ describe IssuableCollections do
controller = klass.new controller = klass.new
allow(controller).to receive(:params).and_return(ActionController::Parameters.new(params)) allow(controller).to receive(:params).and_return(ActionController::Parameters.new(params))
allow(controller).to receive(:current_user).and_return(user)
controller controller
end end
describe '#set_sort_order_from_user_preference' do
describe 'when sort param given' do
let(:params) { { sort: 'updated_desc' } }
context 'when issuable_sorting_field is defined' do
before do
controller.class.define_method(:issuable_sorting_field) { :issues_sort}
end
it 'sets user_preference with the right value' do
controller.send(:set_sort_order_from_user_preference)
expect(user.user_preference.reload.issues_sort).to eq('updated_desc')
end
end
context 'when no issuable_sorting_field is defined on the controller' do
it 'does not touch user_preference' do
allow(user).to receive(:user_preference)
controller.send(:set_sort_order_from_user_preference)
expect(user).not_to have_received(:user_preference)
end
end
end
context 'when a user sorting preference exists' do
let(:params) { {} }
before do
controller.class.define_method(:issuable_sorting_field) { :issues_sort }
end
it 'returns the set preference' do
user.user_preference.update(issues_sort: 'updated_asc')
sort_preference = controller.send(:set_sort_order_from_user_preference)
expect(sort_preference).to eq('updated_asc')
end
end
end
describe '#set_set_order_from_cookie' do describe '#set_set_order_from_cookie' do
describe 'when sort param given' do describe 'when sort param given' do
let(:cookies) { {} } let(:cookies) { {} }
......
...@@ -42,7 +42,9 @@ describe Projects::IssuesController do ...@@ -42,7 +42,9 @@ describe Projects::IssuesController do
it_behaves_like "issuables list meta-data", :issue it_behaves_like "issuables list meta-data", :issue
it_behaves_like 'set sort order from user preference' it_behaves_like 'set sort order from user preference' do
let(:sorting_param) { 'updated_asc' }
end
it "returns index" do it "returns index" do
get :index, params: { namespace_id: project.namespace, project_id: project } get :index, params: { namespace_id: project.namespace, project_id: project }
......
...@@ -153,7 +153,9 @@ describe Projects::MergeRequestsController do ...@@ -153,7 +153,9 @@ describe Projects::MergeRequestsController do
it_behaves_like "issuables list meta-data", :merge_request it_behaves_like "issuables list meta-data", :merge_request
it_behaves_like 'set sort order from user preference' it_behaves_like 'set sort order from user preference' do
let(:sorting_param) { 'updated_asc' }
end
context 'when page param' do context 'when page param' do
let(:last_page) { project.merge_requests.page().total_pages } let(:last_page) { project.merge_requests.page().total_pages }
......
...@@ -3,9 +3,10 @@ ...@@ -3,9 +3,10 @@
require 'spec_helper' require 'spec_helper'
describe UserPreference do describe UserPreference do
let(:user_preference) { create(:user_preference) }
describe '#set_notes_filter' do describe '#set_notes_filter' do
let(:issuable) { build_stubbed(:issue) } let(:issuable) { build_stubbed(:issue) }
let(:user_preference) { create(:user_preference) }
shared_examples 'setting system notes' do shared_examples 'setting system notes' do
it 'returns updated discussion filter' do it 'returns updated discussion filter' do
...@@ -50,4 +51,26 @@ describe UserPreference do ...@@ -50,4 +51,26 @@ describe UserPreference do
end end
end end
end end
describe 'sort_by preferences' do
shared_examples_for 'a sort_by preference' do
it 'allows nil sort fields' do
user_preference.update(attribute => nil)
expect(user_preference).to be_valid
end
end
context 'merge_requests_sort attribute' do
let(:attribute) { :merge_requests_sort }
it_behaves_like 'a sort_by preference'
end
context 'issues_sort attribute' do
let(:attribute) { :issues_sort }
it_behaves_like 'a sort_by preference'
end
end
end end
...@@ -2,18 +2,12 @@ shared_examples 'set sort order from user preference' do ...@@ -2,18 +2,12 @@ shared_examples 'set sort order from user preference' do
describe '#set_sort_order_from_user_preference' do describe '#set_sort_order_from_user_preference' do
# There is no issuable_sorting_field defined in any CE controllers yet, # There is no issuable_sorting_field defined in any CE controllers yet,
# however any other field present in user_preferences table can be used for testing. # however any other field present in user_preferences table can be used for testing.
let(:sorting_field) { :issue_notes_filter }
let(:sorting_param) { 'any' }
before do
allow(controller).to receive(:issuable_sorting_field).and_return(sorting_field)
end
context 'when database is in read-only mode' do context 'when database is in read-only mode' do
it 'it does not update user preference' do it 'it does not update user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true) allow(Gitlab::Database).to receive(:read_only?).and_return(true)
expect_any_instance_of(UserPreference).not_to receive(:update_attribute).with(sorting_field, sorting_param) expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param } get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
end end
...@@ -23,7 +17,7 @@ shared_examples 'set sort order from user preference' do ...@@ -23,7 +17,7 @@ shared_examples 'set sort order from user preference' do
it 'updates user preference' do it 'updates user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(false) allow(Gitlab::Database).to receive(:read_only?).and_return(false)
expect_any_instance_of(UserPreference).to receive(:update_attribute).with(sorting_field, sorting_param) expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param } get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
end end
......
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